aboutsummaryrefslogblamecommitdiff
path: root/src/plugins/Ui/qsui/qsuiwaveformseekbar.cpp
blob: 01f3aac4088a1eb28e267e45aa1e3fd40a63ed79 (plain) (tree)


































                                                                             








                                                                                          






























































                                                                           
                                                                 
 

                                        

                                                            

                                      


                                                   



                                                                        

                                                                        
 





                                 
 
                                           



                                                                      











                                                                                           



                                                                        











                                                                                             



                              
                                        
 
                                                            

                                      


                                                   

                                      












                                                                          


                        












                                                                                           


                        












                                                                                             

         










                                                                                                          













































































































                                                                                                        
                                                               














































                                                                                                        
/***************************************************************************
 *   Copyright (C) 2020 by Ilya Kotov                                      *
 *   forkotov02@ya.ru                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <QPainter>
#include <QPaintEvent>
#include <QtDebug>
#include <cmath>
#include <qmmp/soundcore.h>
#include <qmmp/inputsource.h>
#include <qmmp/decoder.h>
#include <qmmp/decoderfactory.h>
#include <qmmp/audioconverter.h>
#include <qmmp/buffer.h>
#include "qsuiwaveformseekbar.h"

QSUIWaveformSeekBar::QSUIWaveformSeekBar(QWidget *parent) : QWidget(parent)
{
    m_core = SoundCore::instance();
    connect(m_core, SIGNAL(stateChanged(Qmmp::State)), SLOT(onStateChanged(Qmmp::State)));
    connect(m_core, SIGNAL(elapsedChanged(qint64)), SLOT(onElapsedChanged(qint64)));
}

QSize QSUIWaveformSeekBar::sizeHint() const
{
    return QSize(200, 100);
}

void QSUIWaveformSeekBar::onStateChanged(Qmmp::State state)
{
    switch (state)
    {
    case Qmmp::Playing:
    {
        if(!m_scanner)
        {
            m_scanner = new QSUIWaveformScanner(this);
            connect(m_scanner, SIGNAL(finished()), SLOT(onScanFinished()));
        }
        m_scanner->scan(m_core->path());
    }
        break;
    case Qmmp::Stopped:
    case Qmmp::FatalError:
    case Qmmp::NormalError:
    {
        if(m_scanner)
        {
            m_scanner->stop();
            delete m_scanner;
            m_scanner = nullptr;
        }
        m_data.clear();
        m_elapsed = 0;
        m_duration = 0;
        update();
    }
        break;
    default:
        break;
    }
}

void QSUIWaveformSeekBar::onScanFinished()
{
    if(!m_scanner)
        return;

    m_data = m_scanner->data();
    m_channels = m_scanner->audioParameters().channels();
    delete m_scanner;
    m_scanner = nullptr;
    update();
}

void QSUIWaveformSeekBar::onElapsedChanged(qint64 elapsed)
{
    m_elapsed = elapsed;
    m_duration = m_core->duration();
    update();
}

void QSUIWaveformSeekBar::paintEvent(QPaintEvent *e)
{
    QPainter painter (this);
    painter.fillRect(e->rect(), Qt::black);
    painter.setPen("#BECBFF");

    if(m_data.isEmpty())
        return;

    float step = float(width()) * 3 * m_channels / m_data.size();

    painter.setPen("#BECBFF");
    painter.setBrush(QColor("#BECBFF"));

    for(int i = 0; i < m_data.size() - m_channels * 3; i+=3)
    {
        int ch = (i / 3) % m_channels;
        float x1 = step * (i / m_channels / 3);
        float x2 = step * (i / m_channels / 3 + 1);

        if(ch == 0 && m_channels == 1)
        {
            float y1 = height()/2 - m_data[i] * (height() / 4) / 1000;
            float y2 = height()/2 - m_data[i+1] * (height() / 4) / 1000;
            float y3 = height()/2 - m_data[i+3] * (height() / 4) / 1000;
            float y4 = height()/2 - m_data[i+4] * (height() / 4) / 1000;

            QPointF points[4] = {
                { x1, y1 },
                { x1, y2 },
                { x2, y4 },
                { x2, y3 }
            };

            painter.drawPolygon(points, 4);
        }
        else if(ch == 0)
        {
            float y1 = height()/4 - m_data[i] * (height() / 8) / 1000;
            float y2 = height()/4 - m_data[i + 1] * (height() / 8) / 1000;
            float y3 = height()/4 - m_data[i + m_channels * 3] * (height() / 8) / 1000;
            float y4 = height()/4 - m_data[i + m_channels * 3 + 1] * (height() / 8) / 1000;

            QPointF points[4] = {
                { x1, y1 },
                { x1, y2 },
                { x2, y4 },
                { x2, y3 }
            };

            painter.drawPolygon(points, 4);
        }
        else if(ch == 1)
        {
            float y1 = 3*height()/4 - m_data[i] * (height() / 8) / 1000;
            float y2 = 3*height()/4 - m_data[i + 1] * (height() / 8) / 1000;
            float y3 = 3*height()/4 - m_data[i + m_channels * 3] * (height() / 8) / 1000;
            float y4 = 3*height()/4 - m_data[i + m_channels * 3 + 1] * (height() / 8) / 1000;

            QPointF points[4] = {
                { x1, y1 },
                { x1, y2 },
                { x2, y4 },
                { x2, y3 }
            };

            painter.drawPolygon(points, 4);
        }
    }

    painter.setPen("#DDDDDD");
    painter.setBrush(QColor("#DDDDDD"));

    for(int i = 0; i < m_data.size() - m_channels * 3; i+=3)
    {
        int ch = (i / 3) % m_channels;
        float x1 = step * (i / m_channels / 3);
        float x2 = step * (i / m_channels / 3 + 1);

        if(ch == 0 && m_channels == 1)
        {
            float y1 = height()/2 - m_data[i + 2] * (height() / 4) / 1000;
            float y2 = height()/2 + m_data[i + 2] * (height() / 4) / 1000;
            float y3 = height()/2 - m_data[i + 5] * (height() / 4) / 1000;
            float y4 = height()/2 + m_data[i + 5] * (height() / 4) / 1000;

            QPointF points[4] = {
                { x1, y1 },
                { x1, y2 },
                { x2, y4 },
                { x2, y3 }
            };

            painter.drawPolygon(points, 4);
        }
        else if(ch == 0)
        {
            float y1 = height()/4 - m_data[i + 2] * (height() / 8) / 1000;
            float y2 = height()/4 + m_data[i + 2] * (height() / 8) / 1000;
            float y3 = height()/4 - m_data[i + m_channels * 3 + 2] * (height() / 8) / 1000;
            float y4 = height()/4 + m_data[i + m_channels * 3 + 2] * (height() / 8) / 1000;

            QPointF points[4] = {
                { x1, y1 },
                { x1, y2 },
                { x2, y4 },
                { x2, y3 }
            };

            painter.drawPolygon(points, 4);
        }
        else if(ch == 1)
        {
            float y1 = 3*height()/4 - m_data[i + 2] * (height() / 8) / 1000;
            float y2 = 3*height()/4 + m_data[i + 2] * (height() / 8) / 1000;
            float y3 = 3*height()/4 - m_data[i + m_channels * 3 + 2] * (height() / 8) / 1000;
            float y4 = 3*height()/4 + m_data[i + m_channels * 3 + 2] * (height() / 8) / 1000;

            QPointF points[4] = {
                { x1, y1 },
                { x1, y2 },
                { x2, y4 },
                { x2, y3 }
            };

            painter.drawPolygon(points, 4);
        }
    }

    if(m_duration > 0)
    {
        QColor color(Qt::magenta);
        color.setAlpha(150);
        QBrush brush(color);
        painter.fillRect(0, 0, width() * m_elapsed / m_duration, height(), brush);
        color.setAlpha(255);
        painter.setPen(color);
        painter.drawLine(width() * m_elapsed / m_duration, 0, width() * m_elapsed / m_duration, height());
    }
}

QSUIWaveformScanner::QSUIWaveformScanner(QObject *parent) : QThread(parent)
{

}

QSUIWaveformScanner::~QSUIWaveformScanner()
{
    stop();
}

bool QSUIWaveformScanner::scan(const QString &path)
{
    InputSource *source = InputSource::create(path, this);
    if(!source->initialize())
    {
        delete source;
        qWarning("QSUIWaveformScanner: invalid path");
        return false;
    }

    if(source->ioDevice() && !source->ioDevice()->open(QIODevice::ReadOnly))
    {
        source->deleteLater();
        qWarning("QSUIWaveformScanner: cannot open input stream, error: %s",
                 qPrintable(source->ioDevice()->errorString()));
        return false;

    }

    DecoderFactory *factory = nullptr;

    if(!source->path().contains("://"))
        factory = Decoder::findByFilePath(source->path());
    if(!factory)
        factory = Decoder::findByMime(source->contentType());
    if(!factory && source->ioDevice() && source->path().contains("://")) //ignore content of local files
        factory = Decoder::findByContent(source->ioDevice());
    if(!factory && source->path().contains("://"))
        factory = Decoder::findByProtocol(source->path().section("://",0,0));
    if(!factory)
    {
        qWarning("QSUIWaveformScanner: unsupported file format");
        source->deleteLater();
        return false;
    }
    qDebug("QSUIWaveformScanner: selected decoder: %s",qPrintable(factory->properties().shortName));
    if(factory->properties().noInput && source->ioDevice())
        source->ioDevice()->close();
    Decoder *decoder = factory->create(source->path(), source->ioDevice());
    if(!decoder->initialize())
    {
        qWarning("QSUIWaveformScanner: invalid file format");
        source->deleteLater();
        delete decoder;
        return false;
    }
    m_decoder = decoder;
    m_input = source;
    if(!decoder->totalTime())
        source->setOffset(-1);
    m_user_stop = false;
    start();
    return true;
}

void QSUIWaveformScanner::stop()
{
    if(isRunning())
    {
        m_mutex.lock();
        m_user_stop = true;
        m_mutex.unlock();
        wait();
    }

    if(m_decoder)
    {
        delete m_decoder;
        m_decoder = nullptr;
    }

    if(m_input)
    {
        delete m_input;
        m_input = nullptr;
    }
}

const QList<int> &QSUIWaveformScanner::data() const
{
    return m_data;
}

const AudioParameters &QSUIWaveformScanner::audioParameters() const
{
    return m_ap;
}

void QSUIWaveformScanner::run()
{
    m_ap = m_decoder->audioParameters();
    unsigned char tmp[QMMP_BLOCK_FRAMES * m_ap.frameSize() * 4];
    float out[QMMP_BLOCK_FRAMES * m_ap.channels() * sizeof(float)];
    AudioConverter converter;
    converter.configure(m_ap.format());
    m_data.clear();

    qint64 frames = m_decoder->totalTime() * m_ap.sampleRate() / 1000;
    int samplesForCalculation = frames / 512 * m_ap.channels();

    m_mutex.lock();
    float max[m_ap.channels()] = { -1.0 }, min[m_ap.channels()] = { 1.0 }, rms[m_ap.channels()] = { 0 };
    int counter = 0;
    while (!m_user_stop)
    {
        m_mutex.unlock();
        qint64 len = m_decoder->read(tmp, sizeof(tmp));
        if(len > 0)
        {
            converter.toFloat(tmp, out, len / m_ap.sampleSize());

            for(uint sample = 0; sample < len / sizeof(float); sample++)
            {
                int ch = sample % m_ap.channels();
                min[ch] = qMin(min[ch], out[sample]);
                max[ch] = qMax(max[ch], out[sample]);
                rms[ch] += (out[sample] * out[sample]);

                counter++;
                if(counter >= samplesForCalculation)
                {
                    for(int ch = 0; ch < m_ap.channels(); ++ch)
                    {
                        m_data << max[ch] * 1000;
                        m_data << min[ch] * 1000;
                        m_data << std::sqrt(rms[ch] / (counter / m_ap.channels())) * 1000;
                        max[ch] = -1.0;
                        min[ch] = 1.0;
                        rms[ch] = 0;
                    }
                    counter = 0;
                }
            }
        }
        else
        {
            m_mutex.lock();
            qDebug("finished! %d", m_data.count());
            break;
        }

        m_mutex.lock();
    }

    m_mutex.unlock();
}