1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** You may use this file under the terms of the BSD license as follows:
10 **
11 ** "Redistribution and use in source and binary forms, with or without
12 ** modification, are permitted provided that the following conditions are
13 ** met:
14 ** * Redistributions of source code must retain the above copyright
15 ** notice, this list of conditions and the following disclaimer.
16 ** * Redistributions in binary form must reproduce the above copyright
17 ** notice, this list of conditions and the following disclaimer in
18 ** the documentation and/or other materials provided with the
19 ** distribution.
20 ** * Neither the name of The Qt Company Ltd nor the names of its
21 ** contributors may be used to endorse or promote products derived
22 ** from this software without specific prior written permission.
23 **
24 **
25 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "waveform.h"
42 #include "utils.h"
43 #include <QPainter>
44 #include <QResizeEvent>
45 #include <QDebug>
46
47 //#define PAINT_EVENT_TRACE
48 #ifdef PAINT_EVENT_TRACE
49 # define WAVEFORM_PAINT_DEBUG qDebug()
50 #else
51 # define WAVEFORM_PAINT_DEBUG nullDebug()
52 #endif
53
Waveform(QWidget * parent)54 Waveform::Waveform(QWidget *parent)
55 : QWidget(parent)
56 , m_bufferPosition(0)
57 , m_bufferLength(0)
58 , m_audioPosition(0)
59 , m_active(false)
60 , m_tileLength(0)
61 , m_tileArrayStart(0)
62 , m_windowPosition(0)
63 , m_windowLength(0)
64 {
65 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
66 setMinimumHeight(50);
67 }
68
~Waveform()69 Waveform::~Waveform()
70 {
71 deletePixmaps();
72 }
73
paintEvent(QPaintEvent *)74 void Waveform::paintEvent(QPaintEvent * /*event*/)
75 {
76 QPainter painter(this);
77
78 painter.fillRect(rect(), Qt::black);
79
80 if (m_active) {
81 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent"
82 << "windowPosition" << m_windowPosition
83 << "windowLength" << m_windowLength;
84 qint64 pos = m_windowPosition;
85 const qint64 windowEnd = m_windowPosition + m_windowLength;
86 int destLeft = 0;
87 int destRight = 0;
88 while (pos < windowEnd) {
89 const TilePoint point = tilePoint(pos);
90 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos
91 << "tileIndex" << point.index
92 << "positionOffset" << point.positionOffset
93 << "pixelOffset" << point.pixelOffset;
94
95 if (point.index != NullIndex) {
96 const Tile &tile = m_tiles[point.index];
97 if (tile.painted) {
98 const qint64 sectionLength = qMin((m_tileLength - point.positionOffset),
99 (windowEnd - pos));
100 Q_ASSERT(sectionLength > 0);
101
102 const int sourceRight = tilePixelOffset(point.positionOffset + sectionLength);
103 destRight = windowPixelOffset(pos - m_windowPosition + sectionLength);
104
105 QRect destRect = rect();
106 destRect.setLeft(destLeft);
107 destRect.setRight(destRight);
108
109 QRect sourceRect(QPoint(), m_pixmapSize);
110 sourceRect.setLeft(point.pixelOffset);
111 sourceRect.setRight(sourceRight);
112
113 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index
114 << "source" << point.pixelOffset << sourceRight
115 << "dest" << destLeft << destRight;
116
117 painter.drawPixmap(destRect, *tile.pixmap, sourceRect);
118
119 destLeft = destRight;
120
121 if (point.index < m_tiles.count()) {
122 pos = tilePosition(point.index + 1);
123 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos ->" << pos;
124 } else {
125 // Reached end of tile array
126 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "reached end of tile array";
127 break;
128 }
129 } else {
130 // Passed last tile which is painted
131 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted";
132 break;
133 }
134 } else {
135 // pos is past end of tile array
136 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array";
137 break;
138 }
139 }
140
141 WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight;
142 }
143 }
144
resizeEvent(QResizeEvent * event)145 void Waveform::resizeEvent(QResizeEvent *event)
146 {
147 if (event->size() != event->oldSize())
148 createPixmaps(event->size());
149 }
150
initialize(const QAudioFormat & format,qint64 audioBufferSize,qint64 windowDurationUs)151 void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs)
152 {
153 WAVEFORM_DEBUG << "Waveform::initialize"
154 << "audioBufferSize" << audioBufferSize
155 << "windowDurationUs" << windowDurationUs;
156
157 reset();
158
159 m_format = format;
160
161 // Calculate tile size
162 m_tileLength = audioBufferSize;
163
164 // Calculate window size
165 m_windowLength = audioLength(m_format, windowDurationUs);
166
167 // Calculate number of tiles required
168 int nTiles;
169 if (m_tileLength > m_windowLength) {
170 nTiles = 2;
171 } else {
172 nTiles = m_windowLength / m_tileLength + 1;
173 if (m_windowLength % m_tileLength)
174 ++nTiles;
175 }
176
177 WAVEFORM_DEBUG << "Waveform::initialize"
178 << "tileLength" << m_tileLength
179 << "windowLength" << m_windowLength
180 << "nTiles" << nTiles;
181
182 m_pixmaps.fill(0, nTiles);
183 m_tiles.resize(nTiles);
184
185 createPixmaps(rect().size());
186
187 m_active = true;
188 }
189
reset()190 void Waveform::reset()
191 {
192 WAVEFORM_DEBUG << "Waveform::reset";
193
194 m_bufferPosition = 0;
195 m_buffer = QByteArray();
196 m_audioPosition = 0;
197 m_format = QAudioFormat();
198 m_active = false;
199 deletePixmaps();
200 m_tiles.clear();
201 m_tileLength = 0;
202 m_tileArrayStart = 0;
203 m_windowPosition = 0;
204 m_windowLength = 0;
205 }
206
bufferChanged(qint64 position,qint64 length,const QByteArray & buffer)207 void Waveform::bufferChanged(qint64 position, qint64 length, const QByteArray &buffer)
208 {
209 WAVEFORM_DEBUG << "Waveform::bufferChanged"
210 << "audioPosition" << m_audioPosition
211 << "bufferPosition" << position
212 << "bufferLength" << length;
213 m_bufferPosition = position;
214 m_bufferLength = length;
215 m_buffer = buffer;
216 paintTiles();
217 }
218
audioPositionChanged(qint64 position)219 void Waveform::audioPositionChanged(qint64 position)
220 {
221 WAVEFORM_DEBUG << "Waveform::audioPositionChanged"
222 << "audioPosition" << position
223 << "bufferPosition" << m_bufferPosition
224 << "bufferLength" << m_bufferLength;
225
226 if (position >= m_bufferPosition) {
227 if (position + m_windowLength > m_bufferPosition + m_bufferLength)
228 position = qMax(qint64(0), m_bufferPosition + m_bufferLength - m_windowLength);
229 m_audioPosition = position;
230 setWindowPosition(position);
231 }
232 }
233
deletePixmaps()234 void Waveform::deletePixmaps()
235 {
236 QPixmap *pixmap;
237 foreach (pixmap, m_pixmaps)
238 delete pixmap;
239 m_pixmaps.clear();
240 }
241
createPixmaps(const QSize & widgetSize)242 void Waveform::createPixmaps(const QSize &widgetSize)
243 {
244 m_pixmapSize = widgetSize;
245 m_pixmapSize.setWidth(qreal(widgetSize.width()) * m_tileLength / m_windowLength);
246
247 WAVEFORM_DEBUG << "Waveform::createPixmaps"
248 << "widgetSize" << widgetSize
249 << "pixmapSize" << m_pixmapSize;
250
251 Q_ASSERT(m_tiles.count() == m_pixmaps.count());
252
253 // (Re)create pixmaps
254 for (int i=0; i<m_pixmaps.size(); ++i) {
255 delete m_pixmaps[i];
256 m_pixmaps[i] = 0;
257 m_pixmaps[i] = new QPixmap(m_pixmapSize);
258 }
259
260 // Update tile pixmap pointers, and mark for repainting
261 for (int i=0; i<m_tiles.count(); ++i) {
262 m_tiles[i].pixmap = m_pixmaps[i];
263 m_tiles[i].painted = false;
264 }
265 }
266
setWindowPosition(qint64 position)267 void Waveform::setWindowPosition(qint64 position)
268 {
269 WAVEFORM_DEBUG << "Waveform::setWindowPosition"
270 << "old" << m_windowPosition << "new" << position
271 << "tileArrayStart" << m_tileArrayStart;
272
273 const qint64 oldPosition = m_windowPosition;
274 m_windowPosition = position;
275
276 if((m_windowPosition >= oldPosition) &&
277 (m_windowPosition - m_tileArrayStart < (m_tiles.count() * m_tileLength))) {
278 // Work out how many tiles need to be shuffled
279 const qint64 offset = m_windowPosition - m_tileArrayStart;
280 const int nTiles = offset / m_tileLength;
281 shuffleTiles(nTiles);
282 } else {
283 resetTiles(m_windowPosition);
284 }
285
286 if(!paintTiles() && m_windowPosition != oldPosition)
287 update();
288 }
289
tilePosition(int index) const290 qint64 Waveform::tilePosition(int index) const
291 {
292 return m_tileArrayStart + index * m_tileLength;
293 }
294
tilePoint(qint64 position) const295 Waveform::TilePoint Waveform::tilePoint(qint64 position) const
296 {
297 TilePoint result;
298 if (position >= m_tileArrayStart) {
299 const qint64 tileArrayEnd = m_tileArrayStart + m_tiles.count() * m_tileLength;
300 if (position < tileArrayEnd) {
301 const qint64 offsetIntoTileArray = position - m_tileArrayStart;
302 result.index = offsetIntoTileArray / m_tileLength;
303 Q_ASSERT(result.index >= 0 && result.index <= m_tiles.count());
304 result.positionOffset = offsetIntoTileArray % m_tileLength;
305 result.pixelOffset = tilePixelOffset(result.positionOffset);
306 Q_ASSERT(result.pixelOffset >= 0 && result.pixelOffset <= m_pixmapSize.width());
307 }
308 }
309
310 return result;
311 }
312
tilePixelOffset(qint64 positionOffset) const313 int Waveform::tilePixelOffset(qint64 positionOffset) const
314 {
315 Q_ASSERT(positionOffset >= 0 && positionOffset <= m_tileLength);
316 const int result = (qreal(positionOffset) / m_tileLength) * m_pixmapSize.width();
317 return result;
318 }
319
windowPixelOffset(qint64 positionOffset) const320 int Waveform::windowPixelOffset(qint64 positionOffset) const
321 {
322 Q_ASSERT(positionOffset >= 0 && positionOffset <= m_windowLength);
323 const int result = (qreal(positionOffset) / m_windowLength) * rect().width();
324 return result;
325 }
326
paintTiles()327 bool Waveform::paintTiles()
328 {
329 WAVEFORM_DEBUG << "Waveform::paintTiles";
330 bool updateRequired = false;
331
332 for (int i=0; i<m_tiles.count(); ++i) {
333 const Tile &tile = m_tiles[i];
334 if (!tile.painted) {
335 const qint64 tileStart = m_tileArrayStart + i * m_tileLength;
336 const qint64 tileEnd = tileStart + m_tileLength;
337 if (m_bufferPosition <= tileStart && m_bufferPosition + m_bufferLength >= tileEnd) {
338 paintTile(i);
339 updateRequired = true;
340 }
341 }
342 }
343
344 if (updateRequired)
345 update();
346
347 return updateRequired;
348 }
349
paintTile(int index)350 void Waveform::paintTile(int index)
351 {
352 const qint64 tileStart = m_tileArrayStart + index * m_tileLength;
353
354 WAVEFORM_DEBUG << "Waveform::paintTile"
355 << "index" << index
356 << "bufferPosition" << m_bufferPosition
357 << "bufferLength" << m_bufferLength
358 << "start" << tileStart
359 << "end" << tileStart + m_tileLength;
360
361 Q_ASSERT(m_bufferPosition <= tileStart);
362 Q_ASSERT(m_bufferPosition + m_bufferLength >= tileStart + m_tileLength);
363
364 Tile &tile = m_tiles[index];
365 Q_ASSERT(!tile.painted);
366
367 const qint16* base = reinterpret_cast<const qint16*>(m_buffer.constData());
368 const qint16* buffer = base + ((tileStart - m_bufferPosition) / 2);
369 const int numSamples = m_tileLength / (2 * m_format.channels());
370
371 QPainter painter(tile.pixmap);
372
373 painter.fillRect(tile.pixmap->rect(), Qt::black);
374
375 QPen pen(Qt::white);
376 painter.setPen(pen);
377
378 // Calculate initial PCM value
379 qint16 previousPcmValue = 0;
380 if (buffer > base)
381 previousPcmValue = *(buffer - m_format.channels());
382
383 // Calculate initial point
384 const qreal previousRealValue = pcmToReal(previousPcmValue);
385 const int originY = ((previousRealValue + 1.0) / 2) * m_pixmapSize.height();
386 const QPoint origin(0, originY);
387
388 QLine line(origin, origin);
389
390 for (int i=0; i<numSamples; ++i) {
391 const qint16* ptr = buffer + i * m_format.channels();
392
393 const int offset = reinterpret_cast<const char*>(ptr) - m_buffer.constData();
394 Q_ASSERT(offset >= 0);
395 Q_ASSERT(offset < m_bufferLength);
396
397 const qint16 pcmValue = *ptr;
398 const qreal realValue = pcmToReal(pcmValue);
399
400 const int x = tilePixelOffset(i * 2 * m_format.channels());
401 const int y = ((realValue + 1.0) / 2) * m_pixmapSize.height();
402
403 line.setP2(QPoint(x, y));
404 painter.drawLine(line);
405 line.setP1(line.p2());
406 }
407
408 tile.painted = true;
409 }
410
shuffleTiles(int n)411 void Waveform::shuffleTiles(int n)
412 {
413 WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "n" << n;
414
415 while (n--) {
416 Tile tile = m_tiles.first();
417 tile.painted = false;
418 m_tiles.erase(m_tiles.begin());
419 m_tiles += tile;
420 m_tileArrayStart += m_tileLength;
421 }
422
423 WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "tileArrayStart" << m_tileArrayStart;
424 }
425
resetTiles(qint64 newStartPos)426 void Waveform::resetTiles(qint64 newStartPos)
427 {
428 WAVEFORM_DEBUG << "Waveform::resetTiles" << "newStartPos" << newStartPos;
429
430 QVector<Tile>::iterator i = m_tiles.begin();
431 for ( ; i != m_tiles.end(); ++i)
432 i->painted = false;
433
434 m_tileArrayStart = newStartPos;
435 }
436
437