Проект многоканального индикатора звука

ourlive
Offline
Зарегистрирован: 26.05.2012

Предистория. Протолкнули в нашу контору умелые люди многоканальный индикатор звука. По описанию это один из atmel'овских камней с 8-ти (или 10-ти) битным АЦП. 8 своих мультиплексированных каналов расширенны внешним мультиплексом до 32-х. Далее контроллер гонит данные в com1 в hex на комп в логарифмированной форме (т.е. приведённых к дБ). Формат строки [начало строки chr(130)] и [64 байта] с интервалом 50мкс. Аппаратная часть по многим причинам недоступна. Комповская оболочка отображающая инфу также нас сильно не устраивает. Ну и решил я посмотреть что такое процессинг на примере написания альтернативной оболочки.

ourlive
Offline
Зарегистрирован: 26.05.2012

получилась такая штука, которая щас на тестировании.


// ИЗУС версия 1.2
// с ком порта приходят данные в формате начало строки char(13) + 64 байта
// 64 байта - hex значения для 32х звуковых каналов, приведённых к уровню в дБ

import processing.serial.*;
import pitaru.sonia_v2_9.*;

Serial myPort;  
String bufer="";               //буфер чтения
int[] kanal = new int[33];     //поканальные уровни в dB
int scaleX, scaleY;            //экранный коэффициент
PrintWriter log_output;        //ведение лога работы
int log_length=10000;          //длина лога
String comport;                //ком порт
int[] kanal_out = new int[33]; //номер индикатора вывода канала
String[] kanal_name = new String[33];       //имена каналов
String[] kanal_sr = new String[33];         //строки накопления значений каналов
int[] kanal_stat = new int[33];             //таймер замирания канала
int[] kanal_prev = new int[33];             //предыдущее значение
int[] kanal_max = new int[33];              //среднее значения по каналам
int[] kanal_min = new int[33];              //таймер занижения канала
boolean[] kanal_play = new boolean[33];     //кнопки
boolean[] kanal_static = new boolean[33];   //ключ замирания канала  
boolean[] kanal_minimum = new boolean[33];  //ключ занижения канала 
boolean[] kanal_maximum = new boolean[33];  //ключ завышения канала
boolean[] signal_key = new boolean[33];     //ключ сигнала, реакция на звышение или на занижение
int zamiranie=300;                          //порог замирания
int pauza=10;                               //время допустимой паузы
int korrdb=-3;                              //коррекция показаний
int maxdb=0;                                //порог завышения
int kanal_signal=-45;                       //порог срабатывания сигнала по снижению уровня
Sample alarm_mini;             // звук. 
Sample alarm_maxi;
                               // mySample.repeat(); - начать воспроизведение
                               // mySample.stop(1); - остановить воспроизведение
                               // mySample.setVolume(vol); - громкость vol от 0 до 1
                               // mySample.setRate(rate); - скорость воспроизведения rate от 0 до 88200
                               // mySample.setPan(pan); - баланс (только для моно записей) pan от -1 до 1
                               // mySample.getCurrentFrame() - текущий воспроизводимый фрейм
                               // mySample.getNumFrames() - всего фреймов


void setup() {

//OptionsDefault();                   
logStart();                                                      //ведение лога работы
options();                                                       //чтение установок
com_start();
Sonia.start(this);                                               // запуск библиотеки Sonia 
alarm_mini = new Sample("alarmmini.wav");                        // создание новых семплов
alarm_maxi = new Sample("alarmmaxi.wav");
//scaleX=int(displayWidth/100);                                  //определение параметров экрана
//scaleY=int(displayHeight/100);
scaleX=int(500/100); 
scaleY=int(500/100);

fill(100, 100, 100); 
//size(displayWidth, displayHeight);                                    //установка размеров окна
size(scaleX*100, scaleY*100);
for (int i = 0; i < 32; i = i+1) {                                      //присвение стартовых значений
kanal_sr[i]="0000000000";
kanal[kanal_out[i]]=0; 
kanal_prev[i]=0; 
kanal_static[i]=false; 
kanal_min[i]=0; 
kanal_minimum[i]=false;
signal_key[i]=true;
}
signal_start();
alarm_mini.play();
} //***********************************setup

void draw() { //***********************основной цикл
 while (myPort.available() > 0) {
    char inByte = myPort.readChar();                             // прочитать символ с порта
    if (inByte==char(13)) {                                      // после прочтения символа начала строки: char(13)
      
      ConvrtHexDec ();                                           // отправить буфер на преобразование в dec с разделением на каналы
// группа событий после получения данных
      kanal_control_f();                                            // строка накопления
      indicator();
      signal();
      
      bufer=""; }                                                // сбросить буфер
    else {bufer=bufer+inByte;} 
    }  
} //***********************************draw

void ConvrtHexDec (){ //***************преобразование прочитанных у.е. в dB
  if (bufer.length()==64) {                                                   // если буфер не соответствует нужной длине, пропустить обработку
    for (int i = 0; i < 32; i = i+1) {           
    kanal[kanal_out[i]]=int((unhex(bufer.substring(i*2,i*2+2))-214)*0.234)+korrdb; //выделить их строки HEX, преобразовать в DEC, и конвертировать в dB округлив до целых 0-255уе / (-50)-(+10)dB - 
    }         //присвоение с учётом перенаправления вывода, КОРРЕКЦИЯ -3дБ из-за ошибок АЦП для живого сигнала(выненено в файл настроек)
  }//буфер
} //***********************************ConvrtHexDec

void indicator () { //*****************обеспечение индикации
    fill(100, 100, 100);                                                                  // серое поле
    rect(0, 0, scaleX*100, scaleY*100);
    for (int i = 0; i < 16; i = i+1) {
    fill(255, 255, 255);
    rect(scaleX, scaleY+scaleY*i*6, scaleX*45, scaleY*5);                                 // белое поле
    rect(scaleX*50, scaleY+scaleY*i*6, scaleX*45, scaleY*5);
     
    if (kanal_max[i]>maxdb) {fill(255, 0, 0);} else if (kanal_max[i]<-10)  {fill(0, 0, 255);} else {fill(255, 255, 0);} // псевдопиковый индикатор
    rect(int(scaleX*45*(kanal_max[i]+50-korrdb)/59), scaleY+scaleY*i*6, scaleX, scaleY*5);      
    if (kanal_max[i+16]>maxdb) {fill(255, 0, 0);} else if (kanal_max[i+16]<-10)  {fill(0, 0, 255);} else {fill(255, 255, 0);}
    rect(int(scaleX*45*(kanal_max[i+16]+50-korrdb)/59)+scaleX*49, scaleY+scaleY*i*6, scaleX, scaleY*5);

    if (kanal[i]>maxdb) {fill(255, 0, 0);} else if (kanal_static[i]==true) {fill(0, 0, 255);} else {fill(0, 255, 0);}   // пиковый индикатор красный если больше 0дБ, синий при замирании канала, зелёный если норма
    rect(scaleX, scaleY+scaleY*i*6, int(scaleX*45*(kanal[i]+50-korrdb)/59), scaleY*5);
    
    if (kanal[i+16]>maxdb) {fill(255, 0, 0);} else if (kanal_static[i+16]==true) {fill(0, 0, 255);}else {fill(0, 255, 0);}
    rect(scaleX*50, scaleY+scaleY*i*6, int(scaleX*45*(kanal[i+16]+50-korrdb)/59), scaleY*5);
                      
    textSize(scaleY*4);                                                                   // печать имён каналов и измеренных значений
    if (kanal_minimum[i]==true) {fill(255, 0, 0);}  else {fill(0, 0, 0);}  //если среднее пиковое падает ниже порогового и сигнализация включена
    text(kanal_name[i], scaleX*2, scaleY*5+scaleY*i*6);
    if (kanal_minimum[i+16]==true) {fill(255, 0, 0);}  else {fill(0, 0, 0);}    
    text(kanal_name[i+16], scaleX*52, scaleY*5+scaleY*i*6);
    
    fill(0, 0, 0);  
    textSize(scaleY*2);
    text(kanal[i], scaleX*36, scaleY*5+scaleY*i*6);
    text(kanal[i+16], scaleX*85, scaleY*5+scaleY*i*6);
    
    textSize(scaleY);
    if ((kanal_play[i]==true) && static_kanal(kanal_stat[i], millis())>0) { text(static_kanal(kanal_stat[i], millis()), scaleX*42, scaleY*5+scaleY*i*6);}    // замирания
    if ((kanal_play[i+16]==true) && static_kanal(kanal_stat[i+16], millis())>0) {text(static_kanal(kanal_stat[i+16], millis()), scaleX*91, scaleY*5+scaleY*i*6);}
    
    if (kanal_play[i]==false) {fill(200, 200, 200);} else {fill(0, 200, 200);}            // кнопочки
    rect(scaleX*47, scaleY+scaleY*i*6, scaleX, scaleY*5, 7);
    if (kanal_play[i+16]==false) {fill(200, 200, 200);} else {fill(0, 200, 200);}   
    rect(scaleX*96, scaleY+scaleY*i*6, scaleX, scaleY*5, 9);    
    }
    fill(255, 255, 255); 
    rect(0, scaleY*96, scaleX*100, scaleY*4);
    fill(0, 0, 0); 
    textSize(scaleY*2);
    text(static_kanal(0,millis()), scaleX, scaleY*99);
    
} //***********************************indicator

void options() { //********************чтение установочных параметров из файла
  String lines[] = loadStrings("options.ini");
if (lines.length<81) {log_output.println("восстановление файла options"); log_output.flush(); OptionsDefault();  exit(); } // восстановление файла options и закрытие программы
print("длина файла оптионс");println(lines.length);
for (int i = 0 ; i < lines.length; i++) {
if (lines[i].equals("номер_порта:")==true) {comport=lines[i+1];} //при нахождении заголовка значения, соответствующим переменным присвоить значения
if (lines[i].equals("расположение_каналов:")==true) {for (int z = 0 ; z < 32; z++) {kanal_out[z]=int(lines[i+1+z].substring(3,5));}}
if (lines[i].equals("имена_каналов:")==true) {for (int z = 0 ; z < 32; z++) {kanal_name[z]=lines[i+1+z].substring(3,lines[i+1+z].length());} control_kanal_out();}
if (lines[i].equals("сигнализация:")==true) {kanal_signal=int(lines[i+1]);} 
if (lines[i].equals("замирание:")==true) {zamiranie=int(lines[i+1]); }
if (lines[i].equals("коррекция(дБ):")==true) {korrdb=int(lines[i+1]); }
if (lines[i].equals("пауза:")==true) {pauza=int(lines[i+1]); }
}

}// ***********************************options


void OptionsDefault() { //*************установка опций по умолчанию
  String words = "файл_настроек  номер_порта: COM1";
  words += "  расположение_каналов: 00-00 01-01 02-02 03-03 04-04 05-05 06-06 07-07 08-08 09-09 10-10 11-11 12-12 13-13 14-14 15-15 16-16 17-17 18-18 19-19 20-20 21-21 22-22 23-23 24-24 25-25 26-26 26-27 28-28 29-29 30-30 31-31";
  words += "  имена_каналов: 00-канал00 01-канал01 02-канал02 03-канал03 04-канал04 05-канал05 06-канал06 07-канал07 08-канал08 09-канал09 10-канал10 11-канал11 12-канал12 13-канал13 14-канал14 15-канал15 16-канал16 17-канал17 18-канал18 19-канал19";
  words += " 20-канал20 21-канал21 22-канал22 23-канал23 24-канал24 25-канал25 26-канал26 26-канал27 28-канал28 29-канал29 30-канал30 31-канал31";
  words += "  сигнализация: -45  замирание: 300  пауза: 10  коррекция(дБ): -3";
  String[] list = split(words, ' ');
  saveStrings("options.ini", list);
  
} //***********************************OptionsDefault

void logStart() { // ******************старт ведения лога
String lines[] = loadStrings("log.txt");    //чтение предыдущих значений 
log_output = createWriter("log.txt");       //создание файла лога
if (lines.length<log_length) {              //перезапись предыдущих значений 
  for (int i = 0 ; i < lines.length; i++) { //если строк меньше
    log_output.println(lines[i]); }}          
  else {
  for (int i = lines.length-log_length; i < lines.length; i++) { //если строк больше
log_output.println(lines[i]); }} 
log_output.print(time_now()); 

log_output.println("старт приложения"); 
log_output.flush();
} //************************************logStart

void com_start () { // *****************стартовые настройки компорта
String port[] = Serial.list();                                                                //создание списка портов
 for (int i = 0 ; i < port.length; i++) { 
   if (port[i].equals(comport)==true) {myPort = new Serial(this, Serial.list()[i], 9600); }}  //проверка наличие порка указанного в настройках, при наличии - открыть порт
  if (myPort==null) {log_output.print("указанный com порт не обнаружен, обнаружены порты: "); //при отсутствии - записать сообщение в лог и закрыть приложение
  for (int i = 0 ; i < port.length; i++) {log_output.print(port[i]);log_output.print(" ");} 
  log_output.println(" требуется изменить настройки"); log_output.flush(); exit();}
} //************************************com_start

void signal_start () {//****************чтение последних состояний кнопок
  String lines[] = loadStrings("signal.ini");    //чтение предыдущих значений 
  if (lines.length==33) {for (int i = 0 ; i < 31; i++) {kanal_play[i]=boolean(lines[i]);}} else {for (int i = 0 ; i < 31; i++) {kanal_play[i]=false;}}
} //************************************signal_start

void control_kanal_out() { // **********контроль правильности перенаправления каналов
  for (int x = 0 ; x < 31; x++) { for (int y = x+1 ; y < 32; y++) {
    if (kanal_out[x]==kanal_out[y]) {
    log_output.println(" ошибка перенаправления каналов "); 
    log_output.print("канал №"); 
    log_output.print(x); 
    log_output.print(" перенаправлен на индикатор #");
    log_output.print(kanal_out[x]);
    log_output.print(" также как и канал №");
    log_output.print(y);
    OptionsDefault();
    log_output.println(" файл options восстановлен по умолчанию");
    log_output.flush();
    exit();
    }
  }}
} //************************************control_kanal_out()

void kanal_control_f() {//*********************контроль каналов на завышение, занижение, замирание уровня и появление сигнала
int zn=0;
  for (int i = 0; i < 32; i = i+1) {
//-----
   kanal_sr[i]=kanal_sr[i].substring(1)+char(kanal[i]+100);                     // псевдопик
   kanal_max[i]=-51;
     for (int z = 0; z < kanal_sr[i].length()-1; z = z+1) {                     // поиск максимального значения-------------------------------переделать на плавный спад без строки накопления
       zn=int(kanal_sr[i].charAt(z)-100);
       if (kanal_max[i]<zn) {kanal_max[i]=zn;}
     }
if (signal_key[i]==true) {                                                           // сигнализация при наличии сигнала

   
//-----   
   if (kanal_play[i]==true) {                                                                               //занижения уровня
    if (kanal_max[i]>=kanal_signal) {kanal_min[i]=millis(); kanal_minimum[i]=false;}
    else if (static_kanal(kanal_min[i], millis())>pauza) {kanal_minimum[i]=true;}
//-----                                                                                                     //замирание                                                                            
   if (kanal_prev[i]!=kanal[i]) {kanal_prev[i]=kanal[i]; kanal_stat[i]=millis(); kanal_static[i]=false;}    //значения изменились - начать отсчёт времени от сих и и сравнивать будущие значения с текущим
   else if (static_kanal(kanal_stat[i], millis())>zamiranie) {kanal_static[i]=true;}                        //замирание более порогового 
//----- 
   if (kanal[i]>maxdb) {kanal_maximum[i]=true;} else {kanal_maximum[i]=false;}                              //завышение уровня (мгновенное)
 } else {kanal_minimum[i]=false; kanal_static[i]=false;}
//----- 
} else {if (kanal[i]>=kanal_signal) {kanal_minimum[i]=true;} else {kanal_minimum[i]=false;}}                //появление ожидаемого сигнала выше порогового
   

//----- 
   }// цикл i
} //************************************kanal_control_f
int static_kanal(int time1, int time2) { //*********получение значений замирания канала в секундах [время начала] [время окончания(текущее)] в миллисекундах
 time1=int((time2-time1)/1000);
 
return time1;  
} //************************************static_kanal


void mousePressed() { // ***************клик мышью
int i_local=int(mouseY/scaleY/6);                                                   //координаты клика по высоте преобразовать в номер канала       
   if ((mouseButton==LEFT) && (mouseX/scaleX < 50 )) {                              //левая часть экрана
   kanal_play[i_local]= !kanal_play[i_local];                                       //смена состояния кнопок
       if ((kanal[i_local]<=kanal_signal) && (kanal_play[i_local]==true))  {signal_key[i_local]=false;} else {signal_key[i_local]=true;}            //ключь на реакцию по пропаданию сигнала
   } 
   else if ((mouseButton==LEFT)  && (mouseX/scaleX > 50 )) {                        //правая часть экрана (+16)
   kanal_play[i_local+16]= !kanal_play[i_local+16];
       if ((kanal[i_local+16]<=kanal_signal) && (kanal_play[i_local+16]==true))  {signal_key[i_local+16]=false;} else {signal_key[i_local+16]=true;}   //ключь на реакцию по появлению сигнала
   }
   
    String words2="";                                                                        
    for (int i = 0; i < 32; i = i+1) { words2 += str(kanal_play[i])+" ";}
    String[] list2 = split(words2, ' ');
    saveStrings("signal.ini", list2);
} //************************************mousePressed()

void signal() { //**********************события по выходу уровня сигнала за рамки допустимого
  for (int i = 0; i < 32; i = i+1) {
    if ((kanal_play[i]==true) && (kanal_maximum[i]==true)) {                                                             //превышения уровня - пик
      log_output.print(time_now());
      log_output.print(kanal_name[i]);
      log_output.print(" = +");
      log_output.print(kanal[i]);
      log_output.println("dB");
      log_output.flush();
      alarm_mini.play();
    }
    
    if ((kanal_play[i]==true) && (kanal_minimum[i]==true) && (signal_key[i]==true)) {                                    //занижение уровня - псевдопик
      log_output.print(time_now());
      log_output.print(kanal_name[i]);
      log_output.print(" < ");
      log_output.print(str(kanal_signal));
      log_output.print("dB  ");
      log_output.print(static_kanal(kanal_min[i], millis()));
      log_output.print(" сек.");
      log_output.println("");
      log_output.flush();
      alarm_maxi.play();
      }
      
      if ((kanal_minimum[i]==true) && (signal_key[i]==false)) {                                                         //появление ожидаемого сигнала
      log_output.print(time_now());
      log_output.print(kanal_name[i]);
      log_output.print(" появился с уровнем ");
      log_output.print(str(kanal[i]));
      log_output.println("");
      log_output.flush();
      alarm_maxi.play();
      }
      
    if ((kanal_play[i]==true) && (kanal_static[i]==true) &&  (kanal_minimum[i]==false)) {
      log_output.print(time_now());
      log_output.print(kanal_name[i]);
      log_output.print(" - замирание канала на уровне ");
      log_output.print(str(kanal[i]));
      log_output.print("dB  ");
      log_output.print(static_kanal(kanal_stat[i], millis()));
      log_output.print(" сек.");
      log_output.println("");
      log_output.flush();
      alarm_maxi.play();
    }
  } // конец цикла i

} //************************************signal()
  
String time_now() { //******************строка текущего времени
String text=str(year())+"-"+str(month())+"-"+str(day())+" "+str(hour())+":"+str(minute())+":"+str(second())+" ";
return text;
} //************************************time_now()

public void stop(){  // ****************безопасная остановка
  Sonia.stop();
  myPort.stop();
  super.stop();
}

если кому то не лень, поглядите, может есть принципиальные ошибки.

работает так:

1. зацеплены две библиотеки, ком и звук

2. задать базовые установки, прочитать изменяемые установки из внешнего файла (при отдельных ошибках в файле сбросить его по умолчанию).

3. в основном цикле чтаем данных из кома, при сборе строки данных отправляем их на преобразование(порезать по два байта и записать в массив в очевидном десятичном виде), далее полученные данные отправляются на индикцию, сигнализацию (уровень ниже указанного, выше 0дБ, замирание сигнала на одном уровне дольше заданного)

 

ourlive
Offline
Зарегистрирован: 26.05.2012

все остальные функции дополнительные, так например все события пишутся в лог-файл длиной не более заданного, при превышении длины старые данные удаляются.

Визуально уровень сигнала отображается в виде прогрессбара, нормальный уровень - зелёный, превышение - красный. Как настоящий пиковый индикатор (только тут он псевдопиковый).

т.к. на живом устройстве отлаживать было неудобно в нану был закинут эмулятог гонящий схожие данные в ком

//имитация ИЗУСа
int z;
int x;
int x1=100;
int a1=1;
int x2=100;
int a2=2;
int zn[33];
void setup() {
  Serial.begin(9600);
  Serial.println("--start--");
  for (z=5; z<29; z++) {zn[z]=random(0,255);}
}

void loop() {
 
  
 Serial.print(char(13));
  if (x1>253) {a1=-1;}
  if (x1<17) {a1=1;}
  x1=x1+a1;
  Serial.print(x1, HEX);
  if (x2>253) {a2=-2;}
  if (x2<19) {a2=2;}
  x2=x2+a2;
  Serial.print(x2, HEX);
  
  Serial.print("5500");
  
 for (z=5; z<29; z++) {
   if (random(0, 2)==0) {zn[z]=zn[z]+4;} else {zn[z]=zn[z]-4;}
   if (zn[z]<5) {zn[z]=5;}
   if (zn[z]>250) {zn[z]=250;}
   if (zn[z]<16) {Serial.print(0);}
 Serial.print(zn[z], HEX);
 }
 Serial.print("00000000");
  delay(20);
}

 

ourlive
Offline
Зарегистрирован: 26.05.2012

полноценные кнопочки-пимпочки мне было рисовать лень, а цеплять внешние библиотеки вроде бы и не обязательно, потому всё оперативное управление приложением сводится к возможности кликнуть по соответствующему индикатору включив или выключив сигнализацию (координаты клика преобразуются в соответствующий номер канала).

Последним что добавил (и чего в оригинальной оболочке не ожидалось) была возможность сигнализации не пропадания сигнала, а его появления (без добавления лишних элементов управления).