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 "qutescope.h"
24 #include <cmath>
25 #include "types.h" //necessary for the userdata struct
26 #include "qutecsound.h" //necessary for the userdata struct
27
28
QuteScope(QWidget * parent)29 QuteScope::QuteScope(QWidget *parent) : QuteWidget(parent)
30 {
31 QGraphicsScene *m_scene = new QGraphicsScene(this);
32 m_scene->setBackgroundBrush(QBrush(Qt::black));
33 m_widget = new ScopeWidget(this);
34 m_widget->show();
35 m_widget->setAutoFillBackground(true);
36 m_widget->setContextMenuPolicy(Qt::NoContextMenu);
37 // Necessary to pass mouse tracking to widget panel for _MouseX channels
38 m_widget->setMouseTracking(true);
39 // m_widget->setWindowFlags(Qt::WindowStaysOnTopHint);
40 canFocus(false);
41 static_cast<ScopeWidget *>(m_widget)->setScene(m_scene);
42 static_cast<ScopeWidget *>(m_widget)->setResizeAnchor(QGraphicsView::AnchorViewCenter);
43 // static_cast<ScopeWidget *>(m_widget)->setRenderHints(QPainter::Antialiasing);
44 m_label = new QLabel(this);
45 QPalette palette = m_widget->palette();
46 palette.setColor(QPalette::WindowText, QColor(196, 196, 196));
47 m_label->setPalette(palette);
48 auto font = m_label->font();
49 font.setPixelSize(11);
50 m_label->setFont(font);
51 m_label->setText("Scope");
52 m_label->move(10, 0);
53 m_label->resize(500, 25);
54
55 m_params = new ScopeParams(nullptr,
56 m_scene,
57 static_cast<ScopeWidget *>(m_widget),
58 &scopeLock,
59 this->width(),
60 this->height());
61
62 m_scopeData = new ScopeData(m_params);
63 m_lissajouData = new LissajouData(m_params);
64 m_poincareData = new PoincareData(m_params);
65
66 m_dataDisplay = (DataDisplay *)m_scopeData;
67 m_dataDisplay->show();
68
69 // Default properties
70 setProperty("QCS_type", "scope");
71 setProperty("QCS_zoomx", 1.0);
72 setProperty("QCS_zoomy", 1.0);
73 setProperty("QCS_dispx", 1.0);
74 setProperty("QCS_dispy", 1.0);
75 setProperty("QCS_mode", "lin");
76 }
77
~QuteScope()78 QuteScope::~QuteScope()
79 {
80 delete m_poincareData;
81 delete m_lissajouData;
82 delete m_scopeData;
83 delete m_params;
84 }
85
getWidgetLine()86 QString QuteScope::getWidgetLine()
87 {
88 #ifdef USE_WIDGET_MUTEX
89 widgetLock.lockForRead();
90 #endif
91 QString line = "ioGraph {" + QString::number(x()) + ", " + QString::number(y()) + "} ";
92 line += "{"+ QString::number(width()) +", "+ QString::number(height()) +"} ";
93 line += property("QCS_type").toString() + " " + QString::number(property("QCS_zoomx").toDouble(), 'f', 6) + " ";
94 line += QString::number((int) m_value) + " ";
95 line += m_channel;
96 // qDebug("QuteScope::getWidgetLine() %s", line.toStdString().c_str());
97 #ifdef USE_WIDGET_MUTEX
98 widgetLock.unlock();
99 #endif
100 return line;
101 }
102
getWidgetXmlText()103 QString QuteScope::getWidgetXmlText()
104 {
105 xmlText = "";
106 QXmlStreamWriter s(&xmlText);
107 createXmlWriter(s);
108 #ifdef USE_WIDGET_MUTEX
109 widgetLock.lockForRead();
110 #endif
111
112 s.writeTextElement("value", QString::number(m_value, 'f', 8));
113 s.writeTextElement("type", property("QCS_type").toString());
114 s.writeTextElement("zoomx", QString::number(property("QCS_zoomx").toDouble(), 'f', 8));
115 s.writeTextElement("zoomy", QString::number(property("QCS_zoomy").toDouble(), 'f', 8));
116 s.writeTextElement("dispx", QString::number(property("QCS_dispx").toDouble(), 'f', 8));
117 s.writeTextElement("dispy", QString::number(property("QCS_dispy").toDouble(), 'f', 8));
118 s.writeTextElement("mode", QString::number(property("QCS_mode").toDouble(), 'f', 8));
119
120 s.writeEndElement();
121 #ifdef USE_WIDGET_MUTEX
122 widgetLock.unlock();
123 #endif
124 return xmlText;
125 }
126
getWidgetType()127 QString QuteScope::getWidgetType()
128 {
129 return QString("BSBScope");
130 }
131
setType(QString type)132 void QuteScope::setType(QString type)
133 {
134 updateLabel();
135 m_dataDisplay->hide();
136 if (type == "scope") {
137 m_dataDisplay = (DataDisplay *)m_scopeData;
138 }
139 else if (type == "lissajou") {
140 m_dataDisplay = (DataDisplay *)m_lissajouData;
141 }
142 else if (type == "poincare") {
143 m_dataDisplay = (DataDisplay *)m_poincareData;
144 }
145 m_dataDisplay->show();
146 }
147
setValue(double value)148 void QuteScope::setValue(double value)
149 {
150 QuteWidget::setValue(value);
151 updateLabel();
152 }
153
setUd(CsoundUserData * ud)154 void QuteScope::setUd(CsoundUserData *ud)
155 {
156 m_params->ud = ud;
157 }
158
updateLabel()159 void QuteScope::updateLabel()
160 {
161 QString chan;
162 if ((int) m_value < 0) {
163 chan = tr("all", "meaning 'all' channels in scope, 4 letter max");
164 }
165 else if ((int) m_value <= 0) {
166 chan = tr("None", "meaning 'no' channels in scope, 4 letter max");
167 }
168 else {
169 chan = QString::number((int) m_value );
170 }
171 m_label->setText(tr("Scope ch:") + chan);
172 }
173
applyInternalProperties()174 void QuteScope::applyInternalProperties()
175 {
176 QuteWidget::applyInternalProperties();
177 setType(property("QCS_type").toString());
178 setValue(property("QCS_value").toDouble());
179 }
180
createPropertiesDialog()181 void QuteScope::createPropertiesDialog()
182 {
183 QuteWidget::createPropertiesDialog();
184 dialog->setWindowTitle("Scope");
185 // channelLabel->hide();
186 // nameLineEdit->hide();
187 QLabel *label = new QLabel(dialog);
188 label->setText("Type");
189 layout->addWidget(label, 6, 0, Qt::AlignRight|Qt::AlignVCenter);
190 typeComboBox = new QComboBox(dialog);
191 typeComboBox->addItem("Oscilloscope", QVariant(QString("scope")));
192 typeComboBox->addItem("Lissajou curve", QVariant(QString("lissajou")));
193 typeComboBox->addItem("Poincare map", QVariant(QString("poincare")));
194 // typeComboBox->addItem("Spectrogram", QVariant(QString("fft")));
195 layout->addWidget(typeComboBox, 6, 1, Qt::AlignLeft|Qt::AlignVCenter);
196 label = new QLabel(dialog);
197 label->setText("Channel");
198 layout->addWidget(label, 6, 2, Qt::AlignRight|Qt::AlignVCenter);
199 channelBox = new QComboBox(dialog);
200 channelBox->addItem("all", QVariant((int) -255));
201 channelBox->addItem("Ch.1",QVariant((int) 1));
202 channelBox->addItem("Ch.2",QVariant((int) 2));
203 channelBox->addItem("Ch.3",QVariant((int) 3));
204 channelBox->addItem("Ch.4",QVariant((int) 4));
205 channelBox->addItem("Ch.5",QVariant((int) 5));
206 channelBox->addItem("Ch.6",QVariant((int) 6));
207 channelBox->addItem("Ch.7",QVariant((int) 7));
208 channelBox->addItem("Ch.8",QVariant((int) 8));
209 channelBox->addItem("none", QVariant((int) 0));
210 layout->addWidget(channelBox, 6, 3, Qt::AlignLeft|Qt::AlignVCenter);
211 label = new QLabel(dialog);
212 label->setText("Zoom X");
213 layout->addWidget(label, 8, 0, Qt::AlignRight|Qt::AlignVCenter);
214 zoomxBox = new QDoubleSpinBox(dialog);
215 zoomxBox->setRange(1, 20);
216 layout->addWidget(zoomxBox, 8, 1, Qt::AlignLeft|Qt::AlignVCenter);
217 label = new QLabel(dialog);
218 label->setText("Zoom Y");
219 layout->addWidget(label, 8, 2, Qt::AlignRight|Qt::AlignVCenter);
220 zoomyBox = new QDoubleSpinBox(dialog);
221 zoomyBox->setRange(1, 20);
222 layout->addWidget(zoomyBox, 8, 3, Qt::AlignLeft|Qt::AlignVCenter);
223 #ifdef USE_WIDGET_MUTEX
224 widgetLock.lockForRead();
225 #endif
226 typeComboBox->setCurrentIndex(typeComboBox->findData(QVariant(property("QCS_type").toString())));
227 channelBox->setCurrentIndex(channelBox->findData(QVariant((int) m_value)));
228 zoomxBox->setValue(property("QCS_zoomx").toDouble());
229 zoomyBox->setValue(property("QCS_zoomy").toDouble());
230 #ifdef USE_WIDGET_MUTEX
231 widgetLock.unlock();
232 #endif
233 }
234
applyProperties()235 void QuteScope::applyProperties()
236 {
237 #ifdef USE_WIDGET_MUTEX
238 widgetLock.lockForRead();
239 #endif
240 setProperty("QCS_type", typeComboBox->itemData(typeComboBox->currentIndex()).toString());
241 setProperty("QCS_zoomx", zoomxBox->value());
242 setProperty("QCS_zoomy", zoomyBox->value());
243 setProperty("QCS_value", channelBox->itemData(channelBox->currentIndex()).toInt());
244 #ifdef USE_WIDGET_MUTEX
245 widgetLock.unlock();
246 #endif
247 //Must be last to make sure the widgetChanged signal is last
248 QuteWidget::applyProperties();
249 }
250
resizeEvent(QResizeEvent * event)251 void QuteScope::resizeEvent(QResizeEvent * event)
252 {
253 QuteWidget::resizeEvent(event);
254 m_params->setWidth(this->width());
255 m_params->setHeight(this->height());
256 m_scopeData->resize();
257 m_lissajouData->resize();
258 m_poincareData->resize();
259 // QGraphicsScene *m_scene = static_cast<ScopeWidget *>(m_widget)->scene();
260 // m_scene->setSceneRect(-m_ud->zerodBFS, m_ud->zerodBFS, width() - 5, m_ud->zerodBFS *2);
261 // static_cast<ScopeWidget *>(m_widget)->setSceneRect(-m_ud->zerodBFS , m_ud->zerodBFS, width() - 5, m_ud->zerodBFS *2);
262 }
263
updateData()264 void QuteScope::updateData()
265 {
266 m_dataDisplay->updateData((int) m_value,
267 property("QCS_zoomx").toDouble(),
268 property("QCS_zoomy").toDouble(),
269 static_cast<ScopeWidget *>(m_widget)->freeze);
270 }
271
ScopeItem(int width,int height)272 ScopeItem::ScopeItem(int width, int height)
273 {
274 m_width = width;
275 m_height = height;
276 }
277
paint(QPainter * p,const QStyleOptionGraphicsItem *,QWidget *)278 void ScopeItem::paint(QPainter *p,
279 const QStyleOptionGraphicsItem */*option*/,
280 QWidget */*widget*/)
281 {
282 p->setPen(m_pen);
283 p->drawPoints(m_polygon);
284 }
285
setPen(const QPen & pen)286 void ScopeItem::setPen(const QPen & pen)
287 {
288 m_pen = pen;
289 }
290
setPolygon(const QPolygonF & polygon)291 void ScopeItem::setPolygon(const QPolygonF & polygon)
292 {
293 m_polygon = polygon;
294 update(boundingRect());
295 }
296
setSize(int width,int height)297 void ScopeItem::setSize(int width, int height)
298 {
299 m_width = width;
300 m_height = height;
301 prepareGeometryChange();
302 }
303
ScopeData(ScopeParams * params)304 ScopeData::ScopeData(ScopeParams *params) : DataDisplay(params)
305 {
306 curveData.resize(m_params->width + 2);
307 curve = new QGraphicsPolygonItem(/*&curveData*/);
308 curve->setPen(QPen(Qt::green, 0));
309 curve->hide();
310 m_params->scene->addItem(curve);
311 }
312
resize()313 void ScopeData::resize()
314 {
315 curveData.resize(m_params->width + 2);
316 }
317
updateData(int channel,double zoomx,double zoomy,bool freeze)318 void ScopeData::updateData(int channel, double zoomx, double zoomy, bool freeze)
319 {
320 CsoundUserData *ud = m_params->ud;
321 int width = m_params->width;
322 int height = m_params->height;
323 if (ud == 0 || !ud->csEngine->isRunning() )
324 return;
325 if (freeze)
326 return;
327 double value;
328 int numChnls = ud->numChnls;
329 MYFLT newValue;
330 if (channel == 0 || channel > numChnls ) {
331 return;
332 }
333 channel = (channel < 0 ? -1: channel - 1);
334 #ifdef USE_WIDGET_MUTEX
335 //FIXME is this locking needed, or should a separate locking mechanism be implemented?
336 QReadWriteLock *mutex = m_params->mutex;
337 mutex->lockForWrite();
338 #endif
339 // FIXME how to make sure the buffer is read before it is flushed when recorded?
340 // Have another buffer?
341 RingBuffer *buffer = &ud->audioOutputBuffer;
342 buffer->lock();
343 QList<MYFLT> list = buffer->buffer;
344 buffer->unlock();
345 long listSize = list.size();
346 // long offset = buffer->currentPos;
347 long offset = buffer->currentReadPos;
348 for (int i = 0; i < width; i++) {
349 value = 0;
350 for (int j = 0; j < (int) zoomx; j++) {
351 if (channel == -1) {
352 // all channels
353 newValue = 0;
354 for (int k = 0; k < numChnls; k++) {
355 int bufIdx = (int)((((i*zoomx)+j)*numChnls) + offset + k) % listSize;
356 newValue += list[bufIdx];
357 }
358 newValue /= numChnls;
359 if (fabs(newValue) > fabs(value))
360 value = -(double) newValue;
361 }
362 else {
363 int bufIdx = (int)((((i*zoomx)+j)*numChnls) + offset + channel) % listSize;
364 if (fabs(list[bufIdx]) > fabs(value))
365 value = (double) -list[bufIdx];
366 }
367 }
368 curveData[i+1] = QPoint(i, zoomy*value*height/2);
369 }
370 buffer->currentReadPos += width;
371 m_params->widget->setSceneRect(0, -height/2, width, height );
372 curveData.last() = QPoint(width-4, 0);
373 curveData.first() = QPoint(0, 0);
374 curve->setPolygon(curveData);
375 #ifdef USE_WIDGET_MUTEX
376 mutex->unlock();
377 #endif
378 }
379
show()380 void ScopeData::show()
381 {
382 curve->show();
383 }
384
hide()385 void ScopeData::hide()
386 {
387 curve->hide();
388 }
389
LissajouData(ScopeParams * params)390 LissajouData::LissajouData(ScopeParams *params) : DataDisplay(params)
391 {
392 curveData.resize(m_params->width);
393 curve = new ScopeItem(m_params->width, m_params->height);
394 auto pen = QPen(Qt::green);
395 pen.setCosmetic(true);
396 curve->setPen(pen);
397 curve->hide();
398 m_params->scene->addItem(curve);
399 }
400
resize()401 void LissajouData::resize()
402 {
403 // We take 8 times the display width points for each pass
404 // to have a smooth animation
405 curveData.resize(m_params->width * 8);
406 curve->setSize(m_params->width, m_params->height);
407 }
408
updateData(int channel,double zoomx,double zoomy,bool freeze)409 void LissajouData::updateData(int channel, double zoomx, double zoomy, bool freeze)
410 {
411 // The decimation factor (zoom) is not used here
412 CsoundUserData *ud = m_params->ud;
413 int width = m_params->width;
414 int height = m_params->height;
415 if (ud == nullptr || !ud->csEngine->isRunning())
416 return;
417 if (freeze)
418 return;
419 double x, y;
420 int numChnls = ud->numChnls;
421 // We take two consecutives channels, the first one for abscissas and
422 // the second one for ordinates
423 if (channel == 0 || channel >= numChnls || numChnls < 2) {
424 return;
425 }
426 channel = (channel < 0 ? 0 : channel - 1);
427 #ifdef USE_WIDGET_MUTEX
428 QReadWriteLock *mutex = m_params->mutex;
429 mutex->lockForWrite();
430 #endif
431 RingBuffer *buffer = &ud->audioOutputBuffer;
432 buffer->lock();
433 QList<MYFLT> list = buffer->buffer;
434 buffer->unlock();
435 long listSize = list.size();
436 long offset = buffer->currentPos;
437 for (int i = 0; i < curveData.size(); i++) {
438 int bufferIndex = (int)((i*numChnls) + offset + channel) % listSize;
439 x = (double)list[bufferIndex];
440 bufferIndex = (bufferIndex + 1) % listSize;
441 y = (double) -list[bufferIndex];
442 curveData[i] = QPoint(x*width*zoomx/4, y*height*zoomy/4);
443 }
444 m_params->widget->setSceneRect(-width/2, -height/2, width, height );
445 curve->setPolygon(curveData);
446 #ifdef USE_WIDGET_MUTEX
447 mutex->unlock();
448 #endif
449 }
450
show()451 void LissajouData::show()
452 {
453 curve->show();
454 }
455
hide()456 void LissajouData::hide()
457 {
458 curve->hide();
459 }
460
PoincareData(ScopeParams * params)461 PoincareData::PoincareData(ScopeParams *params) : DataDisplay(params)
462 {
463 curveData.resize(m_params->width);
464 curve = new ScopeItem(m_params->width, m_params->height);
465 curve->setPen(QPen(Qt::green, 0));
466 curve->hide();
467 lastValue = 0.0;
468 m_params->scene->addItem(curve);
469 }
470
resize()471 void PoincareData::resize()
472 {
473 // We take 8 times the display width points for each pass
474 // to have a smooth animation
475 curveData.resize(m_params->width * 8);
476 curve->setSize(m_params->width, m_params->height);
477 }
478
updateData(int channel,double zoomx,double zoomy,bool freeze)479 void PoincareData::updateData(int channel, double zoomx, double zoomy, bool freeze)
480 {
481 CsoundUserData *ud = m_params->ud;
482 int width = m_params->width;
483 int height = m_params->height;
484 if (ud == 0 || !ud->csEngine->isRunning() )
485 return;
486 if (freeze)
487 return;
488 double value;
489 int numChnls = ud->numChnls;
490 if (channel == 0 || channel > numChnls) {
491 return;
492 }
493 channel = (channel < 0 ? 0 : channel - 1);
494 #ifdef USE_WIDGET_MUTEX
495 QReadWriteLock *mutex = m_params->mutex;
496 mutex->lockForWrite();
497 #endif
498 RingBuffer *buffer = &ud->audioOutputBuffer;
499 buffer->lock();
500 QList<MYFLT> list = buffer->buffer;
501 buffer->unlock();
502 long listSize = list.size();
503 long offset = buffer->currentPos;
504 for (int i = 0; i < curveData.size(); i++) {
505 int bufferIndex = (int)((i*zoomx*numChnls) + offset + channel) % listSize;
506 value = (double)list[bufferIndex];
507 curveData[i] = QPoint(lastValue*width*zoomx/2, -value*height*zoomy/2);
508 lastValue = value;
509 }
510 m_params->widget->setSceneRect(-width/2, -height/2, width, height );
511 curve->setPolygon(curveData);
512 #ifdef USE_WIDGET_MUTEX
513 mutex->unlock();
514 #endif
515 }
516
show()517 void PoincareData::show()
518 {
519 curve->show();
520 }
521
hide()522 void PoincareData::hide()
523 {
524 curve->hide();
525 }
526
527