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