/*************************************************************************** * Copyright (C) 2012-2021 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 #include #include #include #include #include #include #include #include #include #include "fft.h" #include "inlines.h" #include "qsuivisualization.h" QSUIVisualization::QSUIVisualization(QWidget *parent) : Visual (parent) { m_pixLabel = new QLabel(this); createMenu(); m_timer = new QTimer (this); connect(m_timer, SIGNAL (timeout()), this, SLOT (timeout())); readSettings(); clear(); } QSUIVisualization::~QSUIVisualization() { if(m_drawer) delete m_drawer; } void QSUIVisualization::clear() { if(m_drawer) { m_drawer->clear(); update(); } } void QSUIVisualization::clearCover() { m_cover = QPixmap(); updateCover(); update(); } QSize QSUIVisualization::sizeHint() const { return QSize(200, 100); } void QSUIVisualization::setCover(const QPixmap &pixmap) { m_cover = pixmap; updateCover(); } void QSUIVisualization::timeout() { if(takeData(m_buffer)) { process(); update(); } } void QSUIVisualization::paintEvent (QPaintEvent * e) { if(m_drawer) { QPainter painter (this); painter.fillRect(e->rect(),m_bgColor); m_drawer->draw(&painter, m_offset); } } void QSUIVisualization::hideEvent (QHideEvent *) { m_timer->stop(); } void QSUIVisualization::showEvent (QShowEvent *) { if(m_running) m_timer->start(); } void QSUIVisualization::resizeEvent(QResizeEvent *) { updateCover(); } void QSUIVisualization::process() { if(m_drawer) m_drawer->process(m_buffer, width() - m_offset, height()); } void QSUIVisualization::createMenu() { m_menu = new QMenu (this); connect(m_menu, SIGNAL(triggered (QAction *)),SLOT(writeSettings())); connect(m_menu, SIGNAL(triggered (QAction *)),SLOT(readSettings())); m_coverAction = m_menu->addAction(tr("Cover")); m_coverAction->setCheckable(true); QMenu *visMode = m_menu->addMenu(tr("Visualization Mode")); m_visModeGroup = new QActionGroup(this); m_visModeGroup->setExclusive(true); m_visModeGroup->addAction(tr("Analyzer"))->setData("analyzer"); m_visModeGroup->addAction(tr("Scope"))->setData("scope"); for(QAction *act : m_visModeGroup->actions()) { act->setCheckable(true); visMode->addAction(act); } QMenu *analyzerMode = m_menu->addMenu(tr("Analyzer Mode")); m_analyzerTypeGroup = new QActionGroup(this); m_analyzerTypeGroup->addAction(tr("Cells"))->setData("cells"); m_analyzerTypeGroup->addAction(tr("Lines"))->setData("lines"); for(QAction *act : m_analyzerTypeGroup->actions()) { act->setCheckable(true); analyzerMode->addAction(act); } analyzerMode->addSeparator (); m_peaksAction = analyzerMode->addAction(tr("Peaks")); m_peaksAction->setCheckable(true); QMenu *refreshRate = m_menu->addMenu(tr("Refresh Rate")); m_fpsGroup = new QActionGroup(this); m_fpsGroup->setExclusive(true); m_fpsGroup->addAction(tr("50 fps"))->setData(50); m_fpsGroup->addAction(tr("25 fps"))->setData(25); m_fpsGroup->addAction(tr("10 fps"))->setData(10); m_fpsGroup->addAction(tr("5 fps"))->setData(5); for(QAction *act : m_fpsGroup->actions()) { act->setCheckable(true); refreshRate->addAction(act); } QMenu *analyzerFalloff = m_menu->addMenu(tr("Analyzer Falloff")); m_analyzerFalloffGroup = new QActionGroup(this); m_analyzerFalloffGroup->setExclusive(true); m_analyzerFalloffGroup->addAction(tr("Slowest"))->setData(1.2); m_analyzerFalloffGroup->addAction(tr("Slow"))->setData(1.8); m_analyzerFalloffGroup->addAction(tr("Medium"))->setData(2.2); m_analyzerFalloffGroup->addAction(tr("Fast"))->setData(2.4); m_analyzerFalloffGroup->addAction(tr("Fastest"))->setData(2.8); for(QAction *act : m_analyzerFalloffGroup->actions()) { act->setCheckable(true); analyzerFalloff->addAction(act); } QMenu *peaksFalloff = m_menu->addMenu(tr("Peaks Falloff")); m_peaksFalloffGroup = new QActionGroup(this); m_peaksFalloffGroup->setExclusive(true); m_peaksFalloffGroup->addAction(tr("Slowest"))->setData(0.05); m_peaksFalloffGroup->addAction(tr("Slow"))->setData(0.1); m_peaksFalloffGroup->addAction(tr("Medium"))->setData(0.2); m_peaksFalloffGroup->addAction(tr("Fast"))->setData(0.4); m_peaksFalloffGroup->addAction(tr("Fastest"))->setData(0.8); for(QAction *act : m_peaksFalloffGroup->actions()) { act->setCheckable(true); peaksFalloff->addAction(act); } update(); } void QSUIVisualization::updateCover() { if(m_show_cover && !m_cover.isNull()) { m_offset = height(); m_pixLabel->setGeometry(10,10, height() - 20, height() - 20); m_pixLabel->setPixmap(m_cover.scaled(m_pixLabel->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); m_pixLabel->show(); } else { m_offset = 0; m_pixLabel->hide(); } } void QSUIVisualization::mousePressEvent (QMouseEvent *e) { if (e->button() == Qt::RightButton) m_menu->exec(e->globalPos()); } void QSUIVisualization::readSettings() { QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.beginGroup("Simple"); //general settings m_show_cover = settings.value("vis_show_cover", true).toBool(); m_timer->setInterval(1000 / settings.value("vis_refresh_rate", 25).toInt()); QString visName = settings.value("vis_type","analyzer").toString(); m_bgColor.setNamedColor(settings.value("vis_bg_color", "Black").toString()); //analyzer settings double peaks_falloff = settings.value("vis_peaks_falloff", 0.2).toDouble(); double analyzer_falloff = settings.value("vis_analyzer_falloff", 2.2).toDouble(); bool show_peaks = settings.value("vis_show_peaks", true).toBool(); QString analyzer_type = settings.value("vis_analyzer_type", "cells").toString(); if(!m_update) { m_update = true; m_coverAction->setChecked(m_show_cover); m_peaksAction->setChecked(show_peaks); for(QAction *act : m_visModeGroup->actions()) { if (visName == act->data().toString()) act->setChecked(true); } for(QAction *act : m_fpsGroup->actions ()) { if (m_timer->interval() == 1000 / act->data().toInt()) act->setChecked(true); } for(QAction *act : m_peaksFalloffGroup->actions ()) { if (peaks_falloff == act->data().toDouble()) act->setChecked(true); } for(QAction *act : m_analyzerFalloffGroup->actions ()) { if (analyzer_falloff == act->data().toDouble()) act->setChecked(true); } for(QAction *act : m_analyzerTypeGroup->actions()) { if(analyzer_type == act->data().toString()) act->setChecked(true); } } updateCover(); settings.endGroup(); if(!m_drawer || m_drawer->name() != visName) { if(m_drawer) delete m_drawer; if(visName == "scope") m_drawer = new QSUiScope; else m_drawer = new QSUiAnalyzer; } m_drawer->readSettings(); m_drawer->clear(); } void QSUIVisualization::writeSettings() { QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.beginGroup("Simple"); QAction *act = m_fpsGroup->checkedAction (); settings.setValue("vis_refresh_rate", act ? act->data().toInt() : 25); act = m_peaksFalloffGroup->checkedAction (); settings.setValue("vis_peaks_falloff", act ? act->data().toDouble() : 0.2); act = m_analyzerFalloffGroup->checkedAction (); settings.setValue("vis_analyzer_falloff", act ? act->data().toDouble() : 2.2); settings.setValue("vis_show_peaks", m_peaksAction->isChecked()); settings.setValue("vis_show_cover", m_coverAction->isChecked()); act = m_visModeGroup->checkedAction(); settings.setValue("vis_type", act ? act->data().toString() : "none"); act = m_analyzerTypeGroup->checkedAction(); settings.setValue("vis_analyzer_type", act ? act->data().toString() : "none"); settings.endGroup(); } void QSUIVisualization::start() { m_running = true; if(isVisible()) m_timer->start(); } void QSUIVisualization::stop() { m_running = false; m_timer->stop(); clear(); } QSUiScope::~QSUiScope() { if(m_intern_vis_data) delete [] m_intern_vis_data; } QString QSUiScope::name() const { return "scope"; } void QSUiScope::process(float *buffer, int width, int height) { int step = (QMMP_VISUAL_NODE_SIZE << 8) / width; int pos = 0; if(m_width != width ) { m_width = width; if(m_intern_vis_data) delete [] m_intern_vis_data; m_intern_vis_data = new int[m_width]{ 0 }; } if(m_height != height) { m_height = height; QLinearGradient gradient(0, 0, 0, m_height); gradient.setColorAt(0.1, m_color3); gradient.setColorAt(0.3, m_color2); gradient.setColorAt(0.5, m_color1); gradient.setColorAt(0.7, m_color2); gradient.setColorAt(0.9, m_color3); QBrush brush(gradient); m_pen.setWidthF(1.5); m_pen.setJoinStyle(Qt::RoundJoin); m_pen.setCapStyle(Qt::RoundCap); m_pen.setBrush(brush); } for(int i = 0; i < width - 4; ++i) { pos += step; m_intern_vis_data[i] = int(buffer[pos >> 8] * height / 2); m_intern_vis_data[i] = qBound(-height / 2, m_intern_vis_data[i], height / 2); } } void QSUiScope::draw(QPainter *p, int offset) { p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); for (int i = 0; i < m_width - 5; ++i) { int h1 = m_height / 2 - m_intern_vis_data[i]; int h2 = m_height / 2 - m_intern_vis_data[i+1]; p->setPen(m_pen); p->drawLine(i + offset, h1, offset + i + 1, h2); } } void QSUiScope::clear() { m_width = 0; m_height = 0; } void QSUiScope::readSettings() { QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.beginGroup("Simple"); m_color1.setNamedColor(settings.value("vis_color1", "#BECBFF").toString()); m_color2.setNamedColor(settings.value("vis_color2", "#BECBFF").toString()); m_color3.setNamedColor(settings.value("vis_color3", "#BECBFF").toString()); settings.endGroup(); } QSUiVisualDrawer::~QSUiVisualDrawer() {} QSUiAnalyzer::~QSUiAnalyzer() { if(m_intern_vis_data) delete [] m_intern_vis_data; if(m_peaks) delete [] m_peaks; if(m_x_scale) delete [] m_x_scale; } QString QSUiAnalyzer::name() const { return "analyzer"; } void QSUiAnalyzer::process(float *buffer, int width, int height) { int rows = qMax((height - 2) / m_cell_size.height(),2); int cols = qMax((width - 2) / m_cell_size.width(),1); m_height = height; if(m_rows != rows || m_cols != cols) { m_rows = rows; m_cols = cols; if(m_peaks) delete [] m_peaks; if(m_intern_vis_data) delete [] m_intern_vis_data; if(m_x_scale) delete [] m_x_scale; m_peaks = new double[m_cols]; m_intern_vis_data = new double[m_cols] { 0 }; m_x_scale = new int[m_cols + 1] { 0 }; for(int i = 0; i < m_cols + 1; ++i) m_x_scale[i] = pow(pow(255.0, 1.0 / m_cols), i); QLinearGradient gradient(0, 0, 0, m_height); gradient.setColorAt(0.33, m_color3); gradient.setColorAt(0.66, m_color2); gradient.setColorAt(1.0, m_color1); m_brush = QBrush(gradient); } short dest[256]; calc_freq(dest, buffer); double y_scale = (double) 1.25 * m_rows / log(256); for (int i = 0; i < m_cols; i++) { short y = 0; int magnitude = 0; if(m_x_scale[i] == m_x_scale[i + 1]) { y = dest[i]; } for (int k = m_x_scale[i]; k < m_x_scale[i + 1]; k++) { y = qMax(dest[k], y); } y >>= 7; //256 if (y) { magnitude = int(log (y) * y_scale); magnitude = qBound(0, magnitude, m_rows); } m_intern_vis_data[i] -= m_analyzer_falloff * m_rows / 15; m_intern_vis_data[i] = magnitude > m_intern_vis_data[i] ? magnitude : m_intern_vis_data[i]; if (m_show_peaks) { m_peaks[i] -= m_peaks_falloff * m_rows / 15; m_peaks[i] = magnitude > m_peaks[i] ? magnitude : m_peaks[i]; } } } void QSUiAnalyzer::draw(QPainter *p, int offset) { for (int j = 0; j < m_cols; ++j) { int x = offset + j * m_cell_size.width() + 1; if(m_analyzerType == Cells) { for (int i = 1; i <= m_intern_vis_data[j]; ++i) { p->fillRect(x, m_height - (i - 1) * m_cell_size.height(), m_cell_size.width() - 1, m_cell_size.height() - 4, m_brush); } } else if(m_analyzerType == Lines && m_intern_vis_data[j] > 0) { p->fillRect(x, m_height - 4, m_cell_size.width() - 1, -int(m_intern_vis_data[j] - 1)* m_cell_size.height() + 4, m_brush); } if (m_show_peaks && m_peaks[j] > 1) { p->fillRect(x, m_height - int(m_peaks[j] - 1) * m_cell_size.height(), m_cell_size.width() - 1, m_cell_size.height() - 4, m_peakColor); } } } void QSUiAnalyzer::clear() { m_rows = 0; m_cols = 0; } void QSUiAnalyzer::readSettings() { QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.beginGroup("Simple"); m_color1.setNamedColor(settings.value("vis_color1", "#BECBFF").toString()); m_color2.setNamedColor(settings.value("vis_color2", "#BECBFF").toString()); m_color3.setNamedColor(settings.value("vis_color3", "#BECBFF").toString()); m_peakColor.setNamedColor(settings.value("vis_peak_color", "#DDDDDD").toString()); m_cell_size = QSize(14, 8); m_peaks_falloff = settings.value("vis_peaks_falloff", 0.2).toDouble(); m_analyzer_falloff = settings.value("vis_analyzer_falloff", 2.2).toDouble(); m_show_peaks = settings.value("vis_show_peaks", true).toBool(); QString analyzer_type = settings.value("vis_analyzer_type", "cells").toString(); if(analyzer_type == "lines") m_analyzerType = Lines; else m_analyzerType = Cells; settings.endGroup(); }