waveform.cpp Example File
multimedia/spectrum/app/waveform.cpp
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "waveform.h"
#include "utils.h"
#include <QPainter>
#include <QResizeEvent>
#include <QDebug>
//#define PAINT_EVENT_TRACE
#ifdef PAINT_EVENT_TRACE
# define WAVEFORM_PAINT_DEBUG qDebug()
#else
# define WAVEFORM_PAINT_DEBUG nullDebug()
#endif
Waveform:: Waveform(QWidget * parent)
: QWidget (parent)
, m_bufferPosition(0 )
, m_bufferLength(0 )
, m_audioPosition(0 )
, m_active(false )
, m_tileLength(0 )
, m_tileArrayStart(0 )
, m_windowPosition(0 )
, m_windowLength(0 )
{
setSizePolicy(QSizePolicy :: Preferred, QSizePolicy :: Fixed);
setMinimumHeight(50 );
}
Waveform:: ~ Waveform()
{
deletePixmaps();
}
void Waveform:: paintEvent(QPaintEvent * /*event*/ )
{
QPainter painter(this );
painter. fillRect(rect(), Qt :: black);
if (m_active) {
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent"
< < "windowPosition" < < m_windowPosition
< < "windowLength" < < m_windowLength;
qint64 pos = m_windowPosition;
const qint64 windowEnd = m_windowPosition + m_windowLength;
int destLeft = 0 ;
int destRight = 0 ;
while (pos < windowEnd) {
const TilePoint point = tilePoint(pos);
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "pos" < < pos
< < "tileIndex" < < point. index
< < "positionOffset" < < point. positionOffset
< < "pixelOffset" < < point. pixelOffset;
if (point. index ! = NullIndex) {
const Tile & tile = m_tiles[ point. index] ;
if (tile. painted) {
const qint64 sectionLength = qMin ((m_tileLength - point. positionOffset),
(windowEnd - pos));
Q_ASSERT(sectionLength > 0 );
const int sourceRight = tilePixelOffset(point. positionOffset + sectionLength);
destRight = windowPixelOffset(pos - m_windowPosition + sectionLength);
QRect destRect = rect();
destRect. setLeft(destLeft);
destRect. setRight(destRight);
QRect sourceRect(QPoint (), m_pixmapSize);
sourceRect. setLeft(point. pixelOffset);
sourceRect. setRight(sourceRight);
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "tileIndex" < < point. index
< < "source" < < point. pixelOffset < < sourceRight
< < "dest" < < destLeft < < destRight;
painter. drawPixmap(destRect, * tile. pixmap, sourceRect);
destLeft = destRight;
if (point. index < m_tiles. count()) {
pos = tilePosition(point. index + 1 );
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "pos ->" < < pos;
} else {
// Reached end of tile array
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "reached end of tile array" ;
break ;
}
} else {
// Passed last tile which is painted
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "tile" < < point. index < < "not painted" ;
break ;
}
} else {
// pos is past end of tile array
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "pos" < < pos < < "past end of tile array" ;
break ;
}
}
WAVEFORM_PAINT_DEBUG < < "Waveform::paintEvent" < < "final pos" < < pos < < "final x" < < destRight;
}
}
void Waveform:: resizeEvent(QResizeEvent * event)
{
if (event- > size() ! = event- > oldSize())
createPixmaps(event- > size());
}
void Waveform:: initialize(const QAudioFormat & format, qint64 audioBufferSize, qint64 windowDurationUs)
{
WAVEFORM_DEBUG < < "Waveform::initialize"
< < "audioBufferSize" < < audioBufferSize
< < "windowDurationUs" < < windowDurationUs;
reset();
m_format = format;
// Calculate tile size
m_tileLength = audioBufferSize;
// Calculate window size
m_windowLength = audioLength(m_format, windowDurationUs);
// Calculate number of tiles required
int nTiles;
if (m_tileLength > m_windowLength) {
nTiles = 2 ;
} else {
nTiles = m_windowLength / m_tileLength + 1 ;
if (m_windowLength % m_tileLength)
+ + nTiles;
}
WAVEFORM_DEBUG < < "Waveform::initialize"
< < "tileLength" < < m_tileLength
< < "windowLength" < < m_windowLength
< < "nTiles" < < nTiles;
m_pixmaps. fill(0 , nTiles);
m_tiles. resize(nTiles);
createPixmaps(rect(). size());
m_active = true ;
}
void Waveform:: reset()
{
WAVEFORM_DEBUG < < "Waveform::reset" ;
m_bufferPosition = 0 ;
m_buffer = QByteArray ();
m_audioPosition = 0 ;
m_format = QAudioFormat ();
m_active = false ;
deletePixmaps();
m_tiles. clear();
m_tileLength = 0 ;
m_tileArrayStart = 0 ;
m_windowPosition = 0 ;
m_windowLength = 0 ;
}
void Waveform:: bufferChanged(qint64 position, qint64 length, const QByteArray & buffer)
{
WAVEFORM_DEBUG < < "Waveform::bufferChanged"
< < "audioPosition" < < m_audioPosition
< < "bufferPosition" < < position
< < "bufferLength" < < length;
m_bufferPosition = position;
m_bufferLength = length;
m_buffer = buffer;
paintTiles();
}
void Waveform:: audioPositionChanged(qint64 position)
{
WAVEFORM_DEBUG < < "Waveform::audioPositionChanged"
< < "audioPosition" < < position
< < "bufferPosition" < < m_bufferPosition
< < "bufferLength" < < m_bufferLength;
if (position > = m_bufferPosition) {
if (position + m_windowLength > m_bufferPosition + m_bufferLength)
position = qMax (qint64 (0 ), m_bufferPosition + m_bufferLength - m_windowLength);
m_audioPosition = position;
setWindowPosition(position);
}
}
void Waveform:: deletePixmaps()
{
QPixmap * pixmap;
foreach (pixmap, m_pixmaps)
delete pixmap;
m_pixmaps. clear();
}
void Waveform:: createPixmaps(const QSize & widgetSize)
{
m_pixmapSize = widgetSize;
m_pixmapSize. setWidth(qreal (widgetSize. width()) * m_tileLength / m_windowLength);
WAVEFORM_DEBUG < < "Waveform::createPixmaps"
< < "widgetSize" < < widgetSize
< < "pixmapSize" < < m_pixmapSize;
Q_ASSERT(m_tiles. count() = = m_pixmaps. count());
// (Re)create pixmaps
for (int i= 0 ; i< m_pixmaps. size(); + + i) {
delete m_pixmaps[ i] ;
m_pixmaps[ i] = 0 ;
m_pixmaps[ i] = new QPixmap (m_pixmapSize);
}
// Update tile pixmap pointers, and mark for repainting
for (int i= 0 ; i< m_tiles. count(); + + i) {
m_tiles[ i] . pixmap = m_pixmaps[ i] ;
m_tiles[ i] . painted = false ;
}
}
void Waveform:: setWindowPosition(qint64 position)
{
WAVEFORM_DEBUG < < "Waveform::setWindowPosition"
< < "old" < < m_windowPosition < < "new" < < position
< < "tileArrayStart" < < m_tileArrayStart;
const qint64 oldPosition = m_windowPosition;
m_windowPosition = position;
if ((m_windowPosition > = oldPosition) & &
(m_windowPosition - m_tileArrayStart < (m_tiles. count() * m_tileLength))) {
// Work out how many tiles need to be shuffled
const qint64 offset = m_windowPosition - m_tileArrayStart;
const int nTiles = offset / m_tileLength;
shuffleTiles(nTiles);
} else {
resetTiles(m_windowPosition);
}
if (! paintTiles() & & m_windowPosition ! = oldPosition)
update();
}
qint64 Waveform:: tilePosition(int index) const
{
return m_tileArrayStart + index * m_tileLength;
}
Waveform:: TilePoint Waveform:: tilePoint(qint64 position) const
{
TilePoint result;
if (position > = m_tileArrayStart) {
const qint64 tileArrayEnd = m_tileArrayStart + m_tiles. count() * m_tileLength;
if (position < tileArrayEnd) {
const qint64 offsetIntoTileArray = position - m_tileArrayStart;
result. index = offsetIntoTileArray / m_tileLength;
Q_ASSERT(result. index > = 0 & & result. index < = m_tiles. count());
result. positionOffset = offsetIntoTileArray % m_tileLength;
result. pixelOffset = tilePixelOffset(result. positionOffset);
Q_ASSERT(result. pixelOffset > = 0 & & result. pixelOffset < = m_pixmapSize. width());
}
}
return result;
}
int Waveform:: tilePixelOffset(qint64 positionOffset) const
{
Q_ASSERT(positionOffset > = 0 & & positionOffset < = m_tileLength);
const int result = (qreal (positionOffset) / m_tileLength) * m_pixmapSize. width();
return result;
}
int Waveform:: windowPixelOffset(qint64 positionOffset) const
{
Q_ASSERT(positionOffset > = 0 & & positionOffset < = m_windowLength);
const int result = (qreal (positionOffset) / m_windowLength) * rect(). width();
return result;
}
bool Waveform:: paintTiles()
{
WAVEFORM_DEBUG < < "Waveform::paintTiles" ;
bool updateRequired = false ;
for (int i= 0 ; i< m_tiles. count(); + + i) {
const Tile & tile = m_tiles[ i] ;
if (! tile. painted) {
const qint64 tileStart = m_tileArrayStart + i * m_tileLength;
const qint64 tileEnd = tileStart + m_tileLength;
if (m_bufferPosition < = tileStart & & m_bufferPosition + m_bufferLength > = tileEnd) {
paintTile(i);
updateRequired = true ;
}
}
}
if (updateRequired)
update();
return updateRequired;
}
void Waveform:: paintTile(int index)
{
const qint64 tileStart = m_tileArrayStart + index * m_tileLength;
WAVEFORM_DEBUG < < "Waveform::paintTile"
< < "index" < < index
< < "bufferPosition" < < m_bufferPosition
< < "bufferLength" < < m_bufferLength
< < "start" < < tileStart
< < "end" < < tileStart + m_tileLength;
Q_ASSERT(m_bufferPosition < = tileStart);
Q_ASSERT(m_bufferPosition + m_bufferLength > = tileStart + m_tileLength);
Tile & tile = m_tiles[ index] ;
Q_ASSERT(! tile. painted);
const qint16 * base = reinterpret_cast < const qint16 * > (m_buffer. constData());
const qint16 * buffer = base + ((tileStart - m_bufferPosition) / 2 );
const int numSamples = m_tileLength / (2 * m_format. channelCount());
QPainter painter(tile. pixmap);
painter. fillRect(tile. pixmap- > rect(), Qt :: black);
QPen pen(Qt :: white);
painter. setPen(pen);
// Calculate initial PCM value
qint16 previousPcmValue = 0 ;
if (buffer > base)
previousPcmValue = * (buffer - m_format. channelCount());
// Calculate initial point
const qreal previousRealValue = pcmToReal(previousPcmValue);
const int originY = ((previousRealValue + 1.0 ) / 2 ) * m_pixmapSize. height();
const QPoint origin(0 , originY);
QLine line(origin, origin);
for (int i= 0 ; i< numSamples; + + i) {
const qint16 * ptr = buffer + i * m_format. channelCount();
const int offset = reinterpret_cast < const char * > (ptr) - m_buffer. constData();
Q_ASSERT(offset > = 0 );
Q_ASSERT(offset < m_bufferLength);
Q_UNUSED(offset);
const qint16 pcmValue = * ptr;
const qreal realValue = pcmToReal(pcmValue);
const int x = tilePixelOffset(i * 2 * m_format. channelCount());
const int y = ((realValue + 1.0 ) / 2 ) * m_pixmapSize. height();
line. setP2(QPoint (x, y));
painter. drawLine(line);
line. setP1(line. p2());
}
tile. painted = true ;
}
void Waveform:: shuffleTiles(int n)
{
WAVEFORM_DEBUG < < "Waveform::shuffleTiles" < < "n" < < n;
while (n- - ) {
Tile tile = m_tiles. first();
tile. painted = false ;
m_tiles. erase(m_tiles. begin());
m_tiles + = tile;
m_tileArrayStart + = m_tileLength;
}
WAVEFORM_DEBUG < < "Waveform::shuffleTiles" < < "tileArrayStart" < < m_tileArrayStart;
}
void Waveform:: resetTiles(qint64 newStartPos)
{
WAVEFORM_DEBUG < < "Waveform::resetTiles" < < "newStartPos" < < newStartPos;
QVector < Tile> :: iterator i = m_tiles. begin();
for ( ; i ! = m_tiles. end(); + + i)
i- > painted = false ;
m_tileArrayStart = newStartPos;
}