source: trunk/demos/spectrum/app/waveform.cpp@ 858

Last change on this file since 858 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 14.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the examples of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:BSD$
10** You may use this file under the terms of the BSD license as follows:
11**
12** "Redistribution and use in source and binary forms, with or without
13** modification, are permitted provided that the following conditions are
14** met:
15** * Redistributions of source code must retain the above copyright
16** notice, this list of conditions and the following disclaimer.
17** * Redistributions in binary form must reproduce the above copyright
18** notice, this list of conditions and the following disclaimer in
19** the documentation and/or other materials provided with the
20** distribution.
21** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22** the names of its contributors may be used to endorse or promote
23** products derived from this software without specific prior written
24** permission.
25**
26** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
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
54Waveform::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
69Waveform::~Waveform()
70{
71 deletePixmaps();
72}
73
74void 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
145void Waveform::resizeEvent(QResizeEvent *event)
146{
147 if (event->size() != event->oldSize())
148 createPixmaps(event->size());
149}
150
151void 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
190void 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
207void 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
219void 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
234void Waveform::deletePixmaps()
235{
236 QPixmap *pixmap;
237 foreach (pixmap, m_pixmaps)
238 delete pixmap;
239 m_pixmaps.clear();
240}
241
242void 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
267void 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
290qint64 Waveform::tilePosition(int index) const
291{
292 return m_tileArrayStart + index * m_tileLength;
293}
294
295Waveform::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
313int 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
320int 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
327bool 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
350void 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
411void 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
426void 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
Note: See TracBrowser for help on using the repository browser.