diff options
Diffstat (limited to 'src/plugins/Ui')
| -rw-r--r-- | src/plugins/Ui/qsui/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/plugins/Ui/qsui/forms/mainwindow.ui | 20 | ||||
| -rw-r--r-- | src/plugins/Ui/qsui/mainwindow.cpp | 56 | ||||
| -rw-r--r-- | src/plugins/Ui/qsui/mainwindow.h | 4 | ||||
| -rw-r--r-- | src/plugins/Ui/qsui/qsui.pro | 6 | ||||
| -rw-r--r-- | src/plugins/Ui/qsui/qsuiwaveformseekbar.cpp | 340 | ||||
| -rw-r--r-- | src/plugins/Ui/qsui/qsuiwaveformseekbar.h | 81 |
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 |
