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

Last change on this file since 822 was 769, checked in by Dmitry A. Kuminov, 15 years ago

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

File size: 13.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 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
48Waveform::Waveform(const QByteArray &buffer, QWidget *parent)
49 : QWidget(parent)
50 , m_buffer(buffer)
51 , m_dataLength(0)
52 , m_position(0)
53 , m_active(false)
54 , m_tileLength(0)
55 , m_tileArrayStart(0)
56 , m_windowPosition(0)
57 , m_windowLength(0)
58{
59 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
60 setMinimumHeight(50);
61}
62
63Waveform::~Waveform()
64{
65 deletePixmaps();
66}
67
68void Waveform::paintEvent(QPaintEvent * /*event*/)
69{
70 QPainter painter(this);
71
72 painter.fillRect(rect(), Qt::black);
73
74 if (m_active) {
75 WAVEFORM_DEBUG << "Waveform::paintEvent"
76 << "windowPosition" << m_windowPosition
77 << "windowLength" << m_windowLength;
78 qint64 pos = m_windowPosition;
79 const qint64 windowEnd = m_windowPosition + m_windowLength;
80 int destLeft = 0;
81 int destRight = 0;
82 while (pos < windowEnd) {
83 const TilePoint point = tilePoint(pos);
84 WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos" << pos
85 << "tileIndex" << point.index
86 << "positionOffset" << point.positionOffset
87 << "pixelOffset" << point.pixelOffset;
88
89 if (point.index != NullIndex) {
90 const Tile &tile = m_tiles[point.index];
91 if (tile.painted) {
92 const qint64 sectionLength = qMin((m_tileLength - point.positionOffset),
93 (windowEnd - pos));
94 Q_ASSERT(sectionLength > 0);
95
96 const int sourceRight = tilePixelOffset(point.positionOffset + sectionLength);
97 destRight = windowPixelOffset(pos - m_windowPosition + sectionLength);
98
99 QRect destRect = rect();
100 destRect.setLeft(destLeft);
101 destRect.setRight(destRight);
102
103 QRect sourceRect(QPoint(), m_pixmapSize);
104 sourceRect.setLeft(point.pixelOffset);
105 sourceRect.setRight(sourceRight);
106
107 WAVEFORM_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index
108 << "source" << point.pixelOffset << sourceRight
109 << "dest" << destLeft << destRight;
110
111 painter.drawPixmap(destRect, *tile.pixmap, sourceRect);
112
113 destLeft = destRight;
114
115 if (point.index < m_tiles.count()) {
116 pos = tilePosition(point.index + 1);
117 WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos ->" << pos;
118 } else {
119 // Reached end of tile array
120 WAVEFORM_DEBUG << "Waveform::paintEvent" << "reached end of tile array";
121 break;
122 }
123 } else {
124 // Passed last tile which is painted
125 WAVEFORM_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted";
126 break;
127 }
128 } else {
129 // pos is past end of tile array
130 WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array";
131 break;
132 }
133 }
134
135 WAVEFORM_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight;
136 }
137}
138
139void Waveform::resizeEvent(QResizeEvent *event)
140{
141 if (event->size() != event->oldSize())
142 createPixmaps(event->size());
143}
144
145void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs)
146{
147 WAVEFORM_DEBUG << "Waveform::initialize"
148 << "audioBufferSize" << audioBufferSize
149 << "m_buffer.size()" << m_buffer.size()
150 << "windowDurationUs" << windowDurationUs;
151
152 reset();
153
154 m_format = format;
155
156 // Calculate tile size
157 m_tileLength = audioBufferSize;
158
159 // Calculate window size
160 m_windowLength = audioLength(m_format, windowDurationUs);
161
162 // Calculate number of tiles required
163 int nTiles;
164 if (m_tileLength > m_windowLength) {
165 nTiles = 2;
166 } else {
167 nTiles = m_windowLength / m_tileLength + 1;
168 if (m_windowLength % m_tileLength)
169 ++nTiles;
170 }
171
172 WAVEFORM_DEBUG << "Waveform::initialize"
173 << "tileLength" << m_tileLength
174 << "windowLength" << m_windowLength
175 << "nTiles" << nTiles;
176
177 m_pixmaps.fill(0, nTiles);
178 m_tiles.resize(nTiles);
179
180 createPixmaps(rect().size());
181
182 m_active = true;
183}
184
185void Waveform::reset()
186{
187 WAVEFORM_DEBUG << "Waveform::reset";
188
189 m_dataLength = 0;
190 m_position = 0;
191 m_format = QAudioFormat();
192 m_active = false;
193 deletePixmaps();
194 m_tiles.clear();
195 m_tileLength = 0;
196 m_tileArrayStart = 0;
197 m_windowPosition = 0;
198 m_windowLength = 0;
199}
200
201void Waveform::dataLengthChanged(qint64 length)
202{
203 WAVEFORM_DEBUG << "Waveform::dataLengthChanged" << length;
204 const qint64 oldLength = m_dataLength;
205 m_dataLength = length;
206
207 if (m_active) {
208 if (m_dataLength < oldLength)
209 positionChanged(m_dataLength);
210 else
211 paintTiles();
212 }
213}
214
215void Waveform::positionChanged(qint64 position)
216{
217 WAVEFORM_DEBUG << "Waveform::positionChanged" << position;
218
219 if (position + m_windowLength > m_dataLength)
220 position = m_dataLength - m_windowLength;
221
222 m_position = position;
223
224 setWindowPosition(position);
225}
226
227void Waveform::deletePixmaps()
228{
229 QPixmap *pixmap;
230 foreach (pixmap, m_pixmaps)
231 delete pixmap;
232 m_pixmaps.clear();
233}
234
235void Waveform::createPixmaps(const QSize &widgetSize)
236{
237 m_pixmapSize = widgetSize;
238 m_pixmapSize.setWidth(qreal(widgetSize.width()) * m_tileLength / m_windowLength);
239
240 WAVEFORM_DEBUG << "Waveform::createPixmaps"
241 << "widgetSize" << widgetSize
242 << "pixmapSize" << m_pixmapSize;
243
244 Q_ASSERT(m_tiles.count() == m_pixmaps.count());
245
246 // (Re)create pixmaps
247 for (int i=0; i<m_pixmaps.size(); ++i) {
248 delete m_pixmaps[i];
249 m_pixmaps[i] = 0;
250 m_pixmaps[i] = new QPixmap(m_pixmapSize);
251 }
252
253 // Update tile pixmap pointers, and mark for repainting
254 for (int i=0; i<m_tiles.count(); ++i) {
255 m_tiles[i].pixmap = m_pixmaps[i];
256 m_tiles[i].painted = false;
257 }
258
259 paintTiles();
260}
261
262void Waveform::setWindowPosition(qint64 position)
263{
264 WAVEFORM_DEBUG << "Waveform::setWindowPosition"
265 << "old" << m_windowPosition << "new" << position
266 << "tileArrayStart" << m_tileArrayStart;
267
268 const qint64 oldPosition = m_windowPosition;
269 m_windowPosition = position;
270
271 if((m_windowPosition >= oldPosition) &&
272 (m_windowPosition - m_tileArrayStart < (m_tiles.count() * m_tileLength))) {
273 // Work out how many tiles need to be shuffled
274 const qint64 offset = m_windowPosition - m_tileArrayStart;
275 const int nTiles = offset / m_tileLength;
276 shuffleTiles(nTiles);
277 } else {
278 resetTiles(m_windowPosition);
279 }
280
281 if(!paintTiles() && m_windowPosition != oldPosition)
282 update();
283}
284
285qint64 Waveform::tilePosition(int index) const
286{
287 return m_tileArrayStart + index * m_tileLength;
288}
289
290Waveform::TilePoint Waveform::tilePoint(qint64 position) const
291{
292 TilePoint result;
293 if (position >= m_tileArrayStart) {
294 const qint64 tileArrayEnd = m_tileArrayStart + m_tiles.count() * m_tileLength;
295 if (position < tileArrayEnd) {
296 const qint64 offsetIntoTileArray = position - m_tileArrayStart;
297 result.index = offsetIntoTileArray / m_tileLength;
298 Q_ASSERT(result.index >= 0 && result.index <= m_tiles.count());
299 result.positionOffset = offsetIntoTileArray % m_tileLength;
300 result.pixelOffset = tilePixelOffset(result.positionOffset);
301 Q_ASSERT(result.pixelOffset >= 0 && result.pixelOffset <= m_pixmapSize.width());
302 }
303 }
304
305 return result;
306}
307
308int Waveform::tilePixelOffset(qint64 positionOffset) const
309{
310 Q_ASSERT(positionOffset >= 0 && positionOffset <= m_tileLength);
311 const int result = (qreal(positionOffset) / m_tileLength) * m_pixmapSize.width();
312 return result;
313}
314
315int Waveform::windowPixelOffset(qint64 positionOffset) const
316{
317 Q_ASSERT(positionOffset >= 0 && positionOffset <= m_windowLength);
318 const int result = (qreal(positionOffset) / m_windowLength) * rect().width();
319 return result;
320}
321
322bool Waveform::paintTiles()
323{
324 WAVEFORM_DEBUG << "Waveform::paintTiles";
325 bool updateRequired = false;
326
327 for (int i=0; i<m_tiles.count(); ++i) {
328 const Tile &tile = m_tiles[i];
329 if (!tile.painted) {
330 const qint64 tileEnd = m_tileArrayStart + (i + 1) * m_tileLength;
331 if (m_dataLength >= tileEnd) {
332 paintTile(i);
333 updateRequired = true;
334 }
335 }
336 }
337
338 if (updateRequired)
339 update();
340
341 return updateRequired;
342}
343
344void Waveform::paintTile(int index)
345{
346 WAVEFORM_DEBUG << "Waveform::paintTile" << "index" << index;
347
348 const qint64 tileStart = m_tileArrayStart + index * m_tileLength;
349 Q_ASSERT(m_dataLength >= tileStart + m_tileLength);
350
351 Tile &tile = m_tiles[index];
352 Q_ASSERT(!tile.painted);
353
354 const qint16* base = reinterpret_cast<const qint16*>(m_buffer.constData());
355 const qint16* buffer = base + (tileStart / 2);
356 const int numSamples = m_tileLength / (2 * m_format.channels());
357
358 QPainter painter(tile.pixmap);
359
360 painter.fillRect(tile.pixmap->rect(), Qt::black);
361
362 QPen pen(Qt::white);
363 painter.setPen(pen);
364
365 // Calculate initial PCM value
366 qint16 previousPcmValue = 0;
367 if (buffer > base)
368 previousPcmValue = *(buffer - m_format.channels());
369
370 // Calculate initial point
371 const qreal previousRealValue = pcmToReal(previousPcmValue);
372 const int originY = ((previousRealValue + 1.0) / 2) * m_pixmapSize.height();
373 const QPoint origin(0, originY);
374
375 QLine line(origin, origin);
376
377 for (int i=0; i<numSamples; ++i) {
378 const qint16* ptr = buffer + i * m_format.channels();
379 const qint16 pcmValue = *ptr;
380 const qreal realValue = pcmToReal(pcmValue);
381
382 const int x = tilePixelOffset(i * 2 * m_format.channels());
383 const int y = ((realValue + 1.0) / 2) * m_pixmapSize.height();
384
385 line.setP2(QPoint(x, y));
386 painter.drawLine(line);
387 line.setP1(line.p2());
388 }
389
390 tile.painted = true;
391}
392
393void Waveform::shuffleTiles(int n)
394{
395 WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "n" << n;
396
397 while (n--) {
398 Tile tile = m_tiles.first();
399 tile.painted = false;
400 m_tiles.erase(m_tiles.begin());
401 m_tiles += tile;
402 m_tileArrayStart += m_tileLength;
403 }
404
405 WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "tileArrayStart" << m_tileArrayStart;
406}
407
408void Waveform::resetTiles(qint64 newStartPos)
409{
410 WAVEFORM_DEBUG << "Waveform::resetTiles" << "newStartPos" << newStartPos;
411
412 QVector<Tile>::iterator i = m_tiles.begin();
413 for ( ; i != m_tiles.end(); ++i)
414 i->painted = false;
415
416 m_tileArrayStart = newStartPos;
417}
418
Note: See TracBrowser for help on using the repository browser.