1 /*
2 	Copyright (C) 2008, 2009 Andres Cabrera
3 	mantaraya36@gmail.com
4 
5 	This file is part of CsoundQt.
6 
7 	CsoundQt is free software; you can redistribute it
8 	and/or modify it under the terms of the GNU Lesser General Public
9 	License as published by the Free Software Foundation; either
10 	version 2.1 of the License, or (at your option) any later version.
11 
12 	CsoundQt is distributed in the hope that it will be useful,
13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 	GNU Lesser General Public License for more details.
16 
17 	You should have received a copy of the GNU Lesser General Public
18 	License along with Csound; if not, write to the Free Software
19 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 	02111-1307 USA
21 */
22 
23 #include "qutegraph.h"
24 #include "curve.h"
25 #include <cmath>
26 #include <QPalette>
27 
28 
29 #include <QColorDialog>
30 
31 
32 enum CsoundEngineStatus {
33     Running=0,
34     UserDataNotSet,
35     CsoundInstanceNotSet,
36     EngineNotSet,
37     NotRunning
38 };
39 
40 
csoundEngineStatus(CsoundUserData * ud)41 inline CsoundEngineStatus csoundEngineStatus(CsoundUserData *ud) {
42     if(ud == nullptr) {
43         return CsoundEngineStatus::UserDataNotSet;
44     }
45     if(ud->csound == nullptr) {
46         return CsoundEngineStatus::CsoundInstanceNotSet;
47     }
48     if(ud->csEngine == nullptr) {
49         return CsoundEngineStatus::EngineNotSet;
50     }
51     if(!ud->csEngine->isRunning()) {
52         return CsoundEngineStatus::NotRunning;
53     }
54     return CsoundEngineStatus::Running;
55 }
56 
57 
58 // -----------------------------------------------------------------------------------------
59 
QuteGraph(QWidget * parent)60 QuteGraph::QuteGraph(QWidget *parent) : QuteWidget(parent)
61 {
62 	m_widget = new StackedLayoutWidget(this);
63 	m_widget->show();
64 	//  m_widget->setAutoFillBackground(true);
65 	m_widget->setMouseTracking(true); // Necessary to pass mouse tracking to widget panel for _MouseX channels
66 	m_widget->setContextMenuPolicy(Qt::NoContextMenu);
67     m_numticksY = 6;
68 	m_label = new QLabel(this);
69 	QPalette palette = m_widget->palette();
70     palette.setColor(QPalette::WindowText, QColor(150, 150, 150));
71 	m_label->setPalette(palette);
72 	m_label->setText("");
73     m_label->setFont(QFont({"Helvetica", 7}));
74     m_label->move(120, -4);
75 	m_label->resize(500, 25);
76 	m_pageComboBox = new QComboBox(this);
77     m_pageComboBox->setMinimumWidth(120);
78     m_pageComboBox->setMaximumHeight(14);
79     m_pageComboBox->setFont(QFont({"Sans", 7}));
80     m_pageComboBox->setFocusPolicy(Qt::NoFocus);
81     m_pageComboBox->setStyleSheet("QComboBox QAbstractItemView"
82                                   "{ min-width: 150px; }");
83     // m_pageComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
84     m_pageComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
85 	m_label->setFocusPolicy(Qt::NoFocus);
86     m_drawGrid = true;
87     m_drawTableInfo = true;
88     m_showScrollbars = true;
89 	canFocus(false);
90 	connect(m_pageComboBox, SIGNAL(currentIndexChanged(int)),
91 			this, SLOT(indexChanged(int)));
92 	polygons.clear();
93 
94 	QPalette Pal(this->palette());
95     // set black background
96     Pal.setColor(QPalette::Background, Qt::black);
97     this->setAutoFillBackground(true);
98     this->setPalette(Pal);
99 
100 	// Default properties
101 	setProperty("QCS_zoomx", 1.0);
102 	setProperty("QCS_zoomy", 1.0);
103 	setProperty("QCS_dispx", 1.0);
104 	setProperty("QCS_dispy", 1.0);
105 	setProperty("QCS_modex", "auto");
106 	setProperty("QCS_modey", "auto");
107     setProperty("QCS_showSelector", true);
108     setProperty("QCS_showGrid", true);
109     setProperty("QCS_showTableInfo", true);
110     setProperty("QCS_showScrollbars", true);
111     setProperty("QCS_enableTables", true);
112     setProperty("QCS_enableDisplays", true);
113     m_enableTables = true;
114     m_enableDisplays = true;
115 	setProperty("QCS_all", true);
116 
117     m_showPeak = false;
118     m_showPeakCenterFrequency = 1000.0;
119     m_showPeakRelativeBandwidth = 0.25;
120     m_lastPeakFreq = 0;
121     m_frozen = false;
122     m_getPeakChannel = "";
123     m_peakChannelPtr = nullptr;
124 }
125 
~QuteGraph()126 QuteGraph::~QuteGraph()
127 {
128 }
129 
getWidgetLine()130 QString QuteGraph::getWidgetLine()
131 {
132 	// Extension to MacCsound: type of graph (table, ftt, scope), value (which hold the index of the
133 	// table displayed) zoom and channel name
134 	// channel number is unused in QuteGraph, but selects channel for scope
135 #ifdef  USE_WIDGET_MUTEX
136 	widgetLock.lockForRead();
137 #endif
138 	QString line = "ioGraph {" + QString::number(x()) + ", " + QString::number(y()) + "} ";
139 	line += "{"+ QString::number(width()) +", "+ QString::number(height()) +"} table ";
140 	line += QString::number(m_value, 'f', 6) + " ";
141 	line += QString::number(property("QCS_zoomx").toDouble(), 'f', 6) + " ";
142 	line += m_channel;
143 	//   qDebug("QuteGraph::getWidgetLine(): %s", line.toStdString().c_str());
144 #ifdef  USE_WIDGET_MUTEX
145 	widgetLock.unlock();
146 #endif
147 	return line;
148 }
149 
getWidgetXmlText()150 QString QuteGraph::getWidgetXmlText()
151 {
152 	// Graphs are not implemented in blue
153 	xmlText = "";
154 	QXmlStreamWriter s(&xmlText);
155 	createXmlWriter(s);
156 #ifdef  USE_WIDGET_MUTEX
157 	widgetLock.lockForRead();
158 #endif
159 
160 	s.writeTextElement("value", QString::number((int)m_value));
161 	s.writeTextElement("objectName2", m_channel2);
162 	s.writeTextElement("zoomx", QString::number(property("QCS_zoomx").toDouble(), 'f', 8));
163 	s.writeTextElement("zoomy", QString::number(property("QCS_zoomy").toDouble(), 'f', 8));
164 	s.writeTextElement("dispx", QString::number(property("QCS_dispx").toDouble(), 'f', 8));
165 	s.writeTextElement("dispy", QString::number(property("QCS_dispy").toDouble(), 'f', 8));
166 	s.writeTextElement("modex", property("QCS_modex").toString());
167 	s.writeTextElement("modey", property("QCS_modey").toString());
168     s.writeTextElement("showSelector",
169                        property("QCS_showSelector").toBool() ? "true" : "false");
170     s.writeTextElement("showGrid", property("QCS_showGrid").toBool() ? "true" : "false");
171     s.writeTextElement("showTableInfo",
172                        property("QCS_showTableInfo").toBool() ? "true" : "false");
173     s.writeTextElement("showScrollbars",
174                        property("QCS_showScrollbars").toBool() ? "true" : "false");
175     s.writeTextElement("enableTables",
176                        property("QCS_enableTables").toBool() ? "true" : "false");
177     s.writeTextElement("enableDisplays", m_enableDisplays ? "true" : "false");
178 	s.writeTextElement("all", property("QCS_all").toBool() ? "true" : "false");
179 	s.writeEndElement();
180 #ifdef  USE_WIDGET_MUTEX
181 	widgetLock.unlock();
182 #endif
183 	return xmlText;
184 }
185 
186 
getWidgetType()187 QString QuteGraph::getWidgetType() {
188     return QString("BSBGraph");
189 }
190 
setWidgetGeometry(int x,int y,int width,int height)191 void QuteGraph::setWidgetGeometry(int x,int y,int width,int height)
192 {
193 	QuteWidget::setWidgetGeometry(x,y,width, height);
194 	static_cast<StackedLayoutWidget *>(m_widget)->setWidgetGeometry(0,0,width, height);
195 	int index = static_cast<StackedLayoutWidget *>(m_widget)->currentIndex();
196     changeCurve(-2);
197 
198     if (index < 0)
199 		return;
200     // changeCurve(index);
201 }
202 
mousePressEvent(QMouseEvent * event)203 void QuteGraph::mousePressEvent(QMouseEvent *event) {
204     int index = (int)m_value;
205     switch(graphtypes[index]) {
206     case GraphType::GRAPH_SPECTRUM: {
207         if(event->button() & Qt::LeftButton) {
208             // turn on peak detection
209             m_showPeakTemp = true;
210             m_spectrumPeakMarkers[index]->setVisible(true);
211             m_spectrumPeakTexts[index]->setVisible(true);
212             m_mouseDragging = true;
213             auto view = getView(index);
214             auto peakIndex = view->mapToScene(event->x(), 0).x();
215             auto curve = curves[index];
216             auto freq = peakIndex / curve->get_size() * this->m_ud->sampleRate*0.5;
217             m_showPeakTempFrequency = freq;
218             return;
219         }
220         break;
221     }
222     default:
223         break;
224     }
225 }
226 
mouseReleased()227 void QuteGraph::mouseReleased() {
228     m_mouseDragging = false;
229     if(!m_showPeak ) {
230         m_lastPeakFreq = 0;
231         m_lastTextMarkerY = 0;
232         m_lastTextMarkerX = 0;
233     }
234     m_showPeakTemp = false;
235     int index = (int)m_value;
236     m_spectrumPeakTexts[index]->setVisible(m_showPeak);
237     auto scene = getView(index)->scene();
238     m_spectrumPeakMarkers[index]->setVisible(m_showPeak);
239 }
240 
241 /*
242 void QuteGraph::mouseMoveEvent(QMouseEvent *event) {
243     int index = (int)m_value;
244     switch(graphtypes[index]) {
245     case GraphType::GRAPH_SPECTRUM: {
246         if(!m_mouseDragging)
247             return;
248         auto peakIndex = getView(index)->mapToScene(event->x(), 0).x();
249         auto curveSize = curves[index]->get_size();
250         m_showPeakTempFrequency = peakIndex / curveSize * this->m_ud->sampleRate*0.5;
251         return;
252     }
253     default:
254         break;
255     }
256 }
257 */
258 
259 
keyPressEvent(QKeyEvent * event)260 void QuteGraph::keyPressEvent(QKeyEvent *event) {
261     bool flag;
262     int index = m_value;
263     auto graphtype = graphtypes[index];
264     switch(event->key()) {
265     case Qt::Key_F:
266         if(graphtype != GraphType::GRAPH_SPECTRUM)
267             return;
268         freezeSpectrum(!m_frozen);
269         event->accept();
270         break;
271     case Qt::Key_S:
272         flag = !property("QCS_showSelector").toBool();
273         setProperty("QCS_showSelector", flag?"true":"false");
274         if(flag)
275             m_pageComboBox->show();
276         else
277             m_pageComboBox->hide();
278         event->accept();
279         break;
280     case Qt::Key_G:
281         flag = !property("QCS_showGrid").toBool();
282         setProperty("QCS_showGrid", flag?"true":"false");
283         m_drawGrid = flag;
284         event->accept();
285         break;
286     case Qt::Key_C:
287         flag = !property("QCS_showTableInfo").toBool();
288         setProperty("QCS_showTableInfo", flag?"true":"false");
289         m_drawTableInfo = flag;
290         if(flag)
291             m_label->show();
292         else
293             m_label->hide();
294         event->accept();
295         break;
296     case Qt::Key_Plus:
297         setProperty("QCS_zoomx", property("QCS_zoomx").toDouble()*2);
298         applyInternalProperties();
299         event->accept();
300         break;
301     case Qt::Key_Minus:
302         setProperty("QCS_zoomx", qMax(1.0, property("QCS_zoomx").toDouble()*0.5));
303         applyInternalProperties();
304         event->accept();
305         break;
306     case Qt::Key_Z:
307         flag = !property("QCS_showScrollbars").toBool();
308         setProperty("QCS_showScrollbars", flag);
309         showScrollbars(flag);
310         event->accept();
311         break;
312     case Qt::Key_H:
313         QMessageBox mb;
314         mb.setText(tr("<span style=\"font-size : 11pt\">"
315                       "<ul>"
316                       "<li><code>+</code> : zoom in"
317                       "<li><code>-</code> : zoom out"
318                       "<li><code>F</code> : toggle freeze (spectrum)"
319                       "<li><code>S</code> : toggle Selector"
320                       "<li><code>G</code> : toggle Grid"
321                       "<li><code>C</code> : toggle table information"
322                       "<li><code>Z</code> : toggle Scrollbars"
323                       "</ul>"
324                       "</span>"
325                       ));
326         mb.setWindowTitle("Graph Shortcuts");
327         mb.exec();
328         event->accept();
329         break;
330     }
331 }
332 
333 
setValue(double value)334 void QuteGraph::setValue(double value)
335 {
336     QuteWidget::setValue(value);
337     // m_value2 = this->getTableNumForIndex(value);
338     /*
339     if(value < 0) {
340         int tabnum = -((int)value);
341         int index = this->getIndexForTableNum(tabnum);
342         QuteWidget::setValue(index);
343     } else {
344         QuteWidget::setValue(value);
345     }
346     */
347 
348 }
349 
setValue(QString text)350 void QuteGraph::setValue(QString text)
351 {
352     bool ok;
353     auto parts = text.splitRef(' ', QString::SkipEmptyParts);
354     if(parts[0] == "@set") {
355         bool ok;
356         int index = parts[1].toInt(&ok);
357         if(parts.size() != 2 || !ok) {
358             qDebug() << "@set: value error";
359             qDebug() << "@set syntax: @set <curveindex:int>";
360             return;
361         }
362         if(index >= 0 && index < curves.size())
363             this->setValue(index);
364     }
365     else if(parts[0] == "@find") {
366         // @find type text
367         // type: one of fft, audio, ftable
368         // Example: @find fft asignal
369         qDebug() << "@find " << parts;
370         if(parts.size() != 3) {
371             qDebug() << "@find: Wrong number of arguments";
372             qDebug() << "syntax: @find <kind:str> <text:str>";
373             qDebug() << "    * kind: one of fft, audio, ftable";
374             qDebug() << "    * text: a text to match against the caption";
375             return;
376         }
377         int index;
378         if(parts[1] == "fft") {
379             index = findCurve(CURVE_SPECTRUM, parts[2].toString());
380         }
381         else if (parts[1] == "audio") {
382             index = findCurve(CURVE_AUDIOSIGNAL, parts[2].toString());
383         }
384         else if (parts[1] == "table") {
385             int tabnum = parts[2].toInt(&ok);
386             if(!ok) {
387                 qDebug()<<"@find table syntax: @find table <tablenumber:int>";
388                 return;
389             }
390             index = this->getIndexForTableNum(tabnum);
391         }
392         else {
393             qDebug() << "@find: graph type should be one of 'table', 'fft' or 'audio', got"
394                      << parts[1];
395             return;
396         }
397         if(index >= 0 && index < curves.size())
398             this->setValue(index);
399         else {
400             qDebug() << "@find: graph not found";
401             return;
402         }
403     }
404     else if(parts[0] == "@freeze") {
405         int index = m_value;
406         if(graphtypes[index] != GraphType::GRAPH_SPECTRUM)
407             return;
408         bool ok;
409         int status = parts[1].toInt(&ok);
410         if(status != 0 && status != 1) {
411             qDebug() << "Syntax: @freeze <status:int> (0 or 1)";
412             return;
413         }
414         freezeSpectrum(status);
415     }
416     else if (parts[0] == "@showPeak") {
417         if(parts.size() == 2) {
418             if(parts[1] == "true" || parts[1] == "1") {
419                 m_showPeak = true;
420             }
421             else if (parts[1] == "false" || parts[1] == "0") {
422                 m_showPeak = false;
423             } else {
424                 bool ok;
425                 double freq = parts[1].toDouble(&ok);
426                 if(!ok) {
427                     qDebug() << "@showPeak: expected a frequency, got" << parts[1];
428                     return;
429                 }
430                 m_showPeak = true;
431                 m_showPeakCenterFrequency = freq;
432             }
433         }
434         else if (parts.size() == 3) {
435             // @showPeak freq bw
436             bool ok;
437             double freq = parts[1].toDouble(&ok);
438             if(!ok) {
439                 qDebug() << "@showPeak: expected a frequency, got" << parts[1];
440                 return;
441             }
442             double bw = parts[2].toDouble(&ok);
443             if(!ok) {
444                 qDebug() << "@showPeak: expected a bandwidth as third arg, got" << parts[2];
445                 return;
446             }
447             m_showPeak = true;
448             if(freq > 0)
449                 m_showPeakCenterFrequency = freq;
450             m_showPeakRelativeBandwidth = bw;
451         }
452         else {
453             qDebug() << "@showPeak syntax:";
454             qDebug() << "   @showPeak false : turn off peak display";
455             qDebug() << "   @showPeak 0     : turn off peak display";
456             qDebug() << "   @showPeak true  : turn on peak display";
457             qDebug() << "   @showPeak 1     : turn on peak display";
458             qDebug() << "   @showPeak <freq:double> : turn on peak display, set freq";
459             qDebug() << "   @showPeak <freq:double> <bandwidth:double>";
460             qDebug() << "      (use freq=0 to only set bandwidth)";
461             return;
462         }
463     }
464     else if(parts[0] == "@getPeak") {
465         m_getPeakChannel = parts[1].toString();
466         qDebug() << "@getPeak channel: " << m_getPeakChannel;
467         MYFLT *ptr;
468         csoundGetChannelPtr(m_ud->csound, &ptr,
469                             m_getPeakChannel.toLocal8Bit().constData(),
470                             CSOUND_INPUT_CHANNEL | CSOUND_CONTROL_CHANNEL);
471         m_peakChannelPtr = ptr;
472     }
473     else {
474         qDebug() << "Command" << text << "not found";
475     }
476 }
477 
478 
refreshWidget()479 void QuteGraph::refreshWidget()
480 {
481     bool needsUpdate = false;
482 #ifdef  USE_WIDGET_MUTEX
483 	widgetLock.lockForRead();
484 #endif
485 	int index = 0;
486 	if (m_valueChanged) {
487         index = (int) m_value;
488         if(index < 0) {
489             m_value2 = -index;
490         } else {
491             m_value2 = getTableNumForIndex(index);
492         }
493         m_value2Changed = false;
494 		m_valueChanged = false;
495 		needsUpdate = true;
496 	}
497 	else if (m_value2Changed) {
498         index = getIndexForTableNum((int)m_value2);
499         if (index >= 0) {
500             m_value = index;
501 			//      m_valueChanged = false;
502         }
503         m_value2Changed = false;
504         needsUpdate = true;
505 	}
506 #ifdef  USE_WIDGET_MUTEX
507 	widgetLock.unlock();  // unlock
508 #endif
509     if (needsUpdate) {
510 		if (index < 0) {
511 			index = getIndexForTableNum(-index);
512 		}
513         if (index < 0 ||
514             index >= curves.size() ||
515             curves[index]->get_caption().isEmpty()) {
516             // Don't show if curve has no name. Is this likely?
517 			return;
518 		}
519 		//    m_pageComboBox->blockSignals(true);
520 		//    m_pageComboBox->setCurrentIndex(index);
521 		//    m_pageComboBox->blockSignals(false);
522 		changeCurve(index);
523 
524 	}
525     // QComboBox *cb = this->m_pageComboBox;
526     // cb->move(this->width() - cb->width(),  this->height()-cb->height());
527 }
528 
createPropertiesDialog()529 void QuteGraph::createPropertiesDialog()
530 {
531 	QuteWidget::createPropertiesDialog();
532 	dialog->setWindowTitle("Graph");
533 
534 	channelLabel->setText("Index Channel name =");
535 	channelLabel->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
536 	//  nameLineEdit->setText(getChannelName());
537 
538 	QLabel *label = new QLabel(dialog);
539 	label = new QLabel(dialog);
540 	label->setText("F-table Channel name =");
541 	layout->addWidget(label, 4, 0, Qt::AlignRight|Qt::AlignVCenter);
542 	name2LineEdit = new QLineEdit(dialog);
543 	name2LineEdit->setText(getChannel2Name());
544 	name2LineEdit->setMinimumWidth(320);
545 	layout->addWidget(name2LineEdit, 4,1,1,3, Qt::AlignLeft|Qt::AlignVCenter);
546 
547 	label = new QLabel(dialog);
548 	label->setText("Zoom X");
549 	layout->addWidget(label, 8, 0, Qt::AlignRight|Qt::AlignVCenter);
550 	zoomxBox = new QDoubleSpinBox(dialog);
551 	zoomxBox->setRange(0.1, 10.0);
552 	zoomxBox->setDecimals(1);
553 	zoomxBox->setSingleStep(0.1);
554 	layout->addWidget(zoomxBox, 8, 1, Qt::AlignLeft|Qt::AlignVCenter);
555 
556 	label = new QLabel(dialog);
557 	label->setText("Zoom Y");
558 	layout->addWidget(label, 8, 2, Qt::AlignRight|Qt::AlignVCenter);
559 	zoomyBox = new QDoubleSpinBox(dialog);
560 	zoomyBox->setRange(0.1, 10.0);
561 	zoomyBox->setDecimals(1);
562 	zoomyBox->setSingleStep(0.1);
563 	layout->addWidget(zoomyBox, 8, 3, Qt::AlignLeft|Qt::AlignVCenter);
564 
565     acceptTablesCheckBox = new QCheckBox(dialog);
566     acceptTablesCheckBox->setText("Enable tables");
567     acceptTablesCheckBox->setToolTip("Enable the display of tables. Each time a table is "
568                                      "creted it will be made available to be selected.\n"
569                                      "NB: modifications to a table will not be shown in the "
570                                      "graph, use a TablePlot widget for that.\n"
571                                      "NB2: If you plan to use the Graph widget to display"
572                                      "spectra or signals, uncheck this option");
573     acceptTablesCheckBox->setChecked(property("QCS_enableTables").toBool());
574     layout->addWidget(acceptTablesCheckBox, 9, 0, Qt::AlignLeft|Qt::AlignVCenter);
575 
576     acceptDisplaysCheckBox = new QCheckBox(dialog);
577     acceptDisplaysCheckBox->setText("Enable Displays");
578     acceptDisplaysCheckBox->setToolTip("Enable displaying audio signals/spectra. Check this"
579                                        "if you plan to use the graph widget to display audio"
580                                        "signals and spectra using the opcodes display/dispfft");
581     acceptDisplaysCheckBox->setChecked(property("QCS_enableDisplays").toBool());
582     layout->addWidget(acceptDisplaysCheckBox, 9, 1, Qt::AlignLeft|Qt::AlignVCenter);
583 
584     showSelectorCheckBox = new QCheckBox(dialog);
585     showSelectorCheckBox->setText("Show Selector");
586     showSelectorCheckBox->setChecked(property("QCS_showSelector").toBool());
587     layout->addWidget(showSelectorCheckBox, 10, 0, Qt::AlignLeft|Qt::AlignVCenter);
588 
589     showGridCheckBox = new QCheckBox(dialog);
590     showGridCheckBox->setText("Show Grid");
591     showGridCheckBox->setChecked(property("QCS_showGrid").toBool());
592     showGridCheckBox->setToolTip("Show the grid. Has effect only for spectral graphs");
593     layout->addWidget(showGridCheckBox, 10, 1, Qt::AlignLeft|Qt::AlignVCenter);
594 
595     showTableInfoCheckBox = new QCheckBox(dialog);
596     showTableInfoCheckBox->setText("Show Table Information");
597     showTableInfoCheckBox->setCheckState(
598                 property("QCS_showTableInfo").toBool()?Qt::Checked:Qt::Unchecked);
599     showTableInfoCheckBox->setToolTip("Show the grid. Has effect only for spectral graphs");
600     layout->addWidget(showTableInfoCheckBox, 11, 0, Qt::AlignLeft|Qt::AlignVCenter);
601 
602     showScrollbarsCheckBox = new QCheckBox(dialog);
603     showScrollbarsCheckBox->setText("Show Scrollbars");
604     showScrollbarsCheckBox->setChecked(property("QCS_showScrollbars").toBool());
605     layout->addWidget(showScrollbarsCheckBox, 11, 1, Qt::AlignLeft|Qt::AlignVCenter);
606 
607 #ifdef  USE_WIDGET_MUTEX
608 	widgetLock.lockForRead();
609 #endif
610 	zoomxBox->setValue(property("QCS_zoomx").toDouble());
611 	zoomyBox->setValue(property("QCS_zoomy").toDouble());
612 #ifdef  USE_WIDGET_MUTEX
613 	widgetLock.unlock();
614 #endif
615 	//   channelLabel->hide();
616 	//   nameLineEdit->hide();
617 }
618 
applyProperties()619 void QuteGraph::applyProperties()
620 {
621 #ifdef  USE_WIDGET_MUTEX
622 	widgetLock.lockForWrite();
623 #endif
624 	setProperty("QCS_objectName2", name2LineEdit->text());
625 	setProperty("QCS_zoomx", zoomxBox->value());
626 	setProperty("QCS_zoomy", zoomyBox->value());
627     setProperty("QCS_dispx", 1);
628 	setProperty("QCS_dispy", 1);
629 	setProperty("QCS_modex", "lin");
630 	setProperty("QCS_modey", "lin");
631 	setProperty("QCS_all", true);
632     setProperty("QCS_showSelector", showSelectorCheckBox->checkState());
633     setProperty("QCS_showGrid", showGridCheckBox->checkState());
634     setProperty("QCS_showScrollbars", showScrollbarsCheckBox->isChecked());
635     setProperty("QCS_enableTables", acceptTablesCheckBox->isChecked());
636     setProperty("QCS_enableDisplays", acceptDisplaysCheckBox->isChecked());
637 
638     m_enableTables = acceptTablesCheckBox->isChecked();
639     m_enableDisplays = acceptDisplaysCheckBox->isChecked();
640 
641     showTableInfo(showTableInfoCheckBox->checkState());
642 
643 #ifdef  USE_WIDGET_MUTEX
644 	widgetLock.unlock();
645 #endif
646 	QuteWidget::applyProperties();
647 }
648 
649 
findCurve(CurveType type,QString text)650 int QuteGraph::findCurve(CurveType type, QString text) {
651     // returns the index or -1 if not found
652     for (int i = 0; i < curves.size(); i++) {
653         if(curves[i]->get_type() != type)
654             continue;
655         QString caption = curves[i]->get_caption();
656         if (caption.contains(text)) {
657             return i;
658         }
659     }
660     return -1;
661 }
662 
changeCurve(int index)663 void QuteGraph::changeCurve(int index)
664 {
665     if(curves.size() <= 0)
666         return;
667 
668     int origRequest = index;
669     StackedLayoutWidget *stacked =  static_cast<StackedLayoutWidget *>(m_widget);
670     if (index == -1) { // goto last curve
671         index = stacked->count() - 1;
672 	}
673 	else if (index == -2) { // update curve but don't change which
674         if (m_value < 0)
675 			index = getIndexForTableNum(-m_value);
676         else
677 			index = (int) m_value;
678     } else if (index >= stacked->count()) {
679         qDebug() << "changeCurve: index out of range. Num indices:"<<stacked->size();
680         return;
681     }
682     else if (stacked->currentIndex() == index) {
683         return;
684     } else {
685         // change curve
686         auto view = stacked->currentWidget();
687         view->hide();
688         stacked->blockSignals(true);
689         stacked->setCurrentIndex(index);
690         stacked->blockSignals(false);
691         m_pageComboBox->blockSignals(true);
692         m_pageComboBox->setCurrentIndex(index);
693         m_pageComboBox->blockSignals(false);
694     }
695 
696     if (index < 0  || index >= curves.size()) { // Invalid index
697         QDEBUG << "Invalid index" << index;
698         return;
699     }
700     m_value = index;
701     switch(graphtypes[index]) {
702     case GraphType::GRAPH_FTABLE: {
703         int ftable = getTableNumForIndex(index);
704         if (m_value2 != ftable) {
705             m_value2 = ftable;
706             m_value2Changed = true;
707         }
708         if(m_drawTableInfo) {
709             auto curve = curves[index];
710             auto text = QString("%1 pts (%2, %3)")
711                     .arg(curve->get_size())
712                     .arg(curve->get_max(), 0, 'f', 3)
713                     .arg(curve->get_min(), 0, 'f', 3);
714             m_label->setText(text);
715             m_label->show();
716         }
717         if(origRequest != -2) {
718             drawGraph(curves[index], index);
719         }
720         break;
721     }
722     case GraphType::GRAPH_SPECTRUM:
723         m_value2 = -1;
724         m_label->hide();
725         break;
726     case GraphType::GRAPH_AUDIOSIGNAL:
727         m_value2 = -1;
728         m_label->hide();
729         break;
730     }
731     scaleGraph(index);
732 
733 }
734 
indexChanged(int index)735 void QuteGraph::indexChanged(int index)
736 {
737 #ifdef  USE_WIDGET_MUTEX
738 	widgetLock.lockForRead();
739 #endif
740 	if (m_channel == "") {
741 		setInternalValue(index);
742 	}
743 	else {
744         QPair<QString, double> channelValue(m_channel, index);
745 		emit newValue(channelValue);
746 	}
747     if (m_channel2 == "") {
748 		setValue2(getTableNumForIndex(index));
749 	}
750 	else {
751 		QPair<QString, double> channel2Value(m_channel2, getTableNumForIndex(index));
752 		emit newValue(channel2Value);
753 	}
754 
755 #ifdef  USE_WIDGET_MUTEX
756 	widgetLock.unlock();
757 #endif
758 
759 }
760 
clearCurves()761 void QuteGraph::clearCurves()
762 {
763 	//  curveLock.lock();
764     m_widget->blockSignals(true);
765 	static_cast<StackedLayoutWidget *>(m_widget)->clearCurves();
766 	m_widget->blockSignals(false);
767 	m_pageComboBox->blockSignals(true);
768 	m_pageComboBox->clear();
769 	m_pageComboBox->blockSignals(false);
770 	curves.clear();
771     graphtypes.clear();
772 	lines.clear();
773 	polygons.clear();
774 	m_gridlines.clear();
775     m_gridTextsX.clear();
776     m_gridTextsY.clear();
777     m_spectrumPeakTexts.clear();
778     m_spectrumPeakMarkers.clear();
779     m_showPeak = false;
780     m_showPeakTemp = false;
781     m_showPeakCenterFrequency = 1000;
782     //  curveLock.unlock();
783 }
784 
addCurve(Curve * curve)785 void QuteGraph::addCurve(Curve * curve)
786 {
787     QMutexLocker locker(&curveLock);
788     Q_ASSERT(curve != nullptr);
789     GraphType graphType;
790     QGraphicsView *view;
791     CurveType type = curve->get_type();
792     if(type == CURVE_SPECTRUM) {
793         if(!m_enableDisplays)
794             return;
795         graphType = GraphType::GRAPH_SPECTRUM;
796         auto spectralView = new SpectralView(m_widget);
797         view = static_cast<QGraphicsView*>(spectralView);
798         connect(spectralView, SIGNAL(mouseReleased()), this, SLOT(mouseReleased()));
799         view->setRenderHint(QPainter::Antialiasing);
800     } else if(type == CURVE_FTABLE) {
801         if(!m_enableTables)
802             return;
803         graphType = GraphType::GRAPH_FTABLE;
804         view = new QGraphicsView(m_widget);
805         view->setRenderHint(QPainter::Antialiasing);
806     } else {
807         if(!m_enableDisplays)
808             return;
809         graphType = GraphType::GRAPH_AUDIOSIGNAL;
810         view = new QGraphicsView(m_widget);
811     }
812     QGraphicsScene *scene = new QGraphicsScene(view);
813 	view->setContextMenuPolicy(Qt::NoContextMenu);
814     view->setScene(scene);
815     view->setObjectName(curve->get_caption());
816     auto scrollbarPolicy = property("QCS_showScrollbars").toBool() ? Qt::ScrollBarAsNeeded
817                                                                    : Qt::ScrollBarAlwaysOff;
818     view->setHorizontalScrollBarPolicy(scrollbarPolicy);
819     view->show();
820     scene->setBackgroundBrush(QBrush(Qt::black));
821     lines.append(QVector<QGraphicsLineItem *>());
822     QVector<QGraphicsLineItem *> gridLinesVector;
823     QVector<QGraphicsTextItem *> gridTextVectorX;
824     QVector<QGraphicsTextItem *> gridTextVectorY;
825     qreal freqStep = 1000.0;
826     // Max. samplerate. We generate a grid for this and can make lines/labels visible or
827     // not depending on the actual sample rate
828     qreal sr = 96000;
829     int numTicksX = (int)(sr*0.5 / freqStep);
830     int numTicksY = m_numticksY;
831     auto gridpen = QPen(QColor(90, 90, 90));
832     auto gridpen2 = QPen(QColor(60, 60, 60));
833     gridpen.setCosmetic(true);
834     gridpen2.setCosmetic(true);
835 
836     auto marker = new QGraphicsRectItem();
837     marker->setVisible(false);
838     auto markerpen = QPen(QColor(250, 250, 250));
839     markerpen.setCosmetic(true);
840     marker->setPen(markerpen);
841     marker->setBrush(QBrush(QColor(255, 255, 255, 100)));
842     m_spectrumPeakMarkers.append(marker);
843     scene->addItem(marker);
844     auto markerText = new QGraphicsTextItem();
845     markerText->setDefaultTextColor(markerpen.color());
846     markerText->setVisible(false);
847     markerText->setFlags(QGraphicsItem::ItemIgnoresTransformations);
848     markerText->setFont(QFont("Sans", 8));
849     scene->addItem(markerText);
850     m_spectrumPeakTexts.append(markerText);
851     m_dbRange = 100;
852 
853     if(graphType == GraphType::GRAPH_SPECTRUM) {
854         for (int i = 0 ; i < numTicksY; i++) {
855             // 0 - 0db, 5 -> 100 dB (6:120
856             QGraphicsLineItem *gridLine = new QGraphicsLineItem();
857             gridLine->setPen(gridpen);
858             scene->addItem(gridLine);
859             gridLinesVector.append(gridLine);
860             QGraphicsTextItem *gridText = new QGraphicsTextItem();
861             gridText->setDefaultTextColor(Qt::gray);
862             gridText->setFlags(QGraphicsItem::ItemIgnoresTransformations);
863             int dbs = round(float(i)/numTicksY * 120.0);
864             gridText->setHtml(QString("<div style=\"background:#000000;\">-%1 </p>"
865                                       ).arg(dbs));
866             gridText->setFont(QFont("Sans", 6));
867             gridText->setVisible(false);
868             scene->addItem(gridText);
869             gridTextVectorY.append(gridText);
870 
871         }
872 
873         for (int i = 0; i < numTicksX; i++) {
874             QGraphicsLineItem *gridLine = new QGraphicsLineItem();
875             gridLine->setPen(i%2 == 0 ? gridpen : gridpen2);
876             scene->addItem(gridLine);
877             gridLinesVector.append(gridLine);
878             QGraphicsTextItem *gridText = new QGraphicsTextItem();
879             gridText->setDefaultTextColor(Qt::gray);
880             gridText->setFlags(QGraphicsItem::ItemIgnoresTransformations);
881             if (i > 0 && i%2==0) {
882                 // double kHz = i*((numTicksX-1.0)/numTicksX) * 2.0;
883                 double kHz = i * freqStep / 1000.0 ;
884                 if(i%10==0)
885                     gridText->setHtml(
886                                 QString("<div style=\"background:#000000;\">%1k</p>")
887                                 .arg(kHz, 2, 'f', 1));
888                 else
889                     gridText->setHtml(
890                                 QString("<div style=\"background:#000000;\">%1</p>")
891                                 .arg(kHz, 2, 'f', 1));
892 
893             }
894             gridText->setFont(QFont("Sans", 6));
895             gridText->setVisible(false);
896             scene->addItem(gridText);
897             gridTextVectorX.append(gridText);
898         }
899     }
900 
901     m_gridlines.append(gridLinesVector);
902     m_gridTextsX.append(gridTextVectorX);
903     m_gridTextsY.append(gridTextVectorY);
904 
905     graphtypes.append(graphType);
906 
907     QGraphicsPolygonItem * item = new QGraphicsPolygonItem();
908     auto graphPen = QPen(Qt::yellow);
909     graphPen.setCosmetic(true);
910     item->setPen(graphPen);
911     item->show();
912     polygons.append(item);
913     scene->addItem(item);
914     view->setResizeAnchor (QGraphicsView::NoAnchor);
915     // view->setFocusPolicy(Qt::NoFocus);
916 	m_pageComboBox->blockSignals(true);
917     QString curveTitle = curve->get_caption().trimmed();
918     if(curveTitle.endsWith(":"))
919         curveTitle = curveTitle.mid(0, curveTitle.size()-1);
920     m_pageComboBox->addItem(curveTitle);
921     // m_pageComboBox->addItem(curve->get_title());
922 	m_pageComboBox->blockSignals(false);
923 	//  curveLock.lock();
924 	static_cast<StackedLayoutWidget *>(m_widget)->addWidget(view);
925     curves.append(curve);
926     if (m_value == curves.size() - 1) {
927         // If new curve created corresponds to current stored value
928 		changeCurve(m_value);
929 	}
930 }
931 
getCurveIndex(Curve * curve)932 int QuteGraph::getCurveIndex(Curve * curve)
933 {
934     Q_ASSERT(curve != nullptr);
935 	int index = -1;
936 	for (int i = 0; i < curves.size(); i++) {
937 		if (curves[i] == curve) {
938 			index = i;
939 			break;
940 		}
941 	}
942     return index;
943 }
944 
getView(int index)945 QGraphicsView * QuteGraph::getView(int index) {
946     StackedLayoutWidget *widget_ = static_cast<StackedLayoutWidget *>(m_widget);
947     QGraphicsView *view = static_cast<QGraphicsView *>(widget_->widget(index));
948     return view;
949 }
950 
drawGraph(Curve * curve,int index)951 void QuteGraph::drawGraph(Curve *curve, int index) {
952     // QString caption = curve->get_caption();
953     // auto view = getView(index);
954     // switch(graphtypes[index]) {
955     switch(curve->get_type()) {
956     // case GraphType::GRAPH_FTABLE:
957     case CurveType::CURVE_FTABLE:
958         // drawFtable(curve, index);
959         // view->setRenderHint(QPainter::Antialiasing);
960         drawFtablePath(curve, index);
961         scaleGraph(index);
962         break;
963     // case GraphType::GRAPH_SPECTRUM:
964     case CurveType::CURVE_SPECTRUM:
965         // view->setRenderHint(QPainter::Antialiasing);
966         drawSpectrum(curve, index);
967         // drawSpectrumPath(curve, index);
968         changeCurve(-2); //update curve
969         break;
970     case CurveType::CURVE_AUDIOSIGNAL:
971     // case GraphType::GRAPH_AUDIOSIGNAL:
972         // drawSignal(curve, index);
973         drawSignalPath(curve, index);
974         changeCurve(-2); //update curve
975         break;
976     }
977 }
978 
setCurveData(Curve * curve)979 void QuteGraph::setCurveData(Curve * curve)
980 {
981     Q_ASSERT(curve != nullptr);
982 	int index = getCurveIndex(curve);
983 
984     if (index >= curves.size() ||
985         index < 0 ||
986         index != m_value) {
987         return;
988 	}
989 
990     /*
991     if(!m_enableTables && graphtypes[index] == GraphType::GRAPH_FTABLE)
992         return;
993     */
994 
995     auto view = getView(index);
996 
997     // Refitting curves in view resets the scrollbar so we need the previous value
998     int viewPosx = view->horizontalScrollBar()->value();
999 	int viewPosy = view->verticalScrollBar()->value();
1000 
1001     drawGraph(curve, index);
1002     view->horizontalScrollBar()->setValue(viewPosx);
1003     view->verticalScrollBar()->setValue(viewPosy);
1004 }
1005 
applyInternalProperties()1006 void QuteGraph::applyInternalProperties()
1007 {
1008 	QuteWidget::applyInternalProperties();
1009     if(property("QCS_showSelector").toBool()) {
1010         m_pageComboBox->show();
1011     } else {
1012         m_pageComboBox->hide();
1013     }
1014 	changeCurve(-2);  // Redraw
1015     m_drawGrid = property("QCS_showGrid").toBool();
1016     m_drawTableInfo = property("QCS_showTableInfo").toBool();
1017 
1018     showScrollbars(property("QCS_showScrollbars").toBool());
1019 }
1020 
drawFtablePath(Curve * curve,int index)1021 void QuteGraph::drawFtablePath(Curve *curve, int index) {
1022     Q_ASSERT(index >= 0);
1023     QGraphicsScene *scene = this->getView(index)->scene();
1024     // QGraphicsScene *scene = static_cast<QGraphicsView *>(static_cast<StackedLayoutWidget *>(m_widget)->widget(index))->scene();
1025 
1026     double max = curve->get_max();
1027     max = max == 0 ? 1: max;
1028     int curveSize = curve->get_size();
1029 
1030     int decimate = curveSize /1024;
1031     if (decimate == 0) {
1032         decimate = 1;
1033     }
1034     auto pen = QPen(QColor(255, 45, 7), 0);
1035 
1036     auto rect = this->rect();
1037     int width = rect.width();
1038     int step = curveSize / width;
1039     if(step == 0)
1040         step = 1;
1041 
1042     QPainterPath path;
1043     for (int i = 0; i < curveSize; i+=step) {
1044         double value = curve->get_data(i);
1045         path.lineTo(QPointF(i, -value));
1046     }
1047     scene->clear();
1048     if(step > 1) {
1049         pen.setWidth(0);
1050     }
1051     scene->addPath(path, pen);
1052 }
1053 
1054 
drawFtable(Curve * curve,int index)1055 void QuteGraph::drawFtable(Curve * curve, int index)
1056 {
1057 	//  bool live = curve->getOriginal() != 0;
1058 	Q_ASSERT(index >= 0);
1059     QString caption = curve->get_caption();
1060     if (caption.isEmpty()) {
1061         return;
1062     }
1063     QGraphicsScene *scene = static_cast<QGraphicsView *>(static_cast<StackedLayoutWidget *>(m_widget)->widget(index))->scene();
1064     double max = curve->get_max();
1065     max = max == 0 ? 1: max;
1066     int size = (int) curve->get_size();
1067     int decimate = size /1024;
1068     if (decimate == 0) {
1069         decimate = 1;
1070     }
1071     auto pen = QPen(QColor(255, 45, 7));
1072     pen.setCosmetic(true);
1073     if (lines[index].size() != size) {
1074         foreach (QGraphicsLineItem *line, lines[index]) {
1075             scene->removeItem(line);
1076             delete line;
1077         }
1078         lines[index].clear();
1079         for (int i = 0; i < size; i++) {
1080             if (decimate == 0 || i%decimate == 0) {
1081                 QGraphicsLineItem *line = new QGraphicsLineItem(i, 0, i, 0);
1082                 line->setPen(pen);
1083                 lines[index].append(line);
1084                 scene->addItem(line);
1085             }
1086         }
1087     }
1088     for (int i = 0; i < lines[index].size(); i++) { //skip first item, which is base line
1089         QGraphicsLineItem *line = static_cast<QGraphicsLineItem *>(lines[index][i]);
1090         MYFLT value = curve->get_data((i * decimate));
1091         line->setLine((i * decimate), 0, (i * decimate),  -value );
1092         line->show();
1093     }
1094     scaleGraph(index);
1095 }
1096 
drawSpectrumPath(Curve * curve,int index)1097 void QuteGraph::drawSpectrumPath(Curve *curve, int index) {
1098     int curveSize = curve->get_size();
1099     QGraphicsScene *scene = static_cast<QGraphicsView *>(static_cast<StackedLayoutWidget *>(m_widget)->widget(index))->scene();
1100     QPainterPath path;
1101     double db0 = m_ud->zerodBFS;
1102     path.moveTo(0, -20.0*log10(fabs(curve->get_data(0))/db0));
1103     for(int i=1; i < curveSize; i++) {
1104         double value = 20.0*log10(fabs(curve->get_data(i))/db0);
1105         path.lineTo(QPointF(i, -value));
1106     }
1107     scene->clear();
1108     auto pen = QPen(Qt::yellow);
1109     pen.setCosmetic(true);
1110 
1111     QPainterPath gridPath;
1112     QPainter painter;
1113 
1114     if(m_drawGrid) {
1115         int sr = (int)this->getSr();
1116         int nyquist = sr / 2;
1117         int step = 1000;
1118         int numTicksX = nyquist / step;
1119         int numTicksY = 7;
1120         printf("grid: nyquist: %d, numticks: %d\n", nyquist, numTicksX);
1121 
1122         auto gridPen = QPen(QColor(40, 40, 40));
1123         gridPen.setCosmetic(true);
1124         auto textColor = QColor(128, 128, 128);
1125         qreal curveSizeF = (qreal)curveSize;
1126         auto font = QFont("Sans");
1127         font.setPixelSize(9);
1128         const int maxy = 110;
1129         for (int i = 1; i < numTicksX; i++) {
1130             qreal freq = i * step;
1131             qreal x = freq/nyquist * curveSizeF;
1132             gridPath.moveTo(x, 0);
1133             gridPath.lineTo(x, maxy);
1134             if(i%2 == 0) {
1135                 auto item = scene->addText(QString::number(freq/1000.0, 'f', 1), font);
1136                 item->setDefaultTextColor(textColor);
1137                 item->setPos(x, 0);
1138                 item->setFlag(item->ItemIgnoresTransformations, true);
1139             }
1140         }
1141         for (int i=1; i < numTicksY-1; i++) {
1142             qreal y = (qreal)i/numTicksY * maxy;
1143             gridPath.moveTo(0, y);
1144             gridPath.lineTo(curveSizeF, y);
1145         }
1146         scene->addPath(gridPath, gridPen);
1147     }
1148     scene->addPath(path, pen);
1149 }
1150 
spectrumGetPeak(Curve * curve,double freq,double bandwidth)1151 size_t QuteGraph::spectrumGetPeak(Curve *curve, double freq, double bandwidth) {
1152     qreal sr = this->getSr(44100.);
1153     qreal nyquist = sr * 0.5;
1154     size_t curveSize = curve->get_size();
1155     qreal minfreq = freq - bandwidth*0.5;
1156     qreal maxfreq = freq + bandwidth*0.5;
1157     minfreq = qMax(1.0, minfreq);
1158     maxfreq = qMax(minfreq, maxfreq);
1159     size_t index0 = (size_t)(minfreq / nyquist * curveSize);
1160     index0 = index0 < curveSize - 1 ? index0 : curveSize - 1;
1161     size_t index1 = (size_t)(maxfreq / nyquist * curveSize);
1162     index1 = index1 < curveSize - 1 ? index1 : curveSize - 1;
1163     qreal maxvalue = 0;
1164     size_t maxindex = 0;
1165     if(!m_frozen) {
1166         for(int i=index0; i<index1; i++) {
1167             auto data = curve->get_data(i);
1168             if (data > maxvalue) {
1169                 maxvalue = data;
1170                 maxindex = i;
1171             }
1172         }
1173     }
1174     else {
1175         for(int i=index0; i<index1; i++) {
1176             auto data = frozenCurve[i];
1177             if (data > maxvalue) {
1178                 maxvalue = data;
1179                 maxindex = i;
1180             }
1181         }
1182     }
1183     if(maxindex > curveSize - 1 || maxindex < 0) {
1184         qDebug() << "spectrum peak: wrong index " << maxindex;
1185         return 0;
1186     }
1187     return maxindex;
1188 }
1189 
mton(double midinote)1190 QString mton(double midinote) {
1191     //                                C  C# D D#  E  F  F# G G# A Bb B
1192     static const int _pc2idx[] = {2, 2, 3, 3, 4, 5, 5, 6, 6, 0, 1, 1};
1193     static const int _pc2alt[] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 2, 0};
1194     static const char _alts[] = " #b";
1195 
1196     QString dst = "";
1197     double m = midinote;
1198 
1199     int octave = (int) (m / 12 - 1);
1200     int pc = (int)m % 12;
1201     int cents = round((m - floor(m)) * 100.0);
1202     int sign;
1203 
1204     if (cents == 0) {
1205         sign = 0;
1206     } else if (cents <= 50) {
1207         sign = 1;
1208     } else {
1209         cents = 100 - cents;
1210         sign = -1;
1211         pc += 1;
1212         if (pc == 12) {
1213             pc = 0;
1214             octave += 1;
1215         }
1216     }
1217     if(octave >= 0) {
1218         dst.append('0' + octave);
1219     } else {
1220         dst.append('-');
1221         dst.append('0' - octave);
1222     }
1223     dst.append('A' + _pc2idx[pc]);
1224     int32_t alt = _pc2alt[pc];
1225     if(alt > 0) {
1226         dst.append(_alts[alt]);
1227     }
1228     if(sign == 1) {
1229         dst.append('+');
1230         if(cents != 50) {
1231             dst += QString::number(cents);
1232         }
1233     } else if(sign == -1) {
1234         dst.append('-');
1235         if(cents != 50) {
1236             dst += QString::number(cents);
1237         }
1238     }
1239     return dst;
1240 }
1241 
1242 
quantizePoint(int x,int y,int resx,int resy)1243 QPoint quantizePoint(int x, int y, int resx, int resy) {
1244     x = static_cast<int>(round((double)x / resx) * resx);
1245     y = static_cast<int>(round((double)y / resy) * resy);
1246     return QPoint(x, y);
1247 }
1248 
freezeSpectrum(bool status)1249 void QuteGraph::freezeSpectrum(bool status) {
1250     int index = m_value;
1251     auto curve = curves[index];
1252     if(status) {
1253         m_frozen = true;
1254         frozenCurve.resize(curve->get_size());
1255         for(int i=0; i < curve->get_size(); i++) {
1256             frozenCurve[i] = curve->get_data(i);
1257         }
1258     } else {
1259         m_frozen = false;
1260     }
1261 
1262 }
1263 
drawSpectrum(Curve * curve,int index)1264 void QuteGraph::drawSpectrum(Curve *curve, int index) {
1265     if(!curveLock.tryLock())
1266         return;
1267     int curveSize = curve->get_size();
1268     if(curveSize != frozenCurve.size() && graphtypes[index] == GraphType::GRAPH_SPECTRUM)
1269         freezeSpectrum(false);
1270     auto view = getView(index);
1271     // QGraphicsScene *scene = static_cast<QGraphicsView *>(static_cast<StackedLayoutWidget *>(m_widget)->widget(index))->scene();
1272     QVector<QPointF> polygonPoints;
1273     polygonPoints.resize(curveSize + 2);
1274     double dbRange = m_dbRange;
1275     polygonPoints[0] = QPointF(0, dbRange);
1276     double db0 = m_ud->zerodBFS;
1277 
1278     if(!m_frozen) {
1279         for (int i = 0; i < (int) curveSize; i++) {
1280             auto data = curve->get_data(i);
1281             double db = data > 0.000001 ? 20.0*log10(data/db0) : -dbRange;
1282             // double value = 20.0*log10(fabs(curve->get_data(i))/db0);
1283             polygonPoints[i+1] = QPointF(i, -db); //skip first item, which is base line
1284         }
1285     } else {
1286         for (int i = 0; i < frozenCurve.size(); i++) {
1287             auto data = frozenCurve[i];
1288             double db = data > 0.000001 ? 20.0*log10(data/db0) : -dbRange;
1289             // double value = 20.0*log10(fabs(curve->get_data(i))/db0);
1290             polygonPoints[i+1] = QPointF(i, -db); //skip first item, which is base line
1291         }
1292     }
1293 
1294     polygonPoints.back() = QPointF(curveSize - 1, dbRange);
1295     polygons[index]->setPolygon(QPolygonF(polygonPoints));
1296 
1297     // m_pageComboBox->setItemText(index, curve->get_caption());
1298     // draw Grid
1299     int numTicksY = m_numticksY;
1300     auto gridlinesvec = m_gridlines[index];
1301     auto gridtextvecx = m_gridTextsX[index];
1302     auto gridtextvecy = m_gridTextsY[index];
1303     qreal sr = this->getSr(44100.0);
1304     qreal nyquist = sr * 0.5;
1305     qreal freqStep = 1000.0;  // TODO: allow to configure this
1306     int numTicksX = (int)(nyquist / freqStep);
1307     // TODO: fix y axis
1308     if(m_drawGrid) {
1309         gridtextvecy[0]->setVisible(true);
1310         for (int i = 1; i < numTicksY; i++) {
1311             int y = float(i)/(numTicksY) * dbRange;
1312             gridlinesvec[i]->setLine(0, y, curveSize, y);
1313             gridlinesvec[i]->setVisible(true);
1314             gridtextvecy[i]->setPos(0, y);
1315             gridtextvecy[i]->setVisible(true);
1316         }
1317 
1318         for (int i = 1; i < numTicksX; i++) {
1319             // qreal x = i * qreal(curveSize)/numTicksX;
1320             qreal freq = i * freqStep;
1321             qreal x = freq/nyquist * curveSize;
1322             int idx = i + m_numticksY;
1323             gridlinesvec[idx]->setLine(x, 0, x, dbRange);
1324             gridlinesvec[idx]->setVisible(true);
1325             gridtextvecx[i]->setPos(x, 0);
1326             gridtextvecx[i]->setVisible(true);
1327         }
1328     } else {
1329         for(int i=0; i < numTicksY; i++) {
1330             gridlinesvec[i]->setVisible(false);
1331             gridtextvecy[i]->setVisible(false);
1332         }
1333         for (int i = 0; i < numTicksX; i++) {
1334             gridlinesvec[i+m_numticksY]->setVisible(false);
1335             gridtextvecx[i]->setVisible(false);
1336         }
1337     }
1338 
1339     if(m_showPeak || m_showPeakTemp) {
1340         auto freq = m_showPeakTemp ? m_showPeakTempFrequency : m_showPeakCenterFrequency;
1341         double bandwidth;
1342         if(m_showPeakTemp) {
1343             // if using the mouth to point at a near peak, we take zoom into account and
1344             // the bandwidth is a fraction of the displayed frequency range.
1345             auto zoomx = property("QCS_zoomx").toDouble();
1346             bandwidth = nyquist / zoomx / 8.0;
1347         }
1348         else
1349             bandwidth = freq * m_showPeakRelativeBandwidth;
1350         size_t peakIndex = this->spectrumGetPeak(curve, freq, bandwidth);
1351         auto data = m_frozen ? frozenCurve[peakIndex] : curve->get_data(peakIndex);
1352         double db = data > 0.000001 ? 20.0*log10(data/db0) : -dbRange;
1353         auto marker = m_spectrumPeakMarkers[index];
1354         auto view = this->getView(index);
1355         auto vcenter = view->mapFromScene(QPointF(peakIndex, -db));
1356         auto markerLeftTop = view->mapToScene(QPoint(vcenter.x() - 4, vcenter.y()-4));
1357         auto markerRightBottom = view->mapToScene(vcenter.x() + 4, vcenter.y()+4);
1358         marker->setRect(markerLeftTop.x(), markerLeftTop.y(),
1359                         markerRightBottom.x()-markerLeftTop.x(),
1360                         markerRightBottom.y() - markerLeftTop.y());
1361         auto markerText = m_spectrumPeakTexts[index];
1362         auto textPos = view->mapToScene(vcenter.x() + 10, vcenter.y() - 12);
1363         auto markerY = m_lastTextMarkerY <= 0 ? textPos.y() :
1364                                                 textPos.y() * 0.2 + m_lastTextMarkerY * 0.8;
1365         auto markerX = m_lastTextMarkerX <= 0 ? textPos.x() :
1366                                                 textPos.x() * 0.3 + m_lastTextMarkerX * 0.7;
1367         m_lastTextMarkerY = markerY;
1368         m_lastTextMarkerX = markerX;
1369         markerText->setPos(markerX, markerY);
1370         double factor = nyquist / curveSize;
1371         double peakFreq;
1372         if(peakIndex <= 1 || peakIndex >= curveSize - 2) {
1373             peakFreq = factor * peakIndex;
1374         }
1375         else {
1376             double freq0 = qreal(peakIndex-1)*factor;
1377             double freq1 = qreal(peakIndex)*factor;
1378             double freq2 = qreal(peakIndex+1)*factor;
1379             double amp1 = data;
1380             double amp0, amp2;
1381             if(!m_frozen) {
1382                 amp0 = curve->get_data(peakIndex-1);
1383                 amp2 = curve->get_data(peakIndex+1);
1384             } else {
1385                 amp0 = frozenCurve[peakIndex-1];
1386                 amp2 = frozenCurve[peakIndex+1];
1387             }
1388             amp1 *= amp1;
1389             amp0 *= amp0;
1390             amp2 *= amp2;
1391             peakFreq = (freq0*amp0 + freq1*amp1 + freq2*amp2) / (amp0+amp1+amp2);
1392         }
1393         peakFreq = m_lastPeakFreq <= 0 ? peakFreq : peakFreq * 0.2 + m_lastPeakFreq * 0.8;
1394         m_lastPeakFreq = peakFreq;
1395         double a4 = csoundGetA4(this->m_ud->csound);
1396         double midinote;
1397         QString notename;
1398         if(peakFreq > 10) {
1399             midinote = 12.0 * log2(peakFreq / a4) + 69.0;
1400             notename = mton(midinote);
1401         }
1402         else {
1403             midinote = 0;
1404             notename = "LOW";
1405         }
1406         markerText->setPlainText(QString("%1 Hz (%2)").arg((int)(peakFreq+0.5)).arg(notename));
1407 
1408         marker->setVisible(true);
1409         markerText->setVisible(true);
1410         if(m_peakChannelPtr != nullptr) {
1411             *m_peakChannelPtr = peakFreq;
1412         }
1413     }
1414     else {
1415         if(m_getPeakChannel != nullptr) {
1416             *m_peakChannelPtr = 0;
1417         }
1418     }
1419     curveLock.unlock();
1420 }
1421 
drawSignalPath(Curve * curve,int index)1422 void QuteGraph::drawSignalPath(Curve *curve, int index) {
1423     int curveSize = curve->get_size();
1424     QPainterPath path;
1425     auto zerodbfs = m_ud->zerodBFS;
1426     for(int i=0; i<curveSize; i++) {
1427         auto value = curve->get_data(i)/zerodbfs;
1428         path.lineTo(i, value);
1429     }
1430     QPainterPath grid;
1431     grid.moveTo(0, 0);
1432     grid.lineTo(curveSize, 0);
1433 
1434     QGraphicsScene *scene = static_cast<QGraphicsView *>(static_cast<StackedLayoutWidget *>(m_widget)->widget(index))->scene();
1435     auto pen = QPen(QColor(255, 193, 7), 0);
1436     scene->clear();
1437     scene->addPath(grid, QPen(QColor(40, 40, 40), 0));
1438     scene->addPath(path, pen);
1439 }
1440 
drawSignal(Curve * curve,int index)1441 void QuteGraph::drawSignal(Curve *curve, int index)
1442 {
1443     int curveSize = curve->get_size();
1444     QVector<QPointF> polygonPoints;
1445     polygonPoints.resize(curveSize + 2);
1446     polygonPoints[0] = QPointF(0,0);
1447     for (int i = 0; i < (int) curveSize; i++) {
1448         double value = curve->get_data(i)/m_ud->zerodBFS;
1449         polygonPoints[i + 1] = QPointF(i, value); //skip first item, which is base line
1450     }
1451     polygonPoints.back() = QPointF(curveSize - 1,0);
1452     polygons[index]->setPolygon(QPolygonF(polygonPoints));
1453     auto pen = QPen(QColor(255, 193, 7));
1454     pen.setCosmetic(true);
1455     polygons[index]->setPen(pen);
1456     polygons[index]->setBrush(Qt::NoBrush);
1457     m_pageComboBox->setItemText(index, curve->get_caption());
1458 }
1459 
scaleGraph(int index)1460 void QuteGraph::scaleGraph(int index)
1461 {
1462     auto curve = curves[index];
1463 
1464     double max = curves[index]->get_max();
1465     double min = curves[index]->get_min();
1466 	double zoomx = property("QCS_zoomx").toDouble();
1467 	double zoomy = property("QCS_zoomy").toDouble();
1468 	//  double span = max - min;
1469     //  FIXME implement dispx, dispy and modex, modey
1470     int size = curve->get_size();
1471     double sizef = (double)size;
1472     auto view = getView(index);
1473     //  view->setResizeAnchor(QGraphicsView::NoAnchor);
1474     auto graphType = graphtypes[index];
1475     if(graphType == GraphType::GRAPH_FTABLE && max != min) {
1476         double yrange = max - min;
1477         double factor = 1.17;
1478         view->setSceneRect(0, -max*factor - 0.05, sizef, yrange*factor);
1479         view->fitInView(0, -max*factor/zoomy, sizef/zoomx, yrange*factor/zoomy);
1480     } else if(graphType == GraphType::GRAPH_SPECTRUM) {
1481         double dbRange = m_dbRange;
1482         view->setSceneRect (0, -3, size, dbRange);
1483         view->fitInView(0, -3/zoomy, sizef/zoomx, dbRange/zoomy);
1484     } else { //from display opcode
1485         view->setSceneRect (0, -1, size, 2);
1486         // view->fitInView(0, -10./zoomy, (double) size/zoomx, 10./zoomy);
1487         view->fitInView(0, -2./zoomy, sizef/zoomx, 2./zoomy);
1488 	}
1489 
1490 }
1491 
getTableNumForIndex(int index)1492 int QuteGraph::getTableNumForIndex(int index) {
1493     if (index < 0 || index >= curves.size() || curves.size() <= 0) {
1494         // Invalid index
1495 		return -1;
1496 	}
1497 	int ftable = -1;
1498     if(graphtypes[index] == GraphType::GRAPH_FTABLE) {
1499         QString caption = curves[index]->get_caption();
1500 		ftable= caption.mid(caption.indexOf(" ") + 1,
1501 							caption.indexOf(":") - caption.indexOf(" ") - 1).toInt();
1502 	}
1503     return ftable;
1504 }
1505 
getIndexForTableNum(int ftable)1506 int QuteGraph::getIndexForTableNum(int ftable)
1507 {
1508 	int index = -1;
1509 	for (int i = 0; i < curves.size(); i++) {
1510 		QString text = curves[i]->get_caption();
1511 		if (text.contains("ftable")) {
1512 			QStringList parts = text.split(QRegExp("[ :]"), QString::SkipEmptyParts);
1513             if (parts.size() > 1) {
1514 				int num = parts.last().toInt();
1515 				if (ftable == num) {
1516 					index = i;
1517 					break;
1518 				}
1519 			}
1520 		}
1521 	}
1522 	return index;
1523 }
1524 
setInternalValue(double value)1525 void QuteGraph::setInternalValue(double value)
1526 {
1527 	m_value = value;
1528 	m_valueChanged = true;
1529 }
1530 
showScrollbars(bool show)1531 void QuteGraph::showScrollbars(bool show) {
1532     auto policy = show ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff;
1533     for(int i=0; i < this->curves.size(); i++) {
1534         auto view = getView(i);
1535         view->setHorizontalScrollBarPolicy(policy);
1536     }
1537     m_showScrollbars = show;
1538 }
1539 
1540 // ----------------------
~QuteTableWidget()1541 QuteTableWidget::~QuteTableWidget() {
1542 };
1543 
reset()1544 void QuteTableWidget::reset() {
1545     QMutexLocker locker(&mutex);
1546     m_tabnum = 0;
1547     m_running = false;
1548     m_data = nullptr;
1549     m_tabsize = 0;
1550     if(m_autorange)
1551         m_maxy = 1.0;
1552     if(m_path != nullptr) {
1553         delete m_path;
1554         m_path = nullptr;
1555     }
1556 }
1557 
paintGrid(QPainter * painter)1558 void QuteTableWidget::paintGrid(QPainter *painter) {
1559     int margin = m_margin;
1560     QString maxystr;
1561     if(m_maxy >= 10)
1562         maxystr = QString::number((int)m_maxy);
1563     else if(m_maxy >= 1)
1564         maxystr = QString::number(m_maxy, 'f', 1);
1565     else
1566         maxystr = QString::number(m_maxy, 'f', 2);
1567     auto rect = this->rect();
1568     const int yoffset = 0;
1569     auto x0 = rect.x() + margin;
1570     auto x1 = rect.x() + rect.width() - margin + 1;
1571     auto y0 = rect.y() + margin + yoffset;
1572     auto y1 = rect.y() + rect.height() - margin + yoffset ;
1573     auto ycenter = (y0+y1) / 2;
1574 
1575     auto font = QFont({"Sans", 8});
1576     QFontMetrics fm(font);
1577     auto tabsizestr = QString::number(m_tabsize);
1578     const int textMargin = 4;
1579     painter->setBrush(Qt::NoBrush);
1580     painter->setPen(QPen(QColor(96, 96, 96), 0));
1581     painter->drawLine(rect.x() + fm.boundingRect(maxystr).width()+textMargin*2, y0, x1, y0);
1582     painter->drawLine(x0, ycenter, x1, ycenter);
1583     painter->drawLine(x0, y1, x1 - fm.boundingRect(tabsizestr).width() - 2, y1);
1584     painter->setPen(QColor(48, 48, 48));
1585     painter->drawLine(x0, (y0+ycenter)/2, x1, (y0+ycenter)/2);
1586     painter->drawLine(x0, (y1+ycenter)/2, x1, (y1+ycenter)/2);
1587 
1588     painter->setPen(QColor(200, 200, 200));
1589     painter->setFont(font);
1590     painter->drawText(rect.x()+textMargin, y0+textMargin, maxystr);
1591     // right padding
1592     rect.setWidth(rect.width() - textMargin);
1593     painter->drawText(rect, Qt::AlignRight|Qt::AlignBottom, tabsizestr);
1594 
1595 }
1596 
paintEvent(QPaintEvent * event)1597 void QuteTableWidget::paintEvent(QPaintEvent *event) {
1598     QPainter painter(this);
1599     painter.setRenderHint(QPainter::Antialiasing);
1600     painter.setPen(Qt::NoPen);
1601     bool running = csoundEngineStatus(m_ud) == CsoundEngineStatus::Running;
1602     if(!running) {
1603         painter.setBrush(QColor(176, 0, 32));
1604         painter.drawRect(this->rect());
1605         painter.setPen(Qt::white);
1606         painter.drawText(this->rect(), Qt::AlignCenter, "Stopped");
1607         return;
1608     }
1609     if(m_tabnum <= 0) {
1610         // table not set
1611         painter.setBrush(QColor(0, 176, 32));
1612         painter.drawRect(this->rect());
1613         painter.setPen(Qt::white);
1614         painter.drawText(this->rect(), Qt::AlignCenter, "Table not set");
1615         return;
1616     }
1617     // running OK
1618     painter.setBrush(QColor(24, 24, 24));
1619     painter.drawRect(this->rect());
1620     painter.setBrush(Qt::NoBrush);
1621     if(m_path == nullptr)
1622         return;
1623     mutex.lock();
1624     if(m_showGrid) {
1625         this->paintGrid(&painter);
1626     }
1627     painter.setPen(QPen(m_color, 0));
1628     painter.drawPath(*m_path);
1629 
1630     mutex.unlock();
1631 }
1632 
setRange(double maxy)1633 void QuteTableWidget::setRange(double maxy) {
1634     if(maxy == 0.0) {
1635         m_autorange = true;
1636     } else {
1637         m_autorange = false;
1638         m_maxy = maxy;
1639     }
1640 }
1641 
updatePath()1642 void QuteTableWidget::updatePath() {
1643     if(m_tabnum <= 0 && m_data == nullptr)
1644         return;
1645     if(csoundEngineStatus(m_ud) != CsoundEngineStatus::Running)
1646         return;
1647     int margin = m_margin;
1648     auto rect = this->rect();
1649     auto width = rect.width() - margin*2;
1650     auto height = rect.height() - margin*2;
1651     double maxy = this->m_maxy;
1652     double newmaxy = maxy;
1653     double newminy = -maxy;
1654     double xscale = width / (double)m_tabsize;
1655     double yscale = height * 0.5 / maxy;
1656     double y0 = rect.y() + margin;
1657     double x0 = rect.x() + margin;
1658     int step = m_tabsize / width;
1659     if(step == 0)
1660         step = 1;
1661 
1662     auto path = new QPainterPath();
1663     double ydata = m_data[0];
1664     path->moveTo(x0, (ydata+maxy)*yscale+y0);
1665     for(int i=0; i < m_tabsize; i+=step) {
1666         ydata = -m_data[i];
1667         double x2 = i*xscale + x0;
1668         double y2 = (ydata+maxy)*yscale + y0;
1669         path->lineTo(x2, y2);
1670         if(ydata > newmaxy)
1671             newmaxy = ydata;
1672         else if (ydata < newminy)
1673             newminy = ydata;
1674     }
1675     mutex.lock();
1676     if(m_autorange) {
1677         m_maxy = newmaxy > -newminy ? ceil(newmaxy) : floor(-newminy);
1678     }
1679     if(m_path != nullptr)
1680         delete m_path;
1681     m_path = path;
1682     mutex.unlock();
1683 }
1684 
updateData(int tabnum,bool check)1685 void QuteTableWidget::updateData(int tabnum, bool check) {
1686     if(check && csoundEngineStatus(m_ud) != CsoundEngineStatus::Running) {
1687         m_tabnum = 0;
1688         return;
1689     }
1690     if(tabnum == -1 || tabnum == m_tabnum ) {
1691         if(m_tabnum == 0 || m_data == nullptr || m_tabsize == 0) {
1692             qDebug() << "Table not set, can't update";
1693             return;
1694         }
1695         this->updatePath();
1696         this->update();
1697         return;
1698     }
1699     // Asked to change table ( or set for the first time )
1700     MYFLT *data;
1701     int tabsize = csoundGetTable(m_ud->csound, &data, tabnum);
1702     if(tabsize == 0 || data == nullptr) {
1703         qDebug() << "Table" << tabnum << "not found";
1704         this->reset();
1705         return;
1706     }
1707     mutex.lock();
1708     m_data = data;
1709     m_tabsize = tabsize;
1710     if(m_autorange && m_tabnum != tabnum) {
1711         m_maxy = 1.0;
1712     }
1713     m_tabnum = tabnum;
1714     mutex.unlock();
1715     this->updatePath();
1716     this->update();
1717 }
1718 
1719 
1720 // -------------------------
1721 
~QuteTable()1722 QuteTable::~QuteTable() {};
1723 
1724 // void QuteTable::mousePressEvent(QMouseEvent *event) {};
1725 // void QuteTable::mouseReleaseEvent(QMouseEvent *event) {};
1726 
QuteTable(QWidget * parent)1727 QuteTable::QuteTable(QWidget *parent) : QuteWidget(parent) {
1728     m_widget = new QuteTableWidget(this);
1729     m_value = 0;
1730     m_tabnum = 0;
1731     // auto w = static_cast<QuteTableWidget*>(m_widget);
1732     setProperty("QCS_randomizable", false);
1733     m_widget->setContextMenuPolicy(Qt::NoContextMenu);
1734     m_widget->setMouseTracking(true);
1735     setProperty("QCS_range", 0.0);
1736     setColor(QColor(255, 193, 3));
1737     setProperty("QCS_showGrid", true);
1738 }
1739 
setColor(QColor color)1740 void QuteTable::setColor(QColor color) {
1741     setProperty("QCS_color", color);
1742     static_cast<QuteTableWidget*>(m_widget)->setColor(color);
1743 }
1744 
applyInternalProperties()1745 void QuteTable::applyInternalProperties() {
1746     QuteWidget::applyInternalProperties();
1747     auto w = static_cast<QuteTableWidget*>(m_widget);
1748     auto color = property("QCS_color").value<QColor>();
1749     w->setColor(color);
1750     w->setRange(property("QCS_range").toDouble());
1751     w->showGrid(property("QCS_showGrid").toBool());
1752 }
1753 
createPropertiesDialog()1754 void QuteTable::createPropertiesDialog() {
1755     QuteWidget::createPropertiesDialog();
1756     dialog->setWindowTitle("Table Plot");
1757     auto label = new QLabel(dialog);
1758     label->setText("Color");
1759     layout->addWidget(label, 4, 0, Qt::AlignRight|Qt::AlignVCenter);
1760 
1761     colorButton = new SelectColorButton(dialog);
1762     colorButton->setColor(property("QCS_color").value<QColor>());
1763     layout->addWidget(colorButton, 4, 1, Qt::AlignLeft|Qt::AlignVCenter);
1764 
1765     double range = property("QCS_range").toDouble();
1766     rangeCheckBox = new QCheckBox("Fixed Range", dialog);
1767     rangeCheckBox->setToolTip("If checked the range is fixed to the value set on the right."
1768                               "If the table has values outside this range these will not be"
1769                               "displayed");
1770     rangeCheckBox->setChecked(range > 0.0);
1771     layout->addWidget(rangeCheckBox, 5, 1, Qt::AlignRight|Qt::AlignVCenter);
1772 
1773     rangeSpinBox = new QDoubleSpinBox(dialog);
1774     rangeSpinBox->setRange(0.1, 999999.0);
1775     rangeSpinBox->setSingleStep(0.1);
1776     rangeSpinBox->setToolTip("Sets the max. range to plot. Disable this to use autorange"
1777                              "With a fixed range, values outside the range will not be "
1778                              "visiable");
1779     rangeSpinBox->setValue(range > 0 ? range : 1.0);
1780     layout->addWidget(rangeSpinBox, 5, 2, Qt::AlignLeft|Qt::AlignVCenter);
1781     connect(rangeCheckBox, SIGNAL(toggled(bool)), rangeSpinBox, SLOT(setEnabled(bool)));
1782 
1783     gridCheckBox = new QCheckBox("Show Grid", dialog);
1784     gridCheckBox->setChecked(property("QCS_showGrid").toBool());
1785     layout->addWidget(gridCheckBox, 6, 1, Qt::AlignLeft|Qt::AlignVCenter);
1786 
1787 }
1788 
applyProperties()1789 void QuteTable::applyProperties() {
1790 #ifdef  USE_WIDGET_MUTEX
1791     widgetLock.lockForWrite();
1792 #endif
1793     setProperty("QCS_color", colorButton->getColor());
1794     bool fixedrange = rangeCheckBox->isChecked();
1795     double range = rangeSpinBox->value();
1796     if(fixedrange) {
1797         Q_ASSERT(range > 0.0);
1798         setProperty("QCS_range", range);
1799     } else {
1800         setProperty("QCS_range", 0.0);
1801     }
1802     setProperty("QCS_showGrid", gridCheckBox->isChecked());
1803 
1804 #ifdef  USE_WIDGET_MUTEX
1805     widgetLock.unlock();
1806 #endif
1807     QuteWidget::applyProperties();
1808 }
1809 
refreshWidget()1810 void QuteTable::refreshWidget() {
1811     Q_ASSERT(m_valueChanged);
1812     QMutexLocker locker(&mutex);
1813     m_valueChanged = false;
1814     if(csoundEngineStatus(m_csoundUserData) != CsoundEngineStatus::Running)
1815         return;
1816     auto w = static_cast<QuteTableWidget*>(m_widget);
1817     int tabnum = (int)m_value;
1818     w->blockSignals(true);
1819     w->updateData(tabnum, true);
1820     w->blockSignals(false);
1821 }
1822 
getWidgetXmlText()1823 QString QuteTable::getWidgetXmlText() {
1824     xmlText = "";
1825     QXmlStreamWriter s(&xmlText);
1826     createXmlWriter(s);        // --------- start
1827 
1828     QColor color = property("QCS_color").value<QColor>();
1829     s.writeStartElement("color");
1830     s.writeTextElement("r", QString::number(color.red()));
1831     s.writeTextElement("g", QString::number(color.green()));
1832     s.writeTextElement("b", QString::number(color.blue()));
1833     s.writeEndElement();
1834 
1835     auto range = property("QCS_range").toDouble();
1836     s.writeTextElement("range", QString::number(range, 'f', 2));
1837 
1838     s.writeEndElement();      // --------- end
1839     return xmlText;
1840 }
1841 
onStop()1842 void QuteTable::onStop() {
1843     mutex.lock();
1844     this->blockSignals(true);
1845     m_tabnum = 0;
1846     m_value = 0;
1847     m_valueChanged = true;
1848 
1849     auto w = static_cast<QuteTableWidget*>(m_widget);
1850     w->setUserData(m_csoundUserData);
1851     w->blockSignals(true);
1852     w->reset();
1853     w->blockSignals(false);
1854     // this->applyInternalProperties();
1855     this->blockSignals(false);
1856     mutex.unlock();
1857 }
1858 
setCsoundUserData(CsoundUserData * ud)1859 void QuteTable::setCsoundUserData(CsoundUserData *ud) {
1860     if(ud == nullptr) {
1861         qDebug()<<"CsoundUserData is null";
1862         return;
1863     }
1864     QMutexLocker locker(&mutex);
1865     m_csoundUserData = ud;
1866     auto w = static_cast<QuteTableWidget*>(m_widget);
1867     if(w == nullptr) {
1868         qDebug() << "widget is null";
1869         return;
1870     }
1871     w->setUserData(ud);
1872     connect(ud->csEngine, SIGNAL(stopSignal()), this, SLOT(onStop()));
1873 }
1874 
1875 /*
1876 void QuteTable::setWidgetGeometry(int x, int y, int width, int height) {
1877 
1878     if(csoundEngineStatus(m_csoundUserData)==CsoundEngineStatus::Running) {
1879         return;
1880     }
1881     QuteWidget::setWidgetGeometry(x,y,width, height);
1882 }
1883 */
1884 
setValue(double value)1885 void QuteTable::setValue(double value) {
1886     if(m_value == value) {
1887         return;
1888     }
1889 
1890 #ifdef USE_WIDGET_MUTEX
1891     widgetLock.lockForWrite();
1892 #endif
1893     mutex.lock();
1894     if(value == -1 && m_tabnum > 0) {
1895         // update data, don't change table number
1896         m_valueChanged = false;
1897         auto w = static_cast<QuteTableWidget*>(m_widget);
1898         w->blockSignals(true);
1899         w->updateData(m_tabnum);
1900         w->blockSignals(false);
1901         mutex.unlock();
1902         return;
1903     }
1904     m_value = value;
1905     m_valueChanged = true;
1906     m_tabnum = (int)value;
1907     mutex.unlock();
1908     static_cast<QuteTableWidget*>(m_widget)->updateData(m_tabnum);
1909 
1910 #ifdef USE_WIDGET_MUTEX
1911     widgetLock.unlock();
1912 #endif
1913 
1914 };
1915 
setValue(QString s)1916 void QuteTable::setValue(QString s) {
1917     if(s.isEmpty())
1918         return;
1919     auto parts = s.splitRef(' ', QString::SkipEmptyParts);
1920     if(parts.size() == 0)
1921         return;
1922     if(parts[0] == "@set") {
1923         if(parts.size() != 2) {
1924             qDebug() << "@set message expects a table number (example: @set 103)";
1925             return;
1926         }
1927         int tabnum = parts[1].toInt();
1928         setValue((double)tabnum);
1929     } else if (parts[0] == "@update") {
1930         setValue(-1);
1931     } else
1932         qDebug() << "Message not supported:" << s;
1933 }
1934 
~SpectralView()1935 SpectralView::~SpectralView() {};
1936 
mouseReleaseEvent(QMouseEvent * ev)1937 void SpectralView::mouseReleaseEvent(QMouseEvent *ev) {
1938     emit mouseReleased();
1939     QGraphicsView::mouseReleaseEvent(ev);
1940 }
1941