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