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 ,    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 this );
    painter. fillRect(rect(),  Qt :: black);
    if  (m_active) {
        WAVEFORM_PAINT_DEBUG < <  "Waveform::paintEvent" 
                             < <  "windowPosition"  < <  m_windowPosition
                             < <  "windowLength"  < <  m_windowLength;
        qint64 =  m_windowPosition;
        const  qint64 =  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 =  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 =  rect();
                    destRect. setLeft(destLeft);
                    destRect. setRight(destRight);
                    QRect 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 ,  qint64 < <  "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 =  0 ;
    m_format =  QAudioFormat =  false ;
    deletePixmaps();
    m_tiles. clear();
    m_tileLength =  0 ;
    m_tileArrayStart =  0 ;
    m_windowPosition =  0 ;
    m_windowLength =  0 ;
}
void  Waveform:: bufferChanged(qint64 ,  qint64 ,  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 < <  "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 . 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 // 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 < <  "Waveform::setWindowPosition" 
                   < <  "old"  < <  m_windowPosition < <  "new"  < <  position
                   < <  "tileArrayStart"  < <  m_tileArrayStart;
    const  qint64 =  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 =  m_windowPosition -  m_tileArrayStart;
        const  int  nTiles =  offset /  m_tileLength;
        shuffleTiles(nTiles);
    } else  {
        resetTiles(m_windowPosition);
    }
    if  (! paintTiles() & &  m_windowPosition ! =  oldPosition)
        update();
}
qint64 :: tilePosition(int  index) const 
{
    return  m_tileArrayStart +  index *  m_tileLength;
}
Waveform:: TilePoint Waveform:: tilePoint(qint64 const 
{
    TilePoint result;
    if  (position > =  m_tileArrayStart) {
        const  qint64 =  m_tileArrayStart +  m_tiles. count() *  m_tileLength;
        if  (position <  tileArrayEnd) {
            const  qint64 =  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 const 
{
    Q_ASSERT(positionOffset > =  0  & &  positionOffset < =  m_tileLength);
    const  int  result =  (qreal /  m_tileLength) *  m_pixmapSize. width();
    return  result;
}
int  Waveform:: windowPixelOffset(qint64 const 
{
    Q_ASSERT(positionOffset > =  0  & &  positionOffset < =  m_windowLength);
    const  int  result =  (qreal /  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 =  m_tileArrayStart +  i *  m_tileLength;
            const  qint64 =  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 =  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 . pixmap);
    painter. fillRect(tile. pixmap- > rect(),  Qt :: black);
    QPen Qt :: white);
    painter. setPen(pen);
    // Calculate initial PCM value 
    qint16 =  0 ;
    if  (buffer >  base)
        previousPcmValue =  * (buffer -  m_format. channelCount());
    // Calculate initial point 
    const  qreal =  pcmToReal(previousPcmValue);
    const  int  originY =  ((previousRealValue +  1.0 ) /  2 ) *  m_pixmapSize. height();
    const  QPoint 0 ,  originY);
    QLine ,  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 =  * ptr;
        const  qreal =  pcmToReal(pcmValue);
        const  int  x =  tilePixelOffset(i *  2  *  m_format. channelCount());
        const  int  y =  ((realValue +  1.0 ) /  2 ) *  m_pixmapSize. height();
        line. setP2(QPoint ,  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 < <  "Waveform::resetTiles"  < <  "newStartPos"  < <  newStartPos;
    QVector < Tile> :: iterator i =  m_tiles. begin();
    for  ( ; i ! =  m_tiles. end(); + + i)
        i- > painted =  false ;
    m_tileArrayStart =  newStartPos;
}