1 /***************************************************************************
2 ** **
3 ** Polyphone, a soundfont editor **
4 ** Copyright (C) 2013-2020 Davy Triponney **
5 ** **
6 ** This program is free software: you can redistribute it and/or modify **
7 ** it under the terms of the GNU General Public License as published by **
8 ** the Free Software Foundation, either version 3 of the License, or **
9 ** (at your option) any later version. **
10 ** **
11 ** This program is distributed in the hope that it will be useful, **
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of **
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
14 ** GNU General Public License for more details. **
15 ** **
16 ** You should have received a copy of the GNU General Public License **
17 ** along with this program. If not, see http://www.gnu.org/licenses/. **
18 ** **
19 ****************************************************************************
20 ** Author: Davy Triponney **
21 ** Website/Contact: https://www.polyphone-soundfonts.com **
22 ** Date: 01.01.2013 **
23 ***************************************************************************/
24
25 #include "graphicswave.h"
26 #include "graphicswavepainter.h"
27 #include "contextmanager.h"
28 #include <QPainter>
29 #include "qmath.h"
30 #include <QScrollBar>
31 #include <QSpinBox>
32 #include <QApplication>
33 #include <QMouseEvent>
34 #include <QWheelEvent>
35
36 const int GraphicsWave::TEXT_MARGIN = 5;
37 const int GraphicsWave::OVERLAY_SIZE = 20;
38
GraphicsWave(QWidget * parent)39 GraphicsWave::GraphicsWave(QWidget *parent) : QWidget(parent),
40 _wavePainter(new GraphicsWavePainter(this)),
41 _zoomX(1),
42 _zoomY(1),
43 _posX(.5),
44 _zoomXinit(1),
45 _zoomYinit(1),
46 _posXinit(.5),
47 _zoomFlag(false),
48 _dragFlag(false),
49 _cutFlag(false),
50 _modifiedFlag(false),
51 _sampleSize(0),
52 _sampleRate(0),
53 _startLoop(0),
54 _endLoop(0),
55 _currentPosition(0),
56 _lastPositionUpdate(QDateTime::currentDateTime()),
57 _multipleSelection(false),
58 _qScrollX(nullptr),
59 _spinStart(nullptr),
60 _spinEnd(nullptr),
61 _bFromExt(false)
62 {
63 // Colors
64 _redColor = ContextManager::theme()->getFixedColor(ThemeManager::RED, true);
65 _greenColor = ContextManager::theme()->getFixedColor(ThemeManager::GREEN, true);
66 if (ContextManager::theme()->isDark(ThemeManager::LIST_BACKGROUND, ThemeManager::LIST_TEXT))
67 {
68 _backgroundColor = ContextManager::theme()->getColor(ThemeManager::LIST_BACKGROUND);
69 _textColor = ContextManager::theme()->getColor(ThemeManager::LIST_TEXT);
70 }
71 else
72 {
73 _backgroundColor = ContextManager::theme()->getColor(ThemeManager::LIST_TEXT);
74 _textColor = ContextManager::theme()->getColor(ThemeManager::LIST_BACKGROUND);
75 }
76 _textColor.setAlpha(180);
77
78 // Font
79 _textFont = this->font();
80 _textFont.setBold(true);
81 }
82
~GraphicsWave()83 GraphicsWave::~GraphicsWave()
84 {
85 delete _wavePainter;
86 }
87
setData(QByteArray baData,quint32 sampleRate)88 void GraphicsWave::setData(QByteArray baData, quint32 sampleRate)
89 {
90 // Reset zoom & drag
91 _zoomX = 1;
92 _zoomY = 1;
93 _posX = 0.5;
94
95 // Set data
96 _wavePainter->setData(baData);
97 _sizeX = 0.5 * baData.size() - 1;
98
99 // Save the sample rate
100 _sampleRate = sampleRate;
101 }
102
linkSliderX(QScrollBar * qScrollX)103 void GraphicsWave::linkSliderX(QScrollBar * qScrollX)
104 {
105 _qScrollX = qScrollX;
106 }
107
linkSpinBoxes(QSpinBox * spinStart,QSpinBox * spinEnd)108 void GraphicsWave::linkSpinBoxes(QSpinBox * spinStart, QSpinBox * spinEnd)
109 {
110 _spinStart = spinStart;
111 _spinEnd = spinEnd;
112 }
113
displayMultipleSelection(bool isOn)114 void GraphicsWave::displayMultipleSelection(bool isOn)
115 {
116 _qScrollX->setEnabled(!isOn);
117 _multipleSelection = isOn;
118 if (isOn)
119 _qScrollX->setRange(0, 0);
120 repaint();
121 }
122
setPosX(int posX)123 void GraphicsWave::setPosX(int posX)
124 {
125 if (this->_qScrollX)
126 {
127 _bFromExt = true;
128 if (_qScrollX->maximum() > 0)
129 _posX = static_cast<double>(posX) / this->_qScrollX->maximum();
130 else
131 _posX = 0.5;
132 this->repaint();
133 _bFromExt = false;
134 }
135 }
136
setStartLoop(int pos,bool repaint)137 void GraphicsWave::setStartLoop(int pos, bool repaint)
138 {
139 if (_multipleSelection)
140 return;
141
142 _startLoop = static_cast<quint32>(pos);
143 if (repaint)
144 this->repaint();
145 }
146
setEndLoop(int pos,bool repaint)147 void GraphicsWave::setEndLoop(int pos, bool repaint)
148 {
149 if (_multipleSelection)
150 return;
151
152 _endLoop = static_cast<quint32>(pos);
153 if (repaint)
154 this->repaint();
155 }
156
setCurrentSample(quint32 pos)157 void GraphicsWave::setCurrentSample(quint32 pos)
158 {
159 QDateTime now = QDateTime::currentDateTime();
160 if (_lastPositionUpdate.msecsTo(now) < 16 && pos != 0)
161 return;
162
163 _lastPositionUpdate = now;
164 _currentPosition = pos;
165 repaint();
166 }
167
paintEvent(QPaintEvent * event)168 void GraphicsWave::paintEvent(QPaintEvent *event)
169 {
170 Q_UNUSED(event)
171
172 if (_multipleSelection)
173 {
174 QPainter painter(this);
175 painter.fillRect(this->rect(), _backgroundColor);
176 painter.setPen(_textColor);
177 QFont font = _textFont;
178 font.setPointSize(12);
179 painter.setFont(font);
180 painter.drawText(0, 0, this->width(), this->height(),
181 Qt::AlignCenter | Qt::AlignHCenter, tr("Multiple selection"));
182 return;
183 }
184
185 // Paint the waveform
186 double etendueX = _sizeX / _zoomX;
187 double offsetX = (_sizeX - etendueX) * _posX - 1;
188 quint32 start = offsetX < 0 ? 0 : static_cast<quint32>(offsetX);
189 quint32 end = offsetX + etendueX < 0 ? 0 : static_cast<quint32>(offsetX + etendueX);
190 if (start >= end)
191 return;
192 _wavePainter->paint(start, end, static_cast<float>(_zoomY));
193
194 // Left and right limits
195 QPainter painter(this); // Must be after _wavePainter->paint(...)
196 painter.setPen(_textColor);
197 painter.setFont(_textFont);
198 int fontHeight = 50;
199 painter.drawText(TEXT_MARGIN, this->height() - fontHeight - TEXT_MARGIN,
200 this->width() - TEXT_MARGIN * 2, fontHeight, Qt::AlignLeft | Qt::AlignBottom,
201 QString::number(static_cast<double>(start) / _sampleRate, 'f', 3) + " " + "s");
202 painter.drawText(TEXT_MARGIN, this->height() - fontHeight - TEXT_MARGIN,
203 this->width() - TEXT_MARGIN * 2, fontHeight, Qt::AlignRight | Qt::AlignBottom,
204 QString::number(static_cast<double>(end) / _sampleRate, 'f', 3) + " " + "s");
205
206 // Ajout du trait de lecture
207 double coeff = static_cast<double>(this->width()) / (end - start);
208 if (_currentPosition > 0)
209 {
210 painter.setPen(_textColor);
211 int pos = static_cast<int>(coeff * (_currentPosition - start));
212 painter.drawLine(pos, -1, pos, this->height() + 1);
213 }
214
215 // Display loop
216 if (_startLoop != _endLoop)
217 {
218 // Left vertical bar
219 painter.setPen(QPen(_greenColor, 2.0, Qt::SolidLine));
220 int pos = static_cast<int>(coeff * (_startLoop - start));
221 painter.drawLine(pos, -1, pos, this->height() + 1);
222
223 // Right vertical bar
224 painter.setPen(QPen(_redColor, 2.0, Qt::SolidLine));
225 pos = static_cast<int>(coeff * (_endLoop - start));
226 painter.drawLine(pos, -1, pos, this->height() + 1);
227
228 // Left overlay
229 painter.setRenderHint(QPainter::Antialiasing);
230 QColor color = _greenColor;
231 color.setAlpha(180);
232 painter.setPen(QPen(color, 2.0, Qt::DotLine));
233 quint32 pointNumber = 0;
234 QPointF * points =_wavePainter->getDataAround(_endLoop, OVERLAY_SIZE, pointNumber);
235 for (quint32 i = 0; i < pointNumber; i++)
236 points[i].setX(coeff * (points[i].x() - start - _endLoop + _startLoop));
237 painter.drawPolyline(points, static_cast<int>(pointNumber));
238 delete [] points;
239
240 // Right overlay
241 color = _redColor;
242 color.setAlpha(180);
243 painter.setPen(QPen(color, 2.0, Qt::DotLine));
244 points =_wavePainter->getDataAround(_startLoop, OVERLAY_SIZE, pointNumber);
245 for (quint32 i = 0; i < pointNumber; i++)
246 points[i].setX(coeff * (points[i].x() - start - _startLoop + _endLoop));
247 painter.drawPolyline(points, static_cast<int>(pointNumber));
248 delete [] points;
249 }
250
251 if (_cutFlag)
252 {
253 // Cut area
254 if (_x != _xInit || _y != _yInit)
255 {
256 painter.setRenderHint(QPainter::Antialiasing);
257 QColor color = _textColor;
258 color.setAlpha(100);
259 painter.setPen(color);
260 painter.drawRect(QRectF(qMin(_x, _xInit) * this->width(), -1,
261 qAbs(_x - _xInit) * this->width(), this->height() + 2));
262 painter.fillRect(QRectF(qMin(_x, _xInit) * this->width(), -1,
263 qAbs(_x - _xInit) * this->width(), this->height() + 2),
264 QBrush(color, Qt::BDiagPattern));
265 }
266 }
267 else if (_zoomFlag)
268 {
269 // Zoom line
270 if (_x != _xInit || _y != _yInit)
271 {
272 painter.setRenderHint(QPainter::Antialiasing);
273 painter.setPen(QPen(_redColor, 1.0, Qt::DashLine));
274 painter.drawLine(QPointF(static_cast<double>(_xInit * this->width()), static_cast<double>(_yInit * this->height())),
275 QPointF(static_cast<double>(_x * this->width()), static_cast<double>(_y * this->height())));
276 }
277 }
278
279 // Possibly update scrollbar
280 if (!_bFromExt && _qScrollX)
281 {
282 _qScrollX->blockSignals(true);
283 _qScrollX->setPageStep(static_cast<qint32>(10000 / _zoomX));
284 _qScrollX->setRange(0, static_cast<qint32>(10000. - _qScrollX->pageStep()));
285 _qScrollX->setValue(static_cast<qint32>(_qScrollX->maximum() * _posX));
286 _qScrollX->blockSignals(false);
287 }
288 }
289
mousePressEvent(QMouseEvent * event)290 void GraphicsWave::mousePressEvent(QMouseEvent *event)
291 {
292 if (_multipleSelection)
293 return;
294
295 if (event->button() == Qt::LeftButton && !_zoomFlag)
296 {
297 // Save the initial state
298 _xInit = static_cast<double>(event->x()) / this->width();
299 _yInit = static_cast<double>(event->y()) / this->height();
300 _zoomXinit = _zoomX;
301 _zoomYinit = _zoomY;
302 _posXinit = _posX;
303 _modifiedFlag = false;
304
305 if (QApplication::keyboardModifiers() == Qt::AltModifier)
306 _cutFlag = true;
307 else
308 _dragFlag = true;
309 }
310 else if (event->button() == Qt::RightButton && !_dragFlag && !_cutFlag)
311 {
312 // Save the initial state
313 _xInit = static_cast<double>(event->x()) / this->width();
314 _yInit = static_cast<double>(event->y()) / this->height();
315 _zoomXinit = _zoomX;
316 _zoomYinit = _zoomY;
317 _posXinit = _posX;
318 _modifiedFlag = false;
319
320 _zoomFlag = true;
321 }
322 }
323
mouseReleaseEvent(QMouseEvent * event)324 void GraphicsWave::mouseReleaseEvent(QMouseEvent *event)
325 {
326 if (_multipleSelection)
327 return;
328
329 int startSamplePosition = getSamplePosX(_zoomXinit, _posXinit, _xInit);
330
331 if (event->button() == Qt::LeftButton)
332 {
333 if (!_modifiedFlag)
334 {
335 // Modification start loop
336 if (this->_spinStart && this->_spinEnd)
337 {
338 if (this->_spinEnd->value() > startSamplePosition)
339 {
340 this->_spinEnd->setMinimum(startSamplePosition);
341 this->_spinStart->setValue(startSamplePosition);
342 emit(startLoopChanged());
343 }
344 }
345 else
346 this->setStartLoop(startSamplePosition);
347 }
348 else
349 {
350 if (_cutFlag)
351 {
352 int endSamplePosition = getSamplePosX(_zoomX, _posX, static_cast<double>(event->x()) / this->width());
353 if (startSamplePosition < endSamplePosition)
354 emit(cutOrdered(startSamplePosition, endSamplePosition));
355 else
356 emit(cutOrdered(endSamplePosition, startSamplePosition));
357 }
358 this->setCursor(Qt::ArrowCursor);
359 }
360
361 _dragFlag = false;
362 _cutFlag = false;
363 this->repaint();
364 }
365 else if (event->button() == Qt::RightButton)
366 {
367 if (!_modifiedFlag)
368 {
369 // Modification end loop
370 if (this->_spinStart && this->_spinEnd)
371 {
372 if (this->_spinStart->value() < startSamplePosition)
373 {
374 this->_spinStart->setMaximum(startSamplePosition);
375 this->_spinEnd->setValue(startSamplePosition);
376 emit(endLoopChanged());
377 }
378 }
379 else
380 this->setEndLoop(startSamplePosition);
381 }
382 else
383 {
384 // Stop the cut process
385 _cutFlag = false;
386
387 // Remove the zoom line
388 this->setCursor(Qt::ArrowCursor);
389 }
390
391 _zoomFlag = false;
392 this->repaint();
393 }
394 }
395
mouseMoveEvent(QMouseEvent * event)396 void GraphicsWave::mouseMoveEvent(QMouseEvent *event)
397 {
398 if (_multipleSelection)
399 return;
400
401 _x = static_cast<double>(event->x()) / this->width();
402 _y = static_cast<double>(event->y()) / this->height();
403
404 if (_zoomFlag)
405 {
406 if (!_modifiedFlag)
407 {
408 _modifiedFlag = true;
409 this->setCursor(Qt::SizeAllCursor);
410 }
411
412 this->zoom(event->pos());
413 }
414 else if (_dragFlag)
415 {
416 if (!_modifiedFlag)
417 {
418 _modifiedFlag = true;
419 this->setCursor(Qt::ClosedHandCursor);
420 }
421 this->drag(event->pos());
422 }
423 else if (_cutFlag)
424 {
425 if (!_modifiedFlag)
426 {
427 _modifiedFlag = true;
428 this->setCursor(Qt::UpArrowCursor);
429 }
430 this->repaint();
431 }
432 }
433
wheelEvent(QWheelEvent * event)434 void GraphicsWave::wheelEvent(QWheelEvent *event)
435 {
436 if (_multipleSelection)
437 return;
438
439 if (!_dragFlag && !_zoomFlag && !_cutFlag)
440 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
441 _qScrollX->setValue(static_cast<qint32>(_qScrollX->value() - 0.2 * event->angleDelta().x()));
442 #else
443 _qScrollX->setValue(static_cast<qint32>(_qScrollX->value() - 0.2 * event->delta()));
444 #endif
445 }
446
getSamplePosX(double zoomX,double shiftX,double x)447 int GraphicsWave::getSamplePosX(double zoomX, double shiftX, double x)
448 {
449 int pos = static_cast<int>(_sizeX / zoomX * ((zoomX - 1) * shiftX + x) - 1.0);
450 if (pos < 0)
451 pos = 0;
452 else if (pos > _sizeX)
453 pos = qRound(_sizeX - 1.0);
454 return pos;
455 }
456
zoom(QPoint point)457 void GraphicsWave::zoom(QPoint point)
458 {
459 // Shift
460 double decX = static_cast<double>(point.x()) / this->width() - this->_xInit;
461 double decY = this->_yInit - static_cast<double>(point.y()) / this->height();
462
463 // Update zoom & drag
464 double newZoomX = _zoomXinit * pow(2, 25.0 * decX);
465 double newZoomY = _zoomYinit * pow(2, 5.0 * decY);
466
467 if (newZoomX > 20 * _sizeX / this->width())
468 newZoomX = 20 * _sizeX / this->width();
469 if (newZoomX < 1)
470 newZoomX = 1;
471 if (newZoomY < 1)
472 newZoomY = 1;
473 else if (newZoomY > 50)
474 newZoomY = 50;
475
476 if (newZoomX != _zoomX || newZoomY != _zoomY)
477 {
478 _zoomX = newZoomX;
479 _zoomY = newZoomY;
480
481 // Update posX
482 if (_zoomX > 1)
483 _posX = (_zoomX * _posXinit * (_zoomXinit - 1) + _xInit*(_zoomX - _zoomXinit)) / (_zoomXinit * (_zoomX - 1));
484 }
485
486 this->repaint();
487 }
488
drag(QPoint point)489 void GraphicsWave::drag(QPoint point)
490 {
491 // Shift
492 double decX = static_cast<double>(point.x()) / this->width() - this->_xInit;
493
494 // Update posX et posY
495 if (_zoomXinit > 1)
496 _posX = _posXinit - decX / (_zoomXinit - 1);
497
498 if (_posX < 0)
499 _posX = 0;
500 else if (_posX > 1)
501 _posX = 1;
502
503 this->repaint();
504 }
505