aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortrialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38>2019-12-29 21:25:19 +0000
committertrialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38>2019-12-29 21:25:19 +0000
commitf74cd2553f96824bfbfad0d170ecb41fc34eaca6 (patch)
tree76ac81df8c0c9096654cc8ca3d016d60b93b782b
parent8987e9729f4f15c979fa19760aae192377cd1999 (diff)
downloadqmmp-f74cd2553f96824bfbfad0d170ecb41fc34eaca6.tar.gz
qmmp-f74cd2553f96824bfbfad0d170ecb41fc34eaca6.tar.bz2
qmmp-f74cd2553f96824bfbfad0d170ecb41fc34eaca6.zip
qsui: added waveform seek bar
git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@9172 90c681e8-e032-0410-971d-27865f9a5e38
-rw-r--r--src/plugins/Ui/qsui/CMakeLists.txt1
-rw-r--r--src/plugins/Ui/qsui/forms/mainwindow.ui20
-rw-r--r--src/plugins/Ui/qsui/mainwindow.cpp56
-rw-r--r--src/plugins/Ui/qsui/mainwindow.h4
-rw-r--r--src/plugins/Ui/qsui/qsui.pro6
-rw-r--r--src/plugins/Ui/qsui/qsuiwaveformseekbar.cpp340
-rw-r--r--src/plugins/Ui/qsui/qsuiwaveformseekbar.h81
7 files changed, 470 insertions, 38 deletions
diff --git a/src/plugins/Ui/qsui/CMakeLists.txt b/src/plugins/Ui/qsui/CMakeLists.txt
index b6d46c77a..ce4fa0771 100644
--- a/src/plugins/Ui/qsui/CMakeLists.txt
+++ b/src/plugins/Ui/qsui/CMakeLists.txt
@@ -38,6 +38,7 @@ SET(libqsui_SRCS
hotkeyeditor.cpp
volumeslider.cpp
qsuiquicksearch.cpp
+ qsuiwaveformseekbar.cpp
)
SET(libqsui_HDRS
diff --git a/src/plugins/Ui/qsui/forms/mainwindow.ui b/src/plugins/Ui/qsui/forms/mainwindow.ui
index 4e7ed0f4e..315b5a9ac 100644
--- a/src/plugins/Ui/qsui/forms/mainwindow.ui
+++ b/src/plugins/Ui/qsui/forms/mainwindow.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>614</width>
- <height>474</height>
+ <width>629</width>
+ <height>485</height>
</rect>
</property>
<property name="windowTitle">
@@ -23,8 +23,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>614</width>
- <height>27</height>
+ <width>629</width>
+ <height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -127,6 +127,18 @@
</attribute>
<widget class="QWidget" name="dockWidgetContents_4"/>
</widget>
+ <widget class="QDockWidget" name="waveformSeekBarDockWidget">
+ <property name="floating">
+ <bool>false</bool>
+ </property>
+ <property name="windowTitle">
+ <string>Waveform Seek Bar</string>
+ </property>
+ <attribute name="dockWidgetArea">
+ <number>4</number>
+ </attribute>
+ <widget class="QWidget" name="dockWidgetContents_5"/>
+ </widget>
<action name="actionPrevious">
<property name="icon">
<iconset>
diff --git a/src/plugins/Ui/qsui/mainwindow.cpp b/src/plugins/Ui/qsui/mainwindow.cpp
index 421d5cbf5..afa5bbeb8 100644
--- a/src/plugins/Ui/qsui/mainwindow.cpp
+++ b/src/plugins/Ui/qsui/mainwindow.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2009-2019 by Ilya Kotov *
+ * Copyright (C) 2009-2020 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *
@@ -57,6 +57,7 @@
#include "volumeslider.h"
#include "qsuitabwidget.h"
#include "qsuiquicksearch.h"
+#include "qsuiwaveformseekbar.h"
#include "equalizer.h"
#define KEY_OFFSET 10000
@@ -154,6 +155,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
m_analyzer = new QSUIVisualization(this);
m_ui.analyzerDockWidget->setWidget(m_analyzer);
Visual::add(m_analyzer);
+ //waveform seek bar
+ m_seekBar = new QSUIWaveformSeekBar(this);
+ m_ui.waveformSeekBarDockWidget->setWidget(m_seekBar);
//filesystem browser
m_ui.fileSystemDockWidget->setWidget(new FileSystemBrowser(this));
//cover
@@ -886,43 +890,33 @@ void MainWindow::showMetaData()
void MainWindow::setTitleBarsVisible(bool visible)
{
+ QList<QDockWidget *> widgetList = {
+ m_ui.analyzerDockWidget,
+ m_ui.fileSystemDockWidget,
+ m_ui.coverDockWidget,
+ m_ui.playlistsDockWidget,
+ m_ui.waveformSeekBarDockWidget
+ };
+
if(visible)
{
- QWidget *widget = nullptr;
- if((widget = m_ui.analyzerDockWidget->titleBarWidget()))
- {
- m_ui.analyzerDockWidget->setTitleBarWidget(nullptr);
- delete widget;
- }
- if((widget = m_ui.fileSystemDockWidget->titleBarWidget()))
- {
- m_ui.fileSystemDockWidget->setTitleBarWidget(nullptr);
- delete widget;
- }
- if((widget = m_ui.coverDockWidget->titleBarWidget()))
- {
- m_ui.coverDockWidget->setTitleBarWidget(nullptr);
- delete widget;
- }
- if((widget = m_ui.playlistsDockWidget->titleBarWidget()))
+ for(QDockWidget *w : qAsConst(widgetList))
{
- m_ui.playlistsDockWidget->setTitleBarWidget(nullptr);
- delete widget;
+ QWidget *widget = w->titleBarWidget();
+ if(widget)
+ {
+ w->setTitleBarWidget(nullptr);
+ delete widget;
+ }
}
}
else
{
- if(!m_ui.analyzerDockWidget->titleBarWidget())
- m_ui.analyzerDockWidget->setTitleBarWidget(new QWidget());
-
- if(!m_ui.fileSystemDockWidget->titleBarWidget())
- m_ui.fileSystemDockWidget->setTitleBarWidget(new QWidget());
-
- if(!m_ui.coverDockWidget->titleBarWidget())
- m_ui.coverDockWidget->setTitleBarWidget(new QWidget());
-
- if(!m_ui.playlistsDockWidget->titleBarWidget())
- m_ui.playlistsDockWidget->setTitleBarWidget(new QWidget());
+ for(QDockWidget *w : qAsConst(widgetList))
+ {
+ if(!w->titleBarWidget())
+ w->setTitleBarWidget(new QWidget());
+ }
}
}
diff --git a/src/plugins/Ui/qsui/mainwindow.h b/src/plugins/Ui/qsui/mainwindow.h
index f1ea2bb68..e0245a0c5 100644
--- a/src/plugins/Ui/qsui/mainwindow.h
+++ b/src/plugins/Ui/qsui/mainwindow.h
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2009-2019 by Ilya Kotov *
+ * Copyright (C) 2009-2020 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *
@@ -43,6 +43,7 @@ class QSUIVisualization;
class ListWidget;
class QSUiTabWidget;
class QSUiQuickSearch;
+class QSUIWaveformSeekBar;
/**
@@ -118,6 +119,7 @@ private:
QSUiQuickSearch *m_quickSearch;
KeyboardManager *m_key_manager;
QSUIVisualization *m_analyzer;
+ QSUIWaveformSeekBar *m_seekBar;
QToolButton *m_addListButton, *m_tabListMenuButton;
ListWidget *m_listWidget;
MetaDataFormatter m_titleFormatter;
diff --git a/src/plugins/Ui/qsui/qsui.pro b/src/plugins/Ui/qsui/qsui.pro
index 3bafd4311..53bbf5df6 100644
--- a/src/plugins/Ui/qsui/qsui.pro
+++ b/src/plugins/Ui/qsui/qsui.pro
@@ -33,7 +33,8 @@ SOURCES += \
hotkeyeditor.cpp \
volumeslider.cpp \
qsuiquicksearch.cpp \
- qsuivisualization.cpp
+ qsuivisualization.cpp \
+ qsuiwaveformseekbar.cpp
HEADERS += mainwindow.h \
listwidget.h \
visualmenu.h \
@@ -65,7 +66,8 @@ HEADERS += mainwindow.h \
hotkeyeditor.h \
volumeslider.h \
qsuiquicksearch.h \
- qsuivisualization.h
+ qsuivisualization.h \
+ qsuiwaveformseekbar.h
FORMS += forms/mainwindow.ui \
forms/shortcutdialog.ui \
diff --git a/src/plugins/Ui/qsui/qsuiwaveformseekbar.cpp b/src/plugins/Ui/qsui/qsuiwaveformseekbar.cpp
new file mode 100644
index 000000000..5f9622d2a
--- /dev/null
+++ b/src/plugins/Ui/qsui/qsuiwaveformseekbar.cpp
@@ -0,0 +1,340 @@
+/***************************************************************************
+ * 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(trackInfoChanged()), SLOT(onTrackInfoChanged()));
+ 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::onTrackInfoChanged()
+{
+ m_core->trackInfo().path();
+}
+
+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() / (m_data.count() / 3 / m_channels);
+
+ painter.setPen("#BECBFF");
+
+ for(int i = 0; i < m_data.count(); i+=3)
+ {
+ int ch = (i / 3) % m_channels;
+ float x = step * i / 3 / m_channels;
+
+ 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;
+
+ painter.drawLine(x, y1, x, y2);
+
+ }
+ 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;
+
+ painter.drawLine(x, y1, x, y2);
+
+ }
+ 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;
+
+ painter.drawLine(x, y1, x, y2);
+
+ }
+ }
+
+ painter.setPen("#DDDDDD");
+
+ for(int i = 0; i < m_data.count(); i+=3)
+ {
+ int ch = (i / 3) % m_channels;
+ float x = step * i / 3 / m_channels;
+
+ if(ch == 0 && m_channels == 1)
+ {
+ float y3 = height()/2 - m_data[i+2] * (height() / 4) / 1000;
+ float y4 = height()/2 + m_data[i+2] * (height() / 4) / 1000;
+ painter.drawLine(x, y3, x, y4);
+ }
+ else if(ch == 0)
+ {
+ float y3 = height()/4 - m_data[i+2] * (height() / 8) / 1000;
+ float y4 = height()/4 + m_data[i+2] * (height() / 8) / 1000;
+ painter.drawLine(x, y3, x, y4);
+ }
+ else if(ch == 1)
+ {
+ float y3 = 3*height()/4 - m_data[i+2] * (height() / 8) / 1000;
+ float y4 = 3*height()/4 + m_data[i+2] * (height() / 8) / 1000;
+ painter.drawLine(x, y3, x, y4);
+ }
+ }
+
+ if(m_duration > 0)
+ {
+ QColor color(Qt::white);
+ color.setAlpha(120);
+ QBrush brush(color);
+ painter.fillRect(0, 0, width() * m_elapsed / m_duration, height(), brush);
+ }
+}
+
+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 / 4096 * 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();
+}
diff --git a/src/plugins/Ui/qsui/qsuiwaveformseekbar.h b/src/plugins/Ui/qsui/qsuiwaveformseekbar.h
new file mode 100644
index 000000000..2edde1ee7
--- /dev/null
+++ b/src/plugins/Ui/qsui/qsuiwaveformseekbar.h
@@ -0,0 +1,81 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef QSUIWAVEFORMSEEKBAR_H
+#define QSUIWAVEFORMSEEKBAR_H
+
+#include <QWidget>
+#include <QThread>
+#include <QMutex>
+#include <qmmp/audioparameters.h>
+#include <qmmp/qmmp.h>
+
+class SoundCore;
+class Decoder;
+class InputSource;
+class QSUIWaveformScanner;
+
+class QSUIWaveformSeekBar : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit QSUIWaveformSeekBar(QWidget *parent = nullptr);
+ QSize sizeHint() const override;
+
+private slots:
+ void onTrackInfoChanged();
+ void onStateChanged(Qmmp::State state);
+ void onScanFinished();
+ void onElapsedChanged(qint64 elapsed);
+
+private:
+ void paintEvent(QPaintEvent *e) override;
+
+ SoundCore *m_core;
+ QSUIWaveformScanner *m_scanner = nullptr;
+ QList<int> m_data;
+ int m_channels = 0;
+ qint64 m_elapsed = 0;
+ qint64 m_duration = 0;
+};
+
+class QSUIWaveformScanner : public QThread
+{
+ Q_OBJECT
+public:
+ explicit QSUIWaveformScanner(QObject *parent);
+ ~QSUIWaveformScanner();
+ bool scan(const QString &path);
+ void stop();
+ const QList<int> &data() const;
+ const AudioParameters &audioParameters() const;
+
+private:
+ void run() override;
+
+ bool m_user_stop = false;
+ Decoder *m_decoder = nullptr;
+ InputSource *m_input = nullptr;
+ QMutex m_mutex;
+ QList<int> m_data;
+ AudioParameters m_ap;
+};
+
+#endif // QSUIWAVEFORMSEEKBAR_H