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