Spectrum Example▲
Sélectionnez
/**
**************************************************************************
**
** 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 &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "windowPosition"
&
lt;&
lt; m_windowPosition
&
lt;&
lt; "windowLength"
&
lt;&
lt; m_windowLength;
qint64 pos =
m_windowPosition;
const
qint64 windowEnd =
m_windowPosition +
m_windowLength;
int
destLeft =
0
;
int
destRight =
0
;
while
(pos &
lt; windowEnd) {
const
TilePoint point =
tilePoint(pos);
WAVEFORM_PAINT_DEBUG &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "pos"
&
lt;&
lt; pos
&
lt;&
lt; "tileIndex"
&
lt;&
lt; point.index
&
lt;&
lt; "positionOffset"
&
lt;&
lt; point.positionOffset
&
lt;&
lt; "pixelOffset"
&
lt;&
lt; point.pixelOffset;
if
(point.index !=
NullIndex) {
const
Tile &
amp;tile =
m_tiles[point.index];
if
(tile.painted) {
const
qint64 sectionLength =
qMin((m_tileLength -
point.positionOffset),
(windowEnd -
pos));
Q_ASSERT(sectionLength &
gt; 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 &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "tileIndex"
&
lt;&
lt; point.index
&
lt;&
lt; "source"
&
lt;&
lt; point.pixelOffset &
lt;&
lt; sourceRight
&
lt;&
lt; "dest"
&
lt;&
lt; destLeft &
lt;&
lt; destRight;
painter.drawPixmap(destRect, *
tile.pixmap, sourceRect);
destLeft =
destRight;
if
(point.index &
lt; m_tiles.count()) {
pos =
tilePosition(point.index +
1
);
WAVEFORM_PAINT_DEBUG &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "pos ->"
&
lt;&
lt; pos;
}
else
{
// Reached end of tile array
WAVEFORM_PAINT_DEBUG &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "reached end of tile array"
;
break
;
}
}
else
{
// Passed last tile which is painted
WAVEFORM_PAINT_DEBUG &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "tile"
&
lt;&
lt; point.index &
lt;&
lt; "not painted"
;
break
;
}
}
else
{
// pos is past end of tile array
WAVEFORM_PAINT_DEBUG &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "pos"
&
lt;&
lt; pos &
lt;&
lt; "past end of tile array"
;
break
;
}
}
WAVEFORM_PAINT_DEBUG &
lt;&
lt; "Waveform::paintEvent"
&
lt;&
lt; "final pos"
&
lt;&
lt; pos &
lt;&
lt; "final x"
&
lt;&
lt; destRight;
}
}
void
Waveform::
resizeEvent(QResizeEvent *
event)
{
if
(event-&
gt;size() !=
event-&
gt;oldSize())
createPixmaps(event-&
gt;size());
}
void
Waveform::
initialize(const
QAudioFormat &
amp;format, qint64 audioBufferSize, qint64 windowDurationUs)
{
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::initialize"
&
lt;&
lt; "audioBufferSize"
&
lt;&
lt; audioBufferSize
&
lt;&
lt; "windowDurationUs"
&
lt;&
lt; 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 &
gt; m_windowLength) {
nTiles =
2
;
}
else
{
nTiles =
m_windowLength /
m_tileLength +
1
;
if
(m_windowLength %
m_tileLength)
++
nTiles;
}
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::initialize"
&
lt;&
lt; "tileLength"
&
lt;&
lt; m_tileLength
&
lt;&
lt; "windowLength"
&
lt;&
lt; m_windowLength
&
lt;&
lt; "nTiles"
&
lt;&
lt; nTiles;
m_pixmaps.fill(0
, nTiles);
m_tiles.resize(nTiles);
createPixmaps(rect().size());
m_active =
true
;
}
void
Waveform::
reset()
{
WAVEFORM_DEBUG &
lt;&
lt; "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 &
amp;buffer)
{
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::bufferChanged"
&
lt;&
lt; "audioPosition"
&
lt;&
lt; m_audioPosition
&
lt;&
lt; "bufferPosition"
&
lt;&
lt; position
&
lt;&
lt; "bufferLength"
&
lt;&
lt; length;
m_bufferPosition =
position;
m_bufferLength =
length;
m_buffer =
buffer;
paintTiles();
}
void
Waveform::
audioPositionChanged(qint64 position)
{
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::audioPositionChanged"
&
lt;&
lt; "audioPosition"
&
lt;&
lt; position
&
lt;&
lt; "bufferPosition"
&
lt;&
lt; m_bufferPosition
&
lt;&
lt; "bufferLength"
&
lt;&
lt; m_bufferLength;
if
(position &
gt;=
m_bufferPosition) {
if
(position +
m_windowLength &
gt; 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 &
amp;widgetSize)
{
m_pixmapSize =
widgetSize;
m_pixmapSize.setWidth(qreal(widgetSize.width()) *
m_tileLength /
m_windowLength);
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::createPixmaps"
&
lt;&
lt; "widgetSize"
&
lt;&
lt; widgetSize
&
lt;&
lt; "pixmapSize"
&
lt;&
lt; m_pixmapSize;
Q_ASSERT(m_tiles.count() ==
m_pixmaps.count());
// (Re)create pixmaps
for
(int
i=
0
; i&
lt;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&
lt;m_tiles.count(); ++
i) {
m_tiles[i].pixmap =
m_pixmaps[i];
m_tiles[i].painted =
false
;
}
}
void
Waveform::
setWindowPosition(qint64 position)
{
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::setWindowPosition"
&
lt;&
lt; "old"
&
lt;&
lt; m_windowPosition &
lt;&
lt; "new"
&
lt;&
lt; position
&
lt;&
lt; "tileArrayStart"
&
lt;&
lt; m_tileArrayStart;
const
qint64 oldPosition =
m_windowPosition;
m_windowPosition =
position;
if
((m_windowPosition &
gt;=
oldPosition) &
amp;&
amp;
(m_windowPosition -
m_tileArrayStart &
lt; (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() &
amp;&
amp; 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 &
gt;=
m_tileArrayStart) {
const
qint64 tileArrayEnd =
m_tileArrayStart +
m_tiles.count() *
m_tileLength;
if
(position &
lt; tileArrayEnd) {
const
qint64 offsetIntoTileArray =
position -
m_tileArrayStart;
result.index =
offsetIntoTileArray /
m_tileLength;
Q_ASSERT(result.index &
gt;=
0
&
amp;&
amp; result.index &
lt;=
m_tiles.count());
result.positionOffset =
offsetIntoTileArray %
m_tileLength;
result.pixelOffset =
tilePixelOffset(result.positionOffset);
Q_ASSERT(result.pixelOffset &
gt;=
0
&
amp;&
amp; result.pixelOffset &
lt;=
m_pixmapSize.width());
}
}
return
result;
}
int
Waveform::
tilePixelOffset(qint64 positionOffset) const
{
Q_ASSERT(positionOffset &
gt;=
0
&
amp;&
amp; positionOffset &
lt;=
m_tileLength);
const
int
result =
(qreal(positionOffset) /
m_tileLength) *
m_pixmapSize.width();
return
result;
}
int
Waveform::
windowPixelOffset(qint64 positionOffset) const
{
Q_ASSERT(positionOffset &
gt;=
0
&
amp;&
amp; positionOffset &
lt;=
m_windowLength);
const
int
result =
(qreal(positionOffset) /
m_windowLength) *
rect().width();
return
result;
}
bool
Waveform::
paintTiles()
{
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::paintTiles"
;
bool
updateRequired =
false
;
for
(int
i=
0
; i&
lt;m_tiles.count(); ++
i) {
const
Tile &
amp;tile =
m_tiles[i];
if
(!
tile.painted) {
const
qint64 tileStart =
m_tileArrayStart +
i *
m_tileLength;
const
qint64 tileEnd =
tileStart +
m_tileLength;
if
(m_bufferPosition &
lt;=
tileStart &
amp;&
amp; m_bufferPosition +
m_bufferLength &
gt;=
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 &
lt;&
lt; "Waveform::paintTile"
&
lt;&
lt; "index"
&
lt;&
lt; index
&
lt;&
lt; "bufferPosition"
&
lt;&
lt; m_bufferPosition
&
lt;&
lt; "bufferLength"
&
lt;&
lt; m_bufferLength
&
lt;&
lt; "start"
&
lt;&
lt; tileStart
&
lt;&
lt; "end"
&
lt;&
lt; tileStart +
m_tileLength;
Q_ASSERT(m_bufferPosition &
lt;=
tileStart);
Q_ASSERT(m_bufferPosition +
m_bufferLength &
gt;=
tileStart +
m_tileLength);
Tile &
amp;tile =
m_tiles[index];
Q_ASSERT(!
tile.painted);
const
qint16*
base =
reinterpret_cast
&
lt;const
qint16*&
gt;(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-&
gt;rect(), Qt::
black);
QPen pen(Qt::
white);
painter.setPen(pen);
// Calculate initial PCM value
qint16 previousPcmValue =
0
;
if
(buffer &
gt; 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&
lt;numSamples; ++
i) {
const
qint16*
ptr =
buffer +
i *
m_format.channelCount();
const
int
offset =
reinterpret_cast
&
lt;const
char
*&
gt;(ptr) -
m_buffer.constData();
Q_ASSERT(offset &
gt;=
0
);
Q_ASSERT(offset &
lt; 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 &
lt;&
lt; "Waveform::shuffleTiles"
&
lt;&
lt; "n"
&
lt;&
lt; 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 &
lt;&
lt; "Waveform::shuffleTiles"
&
lt;&
lt; "tileArrayStart"
&
lt;&
lt; m_tileArrayStart;
}
void
Waveform::
resetTiles(qint64 newStartPos)
{
WAVEFORM_DEBUG &
lt;&
lt; "Waveform::resetTiles"
&
lt;&
lt; "newStartPos"
&
lt;&
lt; newStartPos;
QVector&
lt;Tile&
gt;::
iterator i =
m_tiles.begin();
for
( ; i !=
m_tiles.end(); ++
i)
i-&
gt;painted =
false
;
m_tileArrayStart =
newStartPos;
}