1 /*-
2 * Copyright (c) 2016-2021 Hans Petter Selasky. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26 #include "qaudiosonar.h"
27
28 #include "qaudiosonar_buttonmap.h"
29
30 static pthread_mutex_t atomic_mtx;
31 static pthread_mutex_t atomic_graph;
32 static pthread_cond_t atomic_cv;
33
34 static const char *qas_key_map[12] = {
35 "C%1", "D%1B", "D%1", "E%1B", "E%1",
36 "F%1", "G%1B", "G%1", "A%1B",
37 "A%1", "B%1B", "B%1",
38 };
39
40 int qas_num_workers = 2;
41 size_t qas_window_size;
42 size_t qas_in_sequence_number;
43 size_t qas_out_sequence_number;
44 QasMainWindow *qas_mw;
45
46 static void
atomic_init(void)47 atomic_init(void)
48 {
49 pthread_mutex_init(&atomic_mtx, NULL);
50 pthread_mutex_init(&atomic_graph, NULL);
51 pthread_cond_init(&atomic_cv, NULL);
52 }
53
54 void
atomic_lock(void)55 atomic_lock(void)
56 {
57 pthread_mutex_lock(&atomic_mtx);
58 }
59
60 void
atomic_unlock(void)61 atomic_unlock(void)
62 {
63 pthread_mutex_unlock(&atomic_mtx);
64 }
65
66 void
atomic_graph_lock(void)67 atomic_graph_lock(void)
68 {
69 pthread_mutex_lock(&atomic_graph);
70 }
71
72 void
atomic_graph_unlock(void)73 atomic_graph_unlock(void)
74 {
75 pthread_mutex_unlock(&atomic_graph);
76 }
77
78 void
atomic_wait(void)79 atomic_wait(void)
80 {
81 pthread_cond_wait(&atomic_cv, &atomic_mtx);
82 }
83
84 void
atomic_wakeup(void)85 atomic_wakeup(void)
86 {
87 pthread_cond_broadcast(&atomic_cv);
88 }
89
90 static size_t
QasGetSequenceNumber(size_t * phi)91 QasGetSequenceNumber(size_t *phi)
92 {
93 size_t hd = qas_display_height();
94 size_t hi = hd / 2 + 1;
95
96 *phi = hi;
97
98 atomic_lock();
99 size_t seq = qas_out_sequence_number + hd - hi;
100 atomic_unlock();
101
102 return (seq);
103 }
104
QasBandPassBox()105 QasBandPassBox :: QasBandPassBox()
106 {
107 setTitle(QString("Band pass center frequency: %1 Hz").arg(qas_sample_rate / 4));
108
109 grid = new QGridLayout(this);
110
111 pSB = new QScrollBar(Qt::Horizontal);
112
113 pSB->setRange(1, qas_sample_rate / 2);
114 pSB->setSingleStep(1);
115 pSB->setValue(qas_sample_rate / 4);
116 connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(handle_value_changed(int)));
117
118 grid->addWidget(pSB, 0,0,1,1);
119 }
120
121 void
handle_value_changed(int value)122 QasBandPassBox :: handle_value_changed(int value)
123 {
124 setTitle(QString("Band pass center frequency: %1 Hz").arg(value));
125 valueChanged(value);
126 }
127
QasBandWidthBox()128 QasBandWidthBox :: QasBandWidthBox()
129 {
130 setTitle("Band width: 20 Hz");
131
132 grid = new QGridLayout(this);
133
134 pSB = new QScrollBar(Qt::Horizontal);
135
136 pSB->setRange(1, qas_sample_rate);
137 pSB->setSingleStep(1);
138 pSB->setValue(20);
139 connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(handle_value_changed(int)));
140
141 grid->addWidget(pSB, 0,0,1,1);
142 }
143
144 void
handle_value_changed(int value)145 QasBandWidthBox :: handle_value_changed(int value)
146 {
147 setTitle(QString("Band width: %1 Hz").arg(value));
148 valueChanged(value);
149 }
150
QasNoiselevelBox()151 QasNoiselevelBox :: QasNoiselevelBox()
152 {
153 setTitle(QString("Noise level: %1").arg(-64));
154
155 grid = new QGridLayout(this);
156
157 pSB = new QScrollBar(Qt::Horizontal);
158
159 pSB->setRange(-256, 256);
160 pSB->setSingleStep(1);
161 pSB->setValue(-64);
162 connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(handle_value_changed(int)));
163
164 grid->addWidget(pSB, 0,0,1,1);
165 }
166
167 void
handle_value_changed(int value)168 QasNoiselevelBox :: handle_value_changed(int value)
169 {
170 setTitle(QString("Noise level: %1").arg(value));
171 valueChanged(value);
172 }
173
QasConfig(QasMainWindow * _mw)174 QasConfig :: QasConfig(QasMainWindow *_mw)
175 {
176 mw = _mw;
177
178 gl = new QGridLayout(this);
179
180 map_source_0 = new QasButtonMap("Main input channel\0"
181 "INPUT 0\0" "INPUT 1\0" "INPUT 0+1\0"
182 "OUTPUT 0\0" "OUTPUT 1\0" "OUTPUT 0+1\0", 6, 3);
183 map_source_1 = new QasButtonMap("Correlation input channel\0"
184 "INPUT 0\0" "INPUT 1\0" "INPUT 0+1\0"
185 "OUTPUT 0\0" "OUTPUT 1\0" "OUTPUT 0+1\0", 6, 3);
186 map_output_0 = new QasButtonMap("Output for channel 0\0"
187 "SILENCE\0" "BROWN NOISE\0" "WHITE NOISE\0"
188 "BROWN NOISE BP\0" "WHITE NOISE BP\0", 5, 3);
189 map_output_1 = new QasButtonMap("Output for channel 1\0"
190 "SILENCE\0" "BROWN NOISE\0" "WHITE NOISE\0"
191 "BROWN NOISE BP\0" "WHITE NOISE BP\0", 5, 3);
192
193 bp_box_0 = new QasBandPassBox();
194 bw_box_0 = new QasBandWidthBox();
195 nl_box_0 = new QasNoiselevelBox();
196 handle_filter_0(0);
197
198 connect(map_source_0, SIGNAL(selectionChanged(int)), this, SLOT(handle_source_0(int)));
199 connect(map_source_1, SIGNAL(selectionChanged(int)), this, SLOT(handle_source_1(int)));
200 connect(map_output_0, SIGNAL(selectionChanged(int)), this, SLOT(handle_output_0(int)));
201 connect(map_output_1, SIGNAL(selectionChanged(int)), this, SLOT(handle_output_1(int)));
202 connect(bp_box_0, SIGNAL(valueChanged(int)), this, SLOT(handle_filter_0(int)));
203 connect(bw_box_0, SIGNAL(valueChanged(int)), this, SLOT(handle_filter_0(int)));
204 connect(nl_box_0, SIGNAL(valueChanged(int)), this, SLOT(handle_filter_0(int)));
205
206 bp_close = new QPushButton(tr("Close"));
207 connect(bp_close, SIGNAL(released()), this, SLOT(handle_close()));
208
209 gl->addWidget(map_source_0, 0,0,1,1);
210 gl->addWidget(map_source_1, 1,0,1,1);
211 gl->addWidget(map_output_0, 2,0,1,1);
212 gl->addWidget(map_output_1, 3,0,1,1);
213 gl->addWidget(bp_box_0, 0,1,1,1);
214 gl->addWidget(bw_box_0, 1,1,1,1);
215 gl->addWidget(nl_box_0, 2,1,1,1);
216 gl->addWidget(bp_close, 4,0,1,2);
217
218 setWindowTitle(tr(QAS_WINDOW_TITLE));
219 setWindowIcon(QIcon(QAS_WINDOW_ICON));
220 }
221
222 void
handle_source_0(int _value)223 QasConfig :: handle_source_0(int _value)
224 {
225 atomic_lock();
226 qas_source_0 = _value;
227 atomic_unlock();
228 }
229
230 void
handle_source_1(int _value)231 QasConfig :: handle_source_1(int _value)
232 {
233 atomic_lock();
234 qas_source_1 = _value;
235 atomic_unlock();
236 }
237
238 void
handle_output_0(int _value)239 QasConfig :: handle_output_0(int _value)
240 {
241 atomic_lock();
242 qas_output_0 = _value;
243 atomic_unlock();
244 }
245
246 void
handle_close(void)247 QasConfig :: handle_close(void)
248 {
249 hide();
250 }
251
QasView(QasMainWindow * _mw)252 QasView :: QasView(QasMainWindow *_mw)
253 {
254 mw = _mw;
255
256 gl = new QGridLayout(this);
257
258 map_decay_0 = new QasButtonMap("Decay selection\0"
259 "OFF\0" "1/8\0" "1/16\0" "1/32\0"
260 "1/64\0" "1/128\0" "1/256\0", 7, 4);
261
262 connect(map_decay_0, SIGNAL(selectionChanged(int)), this, SLOT(handle_decay_0(int)));
263
264 bp_close = new QPushButton(tr("Close"));
265 connect(bp_close, SIGNAL(released()), this, SLOT(handle_close()));
266
267 gl->addWidget(map_decay_0, 0,0,1,1);
268 gl->addWidget(bp_close, 1,0,1,1);
269
270 setWindowTitle(tr(QAS_WINDOW_TITLE));
271 setWindowIcon(QIcon(QAS_WINDOW_ICON));
272 }
273
274 void
handle_decay_0(int _value)275 QasView :: handle_decay_0(int _value)
276 {
277 atomic_graph_lock();
278 if (_value == 0)
279 qas_view_decay = 0.0;
280 else
281 qas_view_decay = 1.0 - 1.0 / pow(2.0, _value + 2);
282 atomic_graph_unlock();
283 }
284
285 void
handle_close(void)286 QasView :: handle_close(void)
287 {
288 hide();
289 }
290
291 static void
qas_low_pass(double freq,double * factor,size_t window_size)292 qas_low_pass(double freq, double *factor, size_t window_size)
293 {
294 int wh = window_size / 2;
295 int x;
296
297 freq /= (double)qas_sample_rate;
298 freq *= (double)wh;
299
300 factor[wh] += (2.0 * freq) / ((double)wh);
301 freq *= (2.0 * M_PI) / ((double)wh);
302
303 for (x = -wh+1; x < wh; x++) {
304 if (x == 0)
305 continue;
306 factor[x + wh] +=
307 sin(freq * (double)(x)) / (M_PI * (double)(x));
308 }
309 }
310
311 static void
qas_band_pass(double freq_low,double freq_high,double * factor,size_t window_size)312 qas_band_pass(double freq_low, double freq_high,
313 double *factor, size_t window_size)
314 {
315 double low = qas_sample_rate / window_size;
316 double high = qas_sample_rate / 2;
317
318 if (low < 1.0)
319 low = 1.0;
320 /* lowpass */
321 if (freq_low < low)
322 freq_low = low;
323 qas_low_pass(freq_low, factor, window_size);
324
325 /* highpass */
326 if (freq_high >= high)
327 freq_high = high;
328 qas_low_pass(-freq_high, factor, window_size);
329 }
330
331 void
handle_filter_0(int value)332 QasConfig :: handle_filter_0(int value)
333 {
334 double temp[QAS_CORR_SIZE];
335 double adjust = bw_box_0->pSB->value() / 2.0;
336 double center = bp_box_0->pSB->value();
337 int noiseLevel = nl_box_0->pSB->value();
338
339 memset(temp, 0, sizeof(temp));
340 qas_band_pass(center - adjust, center + adjust, temp, QAS_CORR_SIZE);
341
342 atomic_lock();
343 for (size_t x = 0; x != QAS_CORR_SIZE; x++)
344 qas_band_pass_filter[x] = temp[x];
345 qas_noise_level = pow(2.0, noiseLevel / 16.0);
346 atomic_unlock();
347 }
348
349 void
handle_output_1(int _value)350 QasConfig :: handle_output_1(int _value)
351 {
352 atomic_lock();
353 qas_output_1 = _value;
354 atomic_unlock();
355 }
356
QasBand(QasMainWindow * _mw)357 QasBand :: QasBand(QasMainWindow *_mw)
358 {
359 mw = _mw;
360 watchdog = new QTimer(this);
361 connect(watchdog, SIGNAL(timeout()), this, SLOT(handle_watchdog()));
362
363 setMinimumSize(192, 1);
364 setMouseTracking(1);
365 watchdog->start(500);
366 }
367
QasGraph(QasMainWindow * _mw)368 QasGraph :: QasGraph(QasMainWindow *_mw)
369 {
370 mw = _mw;
371 watchdog = new QTimer(this);
372 connect(watchdog, SIGNAL(timeout()), this, SLOT(handle_watchdog()));
373 mon_index = new double [qas_window_size];
374 memset(mon_index, 0, sizeof(double) * qas_window_size);
375
376 setMinimumSize(256, 256);
377 setMouseTracking(1);
378 watchdog->start(500);
379 }
380
~QasGraph()381 QasGraph :: ~QasGraph()
382 {
383 delete mon_index;
384 }
385
386 QString
getText(QMouseEvent * event)387 QasBand :: getText(QMouseEvent *event)
388 {
389 int key = (9 + (12 * event->x()) / width()) % 12;
390
391 return (QString(qas_key_map[key]).arg(5));
392 }
393
394 QString
getFullText(int ypos)395 QasBand :: getFullText(int ypos)
396 {
397 size_t wi = qas_display_band_width() / 3;
398 size_t hi;
399 size_t seq = QasGetSequenceNumber(&hi);
400 int ho = (hi * ypos) / height();
401 QString str;
402 ssize_t real_offset;
403 ssize_t real_band;
404 size_t y;
405 double level = 1U << qas_sensitivity;
406
407 if (ho < 0)
408 ho = 0;
409 else if (ho >= (int)hi)
410 ho = hi - 1;
411
412 double *band = qas_display_get_band(hi - 1 - ho + seq);
413
414 for (size_t x = y = 0; x != wi; x++) {
415 if (band[3 * x] > band[3 * y])
416 y = x;
417
418 if (band[3 * x] >= level) {
419 size_t offset = band[3 * x + 2];
420 size_t key = 9 + (offset + (QAS_WAVE_STEP / 2)) / QAS_WAVE_STEP;
421
422 str += QString(qas_key_map[key % 12]).arg(key / 12);
423 str += " ";
424 }
425 }
426 if (qas_record != 0) {
427 for (size_t x = 0; x != wi; x++) {
428 if (band[3 * x] == 0.0)
429 continue;
430 if (band[3 * x] >= level) {
431 size_t offset = band[3 * x + 2];
432 size_t key = 9 + (offset + (QAS_WAVE_STEP / 2)) / QAS_WAVE_STEP;
433 qas_midi_key_send(0, key, 90, 0);
434 }
435 }
436 qas_midi_delay_send(50);
437
438 for (size_t x = 0; x != wi; x++) {
439 if (band[3 * x] == 0.0)
440 continue;
441 if (band[3 * x] >= level) {
442 size_t offset = band[3 * x + 2];
443 size_t key = 9 + (offset + (QAS_WAVE_STEP / 2)) / QAS_WAVE_STEP;
444 qas_midi_key_send(0, key, 0, 0);
445 }
446 }
447 qas_midi_delay_send(50);
448 }
449
450 /* get band */
451 real_band = band[3 * y + 2];
452
453 /* compute offset */
454 real_offset = (real_band + (QAS_WAVE_STEP / 2));
455 real_offset -= real_offset % QAS_WAVE_STEP;
456 real_offset = (real_band - real_offset);
457 real_offset %= QAS_WAVE_STEP;
458
459 size_t key = 9 + (real_band + (QAS_WAVE_STEP / 2)) / QAS_WAVE_STEP;
460
461 str += "/* ";
462 str += QString(qas_key_map[key % 12]).arg(key / 12);
463 str += QString(" %2Hz R=%3 */")
464 .arg(QAS_FREQ_TABLE_ROUNDED(real_band))
465 .arg((double)real_offset / (double)QAS_WAVE_STEP, 0, 'f', 3);
466
467 return (str);
468 }
469
470 void
mousePressEvent(QMouseEvent * event)471 QasBand :: mousePressEvent(QMouseEvent *event)
472 {
473 if (event->button() == Qt::RightButton) {
474 mw->edit->appendPlainText("");
475 } else if (event->button() == Qt::MiddleButton) {
476 mw->edit->appendPlainText(getText(event));
477 } else if (event->button() == Qt::LeftButton) {
478 mw->edit->appendPlainText(getFullText(event->y()));
479 }
480 event->accept();
481 }
482
483 void
mouseMoveEvent(QMouseEvent * event)484 QasBand :: mouseMoveEvent(QMouseEvent *event)
485 {
486 setToolTip(getText(event));
487 }
488
489 void
paintEvent(QPaintEvent * event)490 QasBand :: paintEvent(QPaintEvent *event)
491 {
492 enum { LIMIT = 8 };
493
494 int w = width();
495 int h = height();
496 size_t hi;
497 ssize_t real_band = 0;
498 ssize_t real_offset;
499 double level = 1U << qas_sensitivity;
500
501 if (w == 0 || h == 0)
502 return;
503
504 size_t seq = QasGetSequenceNumber(&hi);
505
506 QPainter paint(this);
507 QColor white(255,255,255);
508
509 QImage accu(BAND_MAX, hi, QImage::Format_ARGB32);
510
511 accu.fill(white);
512
513 atomic_graph_lock();
514
515 for (size_t y = 0; y != hi; y++) {
516 double *band = qas_display_get_band(y + seq);
517 double max;
518 size_t x, z;
519
520 for (z = x = 0; x != BAND_MAX; x++) {
521 if (band[3 * x] > band[3 * z])
522 z = x;
523 }
524
525 real_band = band[3 * z + 2];
526 max = band[3 * z];
527
528 if (max < 1.0)
529 continue;
530
531 for (size_t x = 0; x != BAND_MAX; x++) {
532 double value = band[3 * x];
533
534 if (value < level)
535 continue;
536
537 int bright = (value / max) * 255.0;
538 if (bright > 255)
539 bright = 255;
540 else if (bright < 0)
541 bright = 0;
542
543 QColor hc(255 - bright, 255 - bright, 255 - bright, 255);
544 size_t yo = hi - 1 - y;
545 accu.setPixelColor(x, yo, hc);
546 }
547 }
548 atomic_graph_unlock();
549
550 if (qas_record != 0 && qas_freeze == 0) {
551 QString str = getFullText(0);
552 mw->handle_append_text(str);
553 }
554
555 paint.drawImage(QRect(0,0,w,h),accu);
556
557 /* compute offset */
558 real_offset = (real_band + (QAS_WAVE_STEP / 2));
559 real_offset -= real_offset % QAS_WAVE_STEP;
560 real_offset = (real_band - real_offset);
561 real_offset %= QAS_WAVE_STEP;
562
563 size_t key = 9 + (real_band + (QAS_WAVE_STEP / 2)) / QAS_WAVE_STEP;
564
565 QString str = QString(qas_key_map[key % 12]).arg(key / 12) +
566 QString(" - %1Hz\nR=%2")
567 .arg(QAS_FREQ_TABLE_ROUNDED(real_band))
568 .arg((double)real_offset / (double)QAS_WAVE_STEP, 0, 'f', 3);
569 mw->lbl_max->setText(str);
570 }
571
572 QString
getText(QMouseEvent * event)573 QasGraph :: getText(QMouseEvent *event)
574 {
575 int graph = (3 * event->y()) / height();
576 int band;
577 int key;
578
579 switch (graph) {
580 case 0:
581 case 1:
582 band = (qas_num_bands * event->x()) / width();
583 if (band > -1 && band < (int)qas_num_bands) {
584 band -= band % QAS_WAVE_STEP;
585 return QString(qas_descr_table[band]) +
586 QString(" /* %1Hz */").arg(QAS_FREQ_TABLE_ROUNDED(band));
587 } else {
588 return QString();
589 }
590 default:
591 for (key = 0; key != (int)qas_window_size; key++) {
592 int pos = mon_index[key] /
593 mon_index[qas_window_size - 1] * (double)width();
594 if (pos >= event->x())
595 break;
596 }
597 key = qas_window_size - 1 - key;
598 return (QString("/* %1ms %2m */")
599 .arg((double)((int)((key * 100000ULL) / qas_sample_rate) / 100.0))
600 .arg((double)((int)((key * 34000ULL) / qas_sample_rate) / 100.0)));
601 }
602 }
603
604 void
mousePressEvent(QMouseEvent * event)605 QasGraph :: mousePressEvent(QMouseEvent *event)
606 {
607 if (event->button() == Qt::RightButton) {
608 mw->edit->appendPlainText("");
609 } else if (event->button() == Qt::LeftButton) {
610 mw->edit->appendPlainText(getText(event));
611 }
612 event->accept();
613 }
614
615 void
mouseMoveEvent(QMouseEvent * event)616 QasGraph :: mouseMoveEvent(QMouseEvent *event)
617 {
618 setToolTip(getText(event));
619 }
620
621 static double
QasReference(double a,double b,double c)622 QasReference(double a, double b, double c)
623 {
624 double max;
625
626 if (a >= b && a >= c)
627 max = a;
628 else if (b >= a && b >= c)
629 max = b;
630 else
631 max = c;
632
633 return (max);
634 }
635
636 void
paintEvent(QPaintEvent * event)637 QasGraph :: paintEvent(QPaintEvent *event)
638 {
639 QPainter paint(this);
640 int w = width();
641 int h = height();
642 size_t wi = qas_display_width() / 3;
643 size_t hi;
644 size_t hg = h / 6 + 1;
645 size_t rg = h / 3 + 1;
646 size_t wc = qas_window_size;
647 double corr_max_power;
648 size_t corr_max_off;
649 size_t zoom = mw->sb_zoom->value();
650 QString corr_sign;
651
652 if (w == 0 || h == 0)
653 return;
654
655 size_t seq = QasGetSequenceNumber(&hi);
656
657 QImage hist(wi, hi, QImage::Format_ARGB32);
658 QImage power(wi, hg, QImage::Format_ARGB32);
659 QImage corr(w, hg, QImage::Format_ARGB32);
660
661 QColor transparent = QColor(0,0,0,0);
662 QColor corr_c = QColor(255,0,0,255);
663 QColor white(255,255,255);
664 QColor black(0,0,0);
665
666 hist.fill(white);
667 power.fill(transparent);
668 corr.fill(transparent);
669
670 double mon_sum = 0.0;
671 for (size_t x = 0; x != wc; x++) {
672 size_t delta = (x > zoom) ? x - zoom : zoom - x;
673 mon_index[x] = mon_sum;
674 mon_sum += pow(wc,4) - pow(delta,4);
675 }
676 atomic_graph_lock();
677 do {
678 size_t x, t;
679 for (t = x = 0; x != wc; x++) {
680 if (fabs(qas_mon_decay[x]) > fabs(qas_mon_decay[t]))
681 t = x;
682 }
683 corr_sign = (qas_mon_decay[t] < 0) ? "(-)" : "(+)";
684 corr_max_power = fabs(qas_mon_decay[t]);
685 if (corr_max_power < 1.0)
686 corr_max_power = 1.0;
687 corr_max_off = wc - 1 - t;
688 for (t = 0; t != wc; t++) {
689 x = mon_index[t] / mon_index[wc - 1] * (double)(w - 1);
690 if (x > (size_t)(w - 1))
691 x = (size_t)(w - 1);
692 int value = (1.0 - (qas_mon_decay[t] / corr_max_power)) * (double)(hg / 2);
693
694 if (value < 0)
695 value = 0;
696 else if (value > (int)(hg - 1))
697 value = (int)(hg - 1);
698
699 while (value < (int)(hg / 2)) {
700 if (corr.pixelColor(x, value).red() < corr_c.red())
701 corr.setPixelColor(x, value, corr_c);
702 value++;
703 }
704 while (value > (int)(hg / 2)) {
705 if (corr.pixelColor(x, value).red() < corr_c.red())
706 corr.setPixelColor(x, value, corr_c);
707 value--;
708 }
709 if (corr.pixelColor(x, value).red() < corr_c.red())
710 corr.setPixelColor(x, value, corr_c);
711 }
712 } while (0);
713
714 for (size_t y = 0; y != hi; y++) {
715 double *data = qas_display_get_line(y + seq);
716 double max;
717 size_t x, z;
718
719 for (z = x = 0; x != wi; x++) {
720 if (data[3 * x] > data[3 * z])
721 z = x;
722 }
723
724 max = data[3 * z];
725 if (max < 2.0) {
726 max = 2.0;
727 continue;
728 }
729
730 if (y == hi - 1) {
731 for (x = 0; x != wi; x++) {
732 double ref;
733
734 if (x == 0)
735 ref = QasReference(data[3 * x], data[3 * (x + 2)], data[3 * (x + 1)]);
736 else if (x == (wi - 1))
737 ref = QasReference(data[3 * x], data[3 * (x - 2)], data[3 * (x - 1)]);
738 else
739 ref = QasReference(data[3 * x], data[3 * (x + 1)], data[3 * (x - 1)]);
740
741 QColor power_c = (data[3 * x] >= ref) ?
742 QColor(0,0,0,255) : QColor(127,127,127,255);
743
744 double power_new = data[3 * x];
745
746 int power_y = (double)(power_new / max) * (hg - 1);
747 if (power_y < 0)
748 power_y = 0;
749 else if (power_y > (int)(hg - 1))
750 power_y = (int)(hg - 1);
751 for (int n = 0; n != power_y; n++)
752 power.setPixelColor(x, hg - 1 - n, power_c);
753 }
754 }
755 for (size_t x = 0; x != wi; x++) {
756 double value = data[3 * x];
757 if (value < 1.0)
758 continue;
759 int level = pow(value / max, 3.0) * 255.0;
760 if (level > 255)
761 level = 255;
762 else if (level < 0)
763 level = 0;
764 QColor hc(255 - level, 255 - level, 255 - level, 255);
765 if (hist.pixelColor(x, hi - 1 - y).red() > hc.red())
766 hist.setPixelColor(x, hi - 1 - y, hc);
767 }
768 }
769 atomic_graph_unlock();
770
771 paint.setPen(QPen(white,0));
772 paint.setBrush(white);
773
774 paint.drawRect(QRectF(0,0,w,h));
775 paint.setRenderHints(QPainter::Antialiasing|
776 QPainter::TextAntialiasing);
777
778 paint.drawImage(QRect(0,rg,w,rg), hist);
779 paint.drawImage(QRect(0,2*rg,w,rg), corr);
780 paint.drawImage(QRect(0,0,w,rg), power);
781
782 /* fill any gaps */
783 paint.setPen(QPen(black,0));
784 paint.setBrush(black);
785 paint.drawRect(QRectF(0,rg-2,w,4));
786
787 QFont fnt(paint.font());
788
789 fnt.setPixelSize(16);
790 paint.setFont(fnt);
791
792 QString str;
793 str = QString("MAX=%1dB@%2samples;%3ms;%4m LAG=%5F")
794 .arg(10.0 * log(corr_max_power) / log(10)).arg(corr_max_off)
795 .arg((double)((int)((corr_max_off * 100000ULL) / qas_sample_rate) / 100.0))
796 .arg((double)((int)((corr_max_off * 34000ULL) / qas_sample_rate) / 100.0))
797 .arg(qas_display_lag());
798
799 paint.setPen(QPen(black,0));
800 paint.setBrush(black);
801 paint.drawText(QPoint(0,16),str);
802
803 str = "POWER";
804 paint.setPen(QPen(black,0));
805 paint.setBrush(black);
806 paint.drawText(QPoint(0,32),str);
807
808 str = "CORRELATION ";
809 str += corr_sign;
810 paint.setPen(QPen(corr_c,0));
811 paint.setBrush(corr_c);
812 paint.drawText(QPoint(12*16,32),str);
813
814 uint8_t iso_num = 255;
815 int last_x = 0;
816 int diff_y = 0;
817 for (size_t x = 0; x != wi; x++) {
818 if (iso_num == qas_iso_table[x])
819 continue;
820 iso_num = qas_iso_table[x];
821 double freq = qas_iso_freq_table[iso_num];
822 if (freq >= 1000.0)
823 str = QString("%1.%2kHz").arg((int)(freq / 1000.0)).arg((int)(freq / 100.0) % 10);
824 else
825 str = QString("%1Hz").arg((int)freq);
826
827 paint.setPen(QPen(black,0));
828 paint.setBrush(black);
829 int next_x = (w * x) / wi;
830 if ((next_x - last_x) < (5 * 16))
831 diff_y += 20;
832 else
833 diff_y = 0;
834 paint.drawText(QPoint(next_x, h - 20 - diff_y),str);
835 if (diff_y == 0)
836 last_x = next_x;
837 }
838 }
839
840 void
handle_watchdog()841 QasBand :: handle_watchdog()
842 {
843 update();
844 }
845
846 void
handle_watchdog()847 QasGraph :: handle_watchdog()
848 {
849 update();
850 }
851
QasMainWindow()852 QasMainWindow :: QasMainWindow()
853 {
854 QPushButton *pb;
855
856 pa_max_index = Pa_GetDeviceCount();
857
858 qc = new QasConfig(this);
859 qv = new QasView(this);
860
861 gl = new QGridLayout(this);
862
863 qg = new QasGraph(this);
864 qb = new QasBand(this);
865
866 lbl_max = new QLabel();
867 lbl_max->setAlignment(Qt::AlignCenter);
868
869 QFont fnt(lbl_max->font());
870 fnt.setPixelSize(16);
871 lbl_max->setFont(fnt);
872
873 sb_zoom = new QScrollBar(Qt::Horizontal);
874 sb_zoom->setRange(0, qas_window_size - 1);
875 sb_zoom->setSingleStep(1);
876 sb_zoom->setValue(0);
877
878 but_dsp_rx = new QPushButton(tr("DSP RX"));
879 connect(but_dsp_rx, SIGNAL(released()), this, SLOT(handle_dsp_rx()));
880 gl->addWidget(but_dsp_rx, 0,0,1,1);
881
882 led_dsp_read = new QLineEdit(paName(Pa_GetDefaultInputDevice()));
883 gl->addWidget(led_dsp_read, 0,1,1,1);
884
885 but_dsp_tx = new QPushButton(tr("DSP TX"));
886 connect(but_dsp_tx, SIGNAL(released()), this, SLOT(handle_dsp_tx()));
887 gl->addWidget(but_dsp_tx, 1,0,1,1);
888
889 led_dsp_write = new QLineEdit(paName(Pa_GetDefaultOutputDevice()));
890 gl->addWidget(led_dsp_write, 1,1,1,1);
891
892 but_midi_tx = new QPushButton(tr("MIDI TX"));
893 gl->addWidget(but_midi_tx, 2,0,1,1);
894
895 led_midi_write = new QLineEdit("/dev/midi");
896 gl->addWidget(led_midi_write, 2,1,1,1);
897
898 pb = new QPushButton(tr("Apply"));
899 connect(pb, SIGNAL(released()), this, SLOT(handle_apply()));
900 gl->addWidget(pb, 0,2,1,1);
901
902 pb = new QPushButton(tr("Reset"));
903 connect(pb, SIGNAL(released()), this, SLOT(handle_reset()));
904 gl->addWidget(pb, 1,2,1,1);
905
906 pb = new QPushButton(tr("AudioConfig"));
907 connect(pb, SIGNAL(released()), this, SLOT(handle_config()));
908 gl->addWidget(pb, 0,7,1,1);
909
910 pb = new QPushButton(tr("ViewConfig"));
911 connect(pb, SIGNAL(released()), this, SLOT(handle_view()));
912 gl->addWidget(pb, 1,7,1,1);
913
914 pb = new QPushButton(tr("Toggle\nFreeze"));
915 connect(pb, SIGNAL(released()), this, SLOT(handle_tog_freeze()));
916 gl->addWidget(pb, 0,6,2,1);
917
918 pb = new QPushButton(tr("RecordTog"));
919 connect(pb, SIGNAL(released()), this, SLOT(handle_tog_record()));
920 gl->addWidget(pb, 2,2,1,1);
921
922 tuning = new QSpinBox();
923 tuning->setRange(-999,999);
924 tuning->setValue(0);
925 tuning->setPrefix("Fine tuning +/-999: ");
926 connect(tuning, SIGNAL(valueChanged(int)), this, SLOT(handle_tuning()));
927
928 sensitivity = new QSlider();
929 sensitivity->setRange(0, 31);
930 sensitivity->setValue(0);
931 sensitivity->setToolTip("Sensitivity");
932 sensitivity->setOrientation(Qt::Horizontal);
933 connect(sensitivity, SIGNAL(valueChanged(int)), this, SLOT(handle_sensitivity()));
934
935 edit = new QPlainTextEdit();
936
937 qbw = new QWidget();
938 glb = new QGridLayout(qbw);
939
940 gl->addWidget(qbw, 0,8,6,2);
941 gl->addWidget(sb_zoom, 3,0,1,8);
942 gl->addWidget(qg, 4,0,2,8);
943 gl->setRowStretch(2,1);
944
945 glb->addWidget(lbl_max, 1,0,1,1);
946 glb->addWidget(tuning, 2,0,1,1);
947 glb->addWidget(sensitivity, 3,0,1,1);
948 glb->addWidget(qb, 4,0,1,1);
949 glb->addWidget(edit, 5,0,1,1);
950 glb->setRowStretch(4,1);
951
952 connect(this, SIGNAL(handle_append_text(const QString)), edit, SLOT(appendPlainText(const QString &)));
953
954 setWindowTitle(tr(QAS_WINDOW_TITLE));
955 setWindowIcon(QIcon(QAS_WINDOW_ICON));
956 }
957
958 void
handle_apply()959 QasMainWindow :: handle_apply()
960 {
961 QString midi_wr = led_midi_write->text().trimmed();
962 QString dsp_rx = led_dsp_read->text().trimmed();
963 QString dsp_tx = led_dsp_write->text().trimmed();
964 PaDeviceIndex i;
965 int x;
966
967 atomic_lock();
968 qas_rx_device = paNoDevice;
969 for (i = 0; i != pa_max_index; i++) {
970 if (dsp_rx == paName(i)) {
971 qas_rx_device = i;
972 break;
973 }
974 }
975 qas_tx_device = paNoDevice;
976 for (i = 0; i != pa_max_index; i++) {
977 if (dsp_tx == paName(i)) {
978 qas_tx_device = i;
979 break;
980 }
981 }
982
983 for (x = 0; x != midi_wr.length() &&
984 x != sizeof(midi_write_device) - 1; x++) {
985 midi_write_device[x] = midi_wr[x].toLatin1();
986 }
987
988 midi_write_device[x] = 0;
989 atomic_wakeup();
990 atomic_unlock();
991 }
992
993 void
handle_dsp_rx()994 QasMainWindow :: handle_dsp_rx()
995 {
996 PaDeviceIndex i;
997
998 if (pa_max_index <= 0)
999 return;
1000 for (i = 0; i != pa_max_index; i++) {
1001 if (led_dsp_read->text() == paName(i)) {
1002 i++;
1003 break;
1004 }
1005 }
1006 i %= pa_max_index;
1007 led_dsp_read->setText(paName(i));
1008 }
1009
1010 void
handle_dsp_tx()1011 QasMainWindow :: handle_dsp_tx()
1012 {
1013 PaDeviceIndex i;
1014
1015 if (pa_max_index <= 0)
1016 return;
1017 for (i = 0; i != pa_max_index; i++) {
1018 if (led_dsp_write->text() == paName(i)) {
1019 i++;
1020 break;
1021 }
1022 }
1023 i %= pa_max_index;
1024 led_dsp_write->setText(paName(i));
1025 }
1026
1027 void
handle_reset()1028 QasMainWindow :: handle_reset()
1029 {
1030 qas_dsp_sync();
1031 }
1032
1033 void
handle_config()1034 QasMainWindow :: handle_config()
1035 {
1036 qc->show();
1037 }
1038
1039 void
handle_view()1040 QasMainWindow :: handle_view()
1041 {
1042 qv->show();
1043 }
1044
1045 void
handle_tuning()1046 QasMainWindow :: handle_tuning()
1047 {
1048 qas_tuning = pow(2.0, (double)tuning->value() / 12000.0);
1049 }
1050
1051 void
handle_sensitivity()1052 QasMainWindow :: handle_sensitivity()
1053 {
1054 atomic_lock();
1055 qas_sensitivity = sensitivity->value();
1056 atomic_unlock();
1057
1058 qb->update();
1059 }
1060
1061 void
handle_slider(int value)1062 QasMainWindow :: handle_slider(int value)
1063 {
1064 }
1065
1066 void
handle_tog_freeze()1067 QasMainWindow :: handle_tog_freeze()
1068 {
1069 atomic_lock();
1070 qas_freeze = !qas_freeze;
1071 atomic_unlock();
1072 }
1073
1074 void
handle_tog_record()1075 QasMainWindow :: handle_tog_record()
1076 {
1077 atomic_lock();
1078 qas_record = !qas_record;
1079 atomic_unlock();
1080 }
1081
1082 static void
usage(void)1083 usage(void)
1084 {
1085 fprintf(stderr, "Usage: qaudiosonar [-r <samplerate>] "
1086 "[-n <workers>] [-w <windowsize>]\n");
1087 exit(0);
1088 }
1089
1090 int
main(int argc,char ** argv)1091 main(int argc, char **argv)
1092 {
1093 QApplication app(argc, argv);
1094 int c;
1095
1096 /* must be first, before any threads are created */
1097 signal(SIGPIPE, SIG_IGN);
1098
1099 while ((c = getopt(argc, argv, "n:r:hw:")) != -1) {
1100 switch (c) {
1101 case 'n':
1102 qas_num_workers = atoi(optarg);
1103 if (qas_num_workers < 1)
1104 qas_num_workers = 1;
1105 else if (qas_num_workers > 16)
1106 qas_num_workers = 16;
1107 break;
1108 case 'r':
1109 qas_sample_rate = atoi(optarg);
1110 if (qas_sample_rate < 8000)
1111 qas_sample_rate = 8000;
1112 else if (qas_sample_rate > 96000)
1113 qas_sample_rate = 96000;
1114 break;
1115 case 'w':
1116 qas_window_size = atoi(optarg);
1117 break;
1118 default:
1119 usage();
1120 break;
1121 }
1122 }
1123
1124 atomic_init();
1125
1126 /* range check window size */
1127 if (qas_window_size == 0)
1128 qas_window_size = QAS_CORR_SIZE;
1129 else if (qas_window_size >= (size_t)(16 * qas_sample_rate))
1130 qas_window_size = (size_t)(16 * qas_sample_rate);
1131
1132 /* align window size */
1133 qas_window_size -= (qas_window_size % QAS_CORR_SIZE);
1134 if (qas_window_size == 0)
1135 errx(EX_USAGE, "Invalid window size\n");
1136
1137 if (Pa_Initialize() != paNoError)
1138 errx(EX_SOFTWARE, "Could not setup portaudio\n");
1139
1140 qas_wave_init();
1141 qas_corr_init();
1142 qas_display_init();
1143 qas_midi_init();
1144 qas_dsp_init();
1145
1146 qas_mw = new QasMainWindow();
1147 qas_mw->show();
1148
1149 return (app.exec());
1150 }
1151