From f5b1e1d7045b310e463586e29dc181ac1211c766 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Thu, 25 Jul 2019 15:49:32 +0200 Subject: [PATCH 01/17] first rough vectorscope structure draft --- plugins/Vectorscope/CMakeLists.txt | 5 ++ plugins/Vectorscope/README.md | 15 ++++ plugins/Vectorscope/VecControls.cpp | 67 +++++++++++++++ plugins/Vectorscope/VecControls.h | 62 ++++++++++++++ plugins/Vectorscope/VecControlsDialog.cpp | 61 +++++++++++++ plugins/Vectorscope/VecControlsDialog.h | 47 ++++++++++ plugins/Vectorscope/VectorView.cpp | 99 ++++++++++++++++++++++ plugins/Vectorscope/VectorView.h | 53 ++++++++++++ plugins/Vectorscope/Vectorscope.cpp | 74 ++++++++++++++++ plugins/Vectorscope/Vectorscope.h | 49 +++++++++++ plugins/Vectorscope/logo.png | Bin 0 -> 774 bytes 11 files changed, 532 insertions(+) create mode 100644 plugins/Vectorscope/CMakeLists.txt create mode 100644 plugins/Vectorscope/README.md create mode 100644 plugins/Vectorscope/VecControls.cpp create mode 100644 plugins/Vectorscope/VecControls.h create mode 100644 plugins/Vectorscope/VecControlsDialog.cpp create mode 100644 plugins/Vectorscope/VecControlsDialog.h create mode 100644 plugins/Vectorscope/VectorView.cpp create mode 100644 plugins/Vectorscope/VectorView.h create mode 100644 plugins/Vectorscope/Vectorscope.cpp create mode 100644 plugins/Vectorscope/Vectorscope.h create mode 100644 plugins/Vectorscope/logo.png diff --git a/plugins/Vectorscope/CMakeLists.txt b/plugins/Vectorscope/CMakeLists.txt new file mode 100644 index 00000000000..4ad76b6034d --- /dev/null +++ b/plugins/Vectorscope/CMakeLists.txt @@ -0,0 +1,5 @@ +INCLUDE(BuildPlugin) +INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS}) +LINK_LIBRARIES(${FFTW3F_LIBRARIES}) +BUILD_PLUGIN(vectorscope Vectorscope.cpp VecControls.cpp VecControlsDialog.cpp VectorView.cpp +MOCFILES VecControls.h VecControlsDialog.h VectorView.h EMBEDDED_RESOURCES *.svg logo.png) diff --git a/plugins/Vectorscope/README.md b/plugins/Vectorscope/README.md new file mode 100644 index 00000000000..e0741241f33 --- /dev/null +++ b/plugins/Vectorscope/README.md @@ -0,0 +1,15 @@ +# Vectorscope plugin + +## Overview + +This plugin consists of one widget and back-end code to provide it with required data. + +The top-level widget is VecControlDialog. It displays configuration knobs and the VectorView widget. The back-end class for its configuration is VecControls, which holds all configuration values. + +VectorView computes and shows the visualization results. Data for processing are received from the Vectorscope class, which handles the interface with LMMS. + + +## Changelog + + 1.0.0 2019-00-00 + - initial release diff --git a/plugins/Vectorscope/VecControls.cpp b/plugins/Vectorscope/VecControls.cpp new file mode 100644 index 00000000000..2c78a1b0e41 --- /dev/null +++ b/plugins/Vectorscope/VecControls.cpp @@ -0,0 +1,67 @@ +/* + * VecControls.cpp - definition of VecControls class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "VecControls.h" + +#include + +#include "Vectorscope.h" +#include "VecControlsDialog.h" + + +VecControls::VecControls(Vectorscope *effect) : + EffectControls(effect), + m_effect(effect), + + // initialize models and set default values + m_persistenceModel(0.5f, 0.0f, 1.0f, 0.05f, this, tr("Display persistence amount")), + m_logarithmicModel(false, this, tr("Logarithmic scale")) +{ + // Colors + m_colorFG = QColor(51, 148, 204, 204); + m_colorBG = QColor(7, 7, 7, 255); // ~20 % gray (after gamma correction) + m_colorGrid = QColor(30, 34, 38, 255); // ~40 % gray (slightly cold / blue) + m_colorLabels = QColor(192, 202, 212, 255); // ~90 % gray (slightly cold / blue) +} + + +// Create the VecControlDialog widget which handles display of GUI elements. +EffectControlDialog* VecControls::createView() +{ + return new VecControlsDialog(this); +} + + +void VecControls::loadSettings(const QDomElement &_this) +{ + m_persistenceModel.loadSettings(_this, "Persistence"); + m_logarithmicModel.loadSettings(_this, "Logarithmic"); +} + + +void VecControls::saveSettings(QDomDocument &doc, QDomElement &parent) +{ + m_persistenceModel.saveSettings(doc, parent, "Persistence"); + m_logarithmicModel.saveSettings(doc, parent, "Logarithmic"); +} diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h new file mode 100644 index 00000000000..b8d31d74bd1 --- /dev/null +++ b/plugins/Vectorscope/VecControls.h @@ -0,0 +1,62 @@ +/* + * VecControls.h - declaration of VecControls class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef VECCONTROLS_H +#define VECCONTROLS_H + +#include "EffectControls.h" + +class Vectorscope; + +// Holds all the configuration values +class VecControls : public EffectControls +{ + Q_OBJECT +public: + explicit VecControls(Vectorscope* effect); + virtual ~VecControls() {} + + EffectControlDialog* createView() override; + + void saveSettings (QDomDocument& doc, QDomElement& parent) override; + void loadSettings (const QDomElement &_this) override; + + QString nodeName() const override {return "Vectorscope";} + int controlCount() override {return 2;} + +private: + Vectorscope *m_effect; + + FloatModel m_persistence; + BoolModel m_logarithmic; + + QColor m_colorFG; + QColor m_colorBG; + QColor m_colorGrid; + QColor m_colorLabels; + + friend class VecControlsDialog; + friend class VectorView; +}; +#endif // VECCONTROLS_H diff --git a/plugins/Vectorscope/VecControlsDialog.cpp b/plugins/Vectorscope/VecControlsDialog.cpp new file mode 100644 index 00000000000..1666cb5d702 --- /dev/null +++ b/plugins/Vectorscope/VecControlsDialog.cpp @@ -0,0 +1,61 @@ +/* + * VecControlsDialog.cpp - definition of VecControlsDialog class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "VecControlsDialog.h" + +#include +#include +#include + +#include "embed.h" +#include "LedCheckbox.h" +#include "VecControls.h" + + +// The entire GUI layout is built here. +VecControlsDialog::VecControlsDialog(VecControls *controls) : + EffectControlDialog(controls), + m_controls(controls) +{ + LedCheckBox *logarithmicButton = new LedCheckBox(tr("Logarithmic scale"), this); + logarithmicButton->setToolTip(tr("Display amplitude on logarithmic scale to better see small values")); + logarithmicButton->setCheckable(true); + logarithmicButton->setMinimumSize(70, 12); + logarithmicButton->setModel(&controls->m_waterfallModel); + +// anchor to left / top corner + config_layout->addWidget(logarithmicButton, 0, 1); + +// persistence knob + + VectorView *display = new VectorView(controls, this); + +} + + +// Suggest the best current widget size. +QSize VecControlsDialog::sizeHint() const +{ + return QSize(300, 200); +} diff --git a/plugins/Vectorscope/VecControlsDialog.h b/plugins/Vectorscope/VecControlsDialog.h new file mode 100644 index 00000000000..b76c06ad001 --- /dev/null +++ b/plugins/Vectorscope/VecControlsDialog.h @@ -0,0 +1,47 @@ +/* + * VecControlsDialog.h - declatation of VecControlsDialog class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef VECCONTROLSDIALOG_H +#define VECCONTROLSDIALOG_H + +#include "EffectControlDialog.h" + +class VecControls; + +//! Top-level widget holding the configuration GUI and vector display +class VecControlsDialog : public EffectControlDialog +{ + Q_OBJECT +public: + explicit VecControlsDialog(VecControls *controls); + virtual ~VecControlsDialog() {} + + bool isResizable() const override {return true;} + QSize sizeHint() const override; + +private: + VecControls *m_controls; +}; + +#endif // VECCONTROLSDIALOG_H diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp new file mode 100644 index 00000000000..cc386d405ff --- /dev/null +++ b/plugins/Vectorscope/VectorView.cpp @@ -0,0 +1,99 @@ +/* VectorViewView.cpp - implementation of VectorViewView class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "VectorView.h" + +#include +#include + + +VectorView::VectorView(SaControls *controls, QWidget *_parent) : + QWidget(_parent), + m_controls(controls) +{ + setMinimumSize(300, 150); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + connect(gui->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(periodicUpdate())); + +} + + +// Compose and draw all the content; called by Qt. +void VectorView::paintEvent(QPaintEvent *event) +{ + #ifdef VEC_DEBUG + int start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + #endif + + // all drawing done here, local variables are sufficient for the boundary + const int displayTop = 1; + const int displayBottom = height() -2; + const int displayLeft = 26; + const int displayRight = width() -26; + const int displayWidth = displayRight - displayLeft; + const int displayHeight = displayBottom - displayTop; + float label_width = 20; + float label_height = 16; + float margin = 2; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // get buffer from Vectorscope using a lockless FIFO + + // dim stored image based on persistence and elapsed time + + // add new points + + // draw the final image + painter.drawImage(displayLeft, displayTop, // top left corner coordinates + QImage(m_displayBuffer.data(), // raw pixel data to display + bufferWidth, + bufferHeight, + QImage::Format_RGB32 + ).scaled(displayWidth, // scale to fit view.. + displayHeight, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + + // always draw the outline + painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.drawRoundedRect(displayLeft, displayTop, displayWidth, displayBottom, 2.0, 2.0); + + #ifdef VEC_DEBUG + // display what FPS would be achieved if vectorscope ran in a loop + start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - start_time; + painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.drawText(displayRight -100, 10, 100, 16, Qt::AlignLeft, + QString(std::string("Max FPS: " + std::to_string(1000000000.0 / start_time)).c_str())); + #endif +} + + +// Periodically trigger repaint and check if the widget is visible. +void VectorView::periodicUpdate() +{ + m_visible = isVisible(); + if (m_visible) {update();} +} + diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h new file mode 100644 index 00000000000..09b0b1e6179 --- /dev/null +++ b/plugins/Vectorscope/VectorView.h @@ -0,0 +1,53 @@ +/* VectorView.h - declaration of VectorView class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ +#ifndef VECTORVIEW_H +#define VECTORVIEW_H + +#include +#include + +#include "VecControls.h" + + +// Widget that displays a vectorscope visualization of stereo signal. +class VectorView : public QWidget +{ + Q_OBJECT +public: + explicit VectorView(VecControls *controls, QWidget *_parent = 0); + virtual ~VectorView() {} + + QSize sizeHint() const override {return QSize(300, 200);} + +protected: + void paintEvent(QPaintEvent *event) override; + +private slots: + void periodicUpdate(); + +private: + VecControls *m_controls; + + bool m_visible; +}; +#endif // VECTORVIEW_H diff --git a/plugins/Vectorscope/Vectorscope.cpp b/plugins/Vectorscope/Vectorscope.cpp new file mode 100644 index 00000000000..2429d171184 --- /dev/null +++ b/plugins/Vectorscope/Vectorscope.cpp @@ -0,0 +1,74 @@ +/* + * Vectorscope.cpp - definition of Vectorscope class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "Vectorscope.h" + +#include "embed.h" +#include "plugin_export.h" + + +extern "C" { + Plugin::Descriptor PLUGIN_EXPORT vectorscope_plugin_descriptor = + { + STRINGIFY(PLUGIN_NAME), + "Vectorscope", + QT_TRANSLATE_NOOP("pluginBrowser", "A stereo field visualizer."), + "Martin Pavelek ", + 0x0100, + Plugin::Effect, + new PluginPixmapLoader("logo"), + NULL, + NULL + }; +} + + +Vectorscope::Vectorscope(Model *parent, const Plugin::Descriptor::SubPluginFeatures::Key *key) : + Effect(&vectorscope_plugin_descriptor, parent, key), + m_controls(this) +{ +} + + +// Take audio data and save them to a list for processing when redraw comes. +// Skip processing if the controls dialog isn't visible, it would only waste CPU cycles. +bool Vectorscope::processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) +{ + if (!isEnabled() || !isRunning ()) {return false;} + if (m_controls.isViewVisible()) + { + m_dataBuffer.insert(m_dataBuffer.end(), buffer, buffer + frame_count); + } + return isRunning(); +} + + +extern "C" { + // needed for getting plugin out of shared lib + PLUGIN_EXPORT Plugin *lmms_plugin_main(Model *parent, void *data) + { + return new Vectorscope(parent, static_cast(data)); + } +} + diff --git a/plugins/Vectorscope/Vectorscope.h b/plugins/Vectorscope/Vectorscope.h new file mode 100644 index 00000000000..40cdd5f7bf8 --- /dev/null +++ b/plugins/Vectorscope/Vectorscope.h @@ -0,0 +1,49 @@ +/* Vectorscope.h - declaration of Vectorscope class. + * + * Copyright (c) 2019 Martin Pavelek + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef VECTORSCOPE_H +#define VECTORSCOPE_H + +#include + +#include "Effect.h" +#include "VecControls.h" + + +//! Top level class; handles LMMS interface and accumulates data for processing. +class Vectorscope : public Effect +{ +public: + Vectorscope(Model *parent, const Descriptor::SubPluginFeatures::Key *key); + virtual ~Vectorscope() {}; + + bool processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) override; + EffectControls *controls() override {return &m_controls;} + +private: + SaControls m_controls; + std::list m_dataBuffer; +}; + +#endif // VECTORSCOPE_H + diff --git a/plugins/Vectorscope/logo.png b/plugins/Vectorscope/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9340da708dd79ed97111eb535f51b81a91d6a15b GIT binary patch literal 774 zcmV+h1Nr=kP)7WEc)VQ)zLm`B#lSD% z0Wg;#IH?7|3b0p&fj_`2;A{cm@zx+(Ge?s$@EN$6LtMjg>;+(boCbaXw;ja=b6mQ?gRp67Yf18(18niCN0v&eY^{Cr&#;#IcF{ks?!* z&o!_q>9xbSw`QytRI!M?zP_o#K-8ExJaV?vi znPt?~0KhTu23U+Gy8^Tw;^SzWSet9n Date: Sat, 9 Nov 2019 23:45:08 +0100 Subject: [PATCH 02/17] Working prototype --- cmake/modules/PluginList.cmake | 1 + plugins/Vectorscope/VecControls.cpp | 5 +- plugins/Vectorscope/VecControls.h | 9 +- plugins/Vectorscope/VecControlsDialog.cpp | 46 +++++++-- plugins/Vectorscope/VectorView.cpp | 119 +++++++++++++++++----- plugins/Vectorscope/VectorView.h | 22 +++- plugins/Vectorscope/Vectorscope.cpp | 14 ++- plugins/Vectorscope/Vectorscope.h | 11 +- 8 files changed, 179 insertions(+), 48 deletions(-) diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index c82bba3291a..2d853038873 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -64,6 +64,7 @@ SET(LMMS_PLUGIN_LIST VstEffect watsyn waveshaper + Vectorscope vibed Xpressive zynaddsubfx diff --git a/plugins/Vectorscope/VecControls.cpp b/plugins/Vectorscope/VecControls.cpp index 2c78a1b0e41..37c82a82322 100644 --- a/plugins/Vectorscope/VecControls.cpp +++ b/plugins/Vectorscope/VecControls.cpp @@ -36,7 +36,8 @@ VecControls::VecControls(Vectorscope *effect) : // initialize models and set default values m_persistenceModel(0.5f, 0.0f, 1.0f, 0.05f, this, tr("Display persistence amount")), - m_logarithmicModel(false, this, tr("Logarithmic scale")) + m_logarithmicModel(false, this, tr("Logarithmic scale")), + m_fullViewModel(false, this, tr("Full view")) { // Colors m_colorFG = QColor(51, 148, 204, 204); @@ -57,6 +58,7 @@ void VecControls::loadSettings(const QDomElement &_this) { m_persistenceModel.loadSettings(_this, "Persistence"); m_logarithmicModel.loadSettings(_this, "Logarithmic"); + m_fullViewModel.loadSettings(_this, "FullView"); } @@ -64,4 +66,5 @@ void VecControls::saveSettings(QDomDocument &doc, QDomElement &parent) { m_persistenceModel.saveSettings(doc, parent, "Persistence"); m_logarithmicModel.saveSettings(doc, parent, "Logarithmic"); + m_fullViewModel.saveSettings(doc, parent, "FullView"); } diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h index b8d31d74bd1..a9257430dab 100644 --- a/plugins/Vectorscope/VecControls.h +++ b/plugins/Vectorscope/VecControls.h @@ -27,6 +27,8 @@ #include "EffectControls.h" +#include + class Vectorscope; // Holds all the configuration values @@ -43,13 +45,14 @@ class VecControls : public EffectControls void loadSettings (const QDomElement &_this) override; QString nodeName() const override {return "Vectorscope";} - int controlCount() override {return 2;} + int controlCount() override {return 3;} private: Vectorscope *m_effect; - FloatModel m_persistence; - BoolModel m_logarithmic; + FloatModel m_persistenceModel; + BoolModel m_logarithmicModel; + BoolModel m_fullViewModel; QColor m_colorFG; QColor m_colorBG; diff --git a/plugins/Vectorscope/VecControlsDialog.cpp b/plugins/Vectorscope/VecControlsDialog.cpp index 1666cb5d702..9f2ea535439 100644 --- a/plugins/Vectorscope/VecControlsDialog.cpp +++ b/plugins/Vectorscope/VecControlsDialog.cpp @@ -24,6 +24,7 @@ #include "VecControlsDialog.h" +#include #include #include #include @@ -31,6 +32,8 @@ #include "embed.h" #include "LedCheckbox.h" #include "VecControls.h" +#include "Vectorscope.h" +#include "VectorView.h" // The entire GUI layout is built here. @@ -38,24 +41,51 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : EffectControlDialog(controls), m_controls(controls) { - LedCheckBox *logarithmicButton = new LedCheckBox(tr("Logarithmic scale"), this); + QVBoxLayout *master_layout = new QVBoxLayout; + setLayout(master_layout); + + // Visualizer widget + VectorView *display = new VectorView(controls, m_controls->m_effect->getBuffer(), 512, this); + master_layout->addWidget(display); + + // Config area located inside visualizer + QVBoxLayout *internal_layout = new QVBoxLayout(display); + QHBoxLayout *config_layout = new QHBoxLayout(); + QVBoxLayout *switch_layout = new QVBoxLayout(); + internal_layout->addStretch(); + internal_layout->addLayout(config_layout); + config_layout->addLayout(switch_layout); + + // Log. scale switch + LedCheckBox *logarithmicButton = new LedCheckBox(tr("Log. scale"), this); logarithmicButton->setToolTip(tr("Display amplitude on logarithmic scale to better see small values")); logarithmicButton->setCheckable(true); logarithmicButton->setMinimumSize(70, 12); - logarithmicButton->setModel(&controls->m_waterfallModel); - -// anchor to left / top corner - config_layout->addWidget(logarithmicButton, 0, 1); + logarithmicButton->setModel(&controls->m_logarithmicModel); + switch_layout->addWidget(logarithmicButton); -// persistence knob + // Full-view switch + LedCheckBox *fullViewButton = new LedCheckBox(tr("Full view"), this); + fullViewButton->setToolTip(tr("Display amplitude on logarithmic scale to better see small values")); + fullViewButton->setCheckable(true); + fullViewButton->setMinimumSize(70, 12); + fullViewButton->setModel(&controls->m_fullViewModel); + switch_layout->addWidget(fullViewButton); - VectorView *display = new VectorView(controls, this); + config_layout->addStretch(); + // Persistence knob + Knob *persistenceKnob = new Knob(knobSmall_17, this); + persistenceKnob->setModel(&controls->m_persistenceModel); + persistenceKnob->setLabel(tr("Persist.")); + persistenceKnob->setToolTip(tr("Trace persistence: higher amount means the trace will stay bright for longer time.")); + persistenceKnob->setHintText(tr("Trace persistence"), ""); + config_layout->addWidget(persistenceKnob); } // Suggest the best current widget size. QSize VecControlsDialog::sizeHint() const { - return QSize(300, 200); + return QSize(300, 300); } diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index cc386d405ff..d2251d9d2eb 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -22,19 +22,33 @@ #include "VectorView.h" +#include #include #include +#include "GuiApplication.h" +#include "MainWindow.h" -VectorView::VectorView(SaControls *controls, QWidget *_parent) : +#define VEC_DEBUG +#include + +VectorView::VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *_parent) : QWidget(_parent), - m_controls(controls) + m_controls(controls), + m_inputBuffer(inputBuffer), + m_bufferReader(*inputBuffer), + m_displaySize(displaySize) { - setMinimumSize(300, 150); + setMinimumSize(128, 128); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(gui->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(periodicUpdate())); + m_displayBuffer.resize(sizeof qRgb(0,0,0) * m_displaySize * m_displaySize, 0); + + #ifdef SA_DEBUG + m_execution_avg = 0; + #endif } @@ -42,16 +56,20 @@ VectorView::VectorView(SaControls *controls, QWidget *_parent) : void VectorView::paintEvent(QPaintEvent *event) { #ifdef VEC_DEBUG - int start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + unsigned int draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count(); #endif - // all drawing done here, local variables are sufficient for the boundary + // all drawing done in this method, local variables are sufficient for the boundary const int displayTop = 1; const int displayBottom = height() -2; - const int displayLeft = 26; - const int displayRight = width() -26; + const int displayLeft = 1; + const int displayRight = width() -2; const int displayWidth = displayRight - displayLeft; const int displayHeight = displayBottom - displayTop; + + const int centerX = displayLeft + (displayWidth / 2) + 1; + const int centerY = displayTop + (displayHeight / 2) + 1; + float label_width = 20; float label_height = 16; float margin = 2; @@ -59,33 +77,82 @@ void VectorView::paintEvent(QPaintEvent *event) QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); - // get buffer from Vectorscope using a lockless FIFO - // dim stored image based on persistence and elapsed time - - // add new points + // check timestamp, limit dimming to 10 FPS + const float persist = m_controls->m_persistenceModel.value(); + for (std::size_t i = 0; i < m_displayBuffer.size(); i++) + { + m_displayBuffer.data()[i] = m_displayBuffer.data()[i] * persist; + } + // get input data using a lockless FIFO buffer + auto in_buffer = m_bufferReader.read_max(m_inputBuffer->capacity()); + std::size_t frame_count = in_buffer.size(); + + + //todo: idealne by to chtelo nejakou drawing function, old + new + pocet pixelu mezi nima + // mozna i podle vzdalenosti (tj. rychlosti) tlumit intenzitu drahy, to by byl ultimatni simulacni rezim + + unsigned int x = m_displaySize / 2; + unsigned int y = m_displaySize / 2; + // draw new points on top + for (; frame_count; frame_count--) // twice smaller + { + float left = in_buffer[frame_count][0] * (m_displaySize -1) * 0.25; + float right = in_buffer[frame_count][1] * (m_displaySize -1) * 0.25; + + x = fmax(fmin((3*x_old + (right - left + m_displaySize / 2)) / 4, m_displaySize - 1), 0); + y = fmax(fmin((3*y_old + (m_displaySize - (right + left + m_displaySize / 2))) / 4, m_displaySize - 1), 0); + ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); + + x = fmax(fmin((x_old + (right - left + m_displaySize / 2)) / 2, m_displaySize - 1), 0); + y = fmax(fmin((y_old + (m_displaySize - (right + left + m_displaySize / 2))) / 2, m_displaySize - 1), 0); + ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); + + x = fmax(fmin((x_old + 3*(right - left + m_displaySize / 2)) / 4, m_displaySize - 1), 0); + y = fmax(fmin((y_old + 3*(m_displaySize - (right + left + m_displaySize / 2))) / 4, m_displaySize - 1), 0); + ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); + + x = fmax(fmin(right - left + m_displaySize / 2, m_displaySize - 1), 0); + y = fmax(fmin(m_displaySize - (right + left + m_displaySize / 2), m_displaySize - 1), 0); + x_old = x; + y_old = y; +// std::cout << "point " << frame_count << " left " << left << " right " << right << std::endl; + + ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); + } // draw the final image - painter.drawImage(displayLeft, displayTop, // top left corner coordinates - QImage(m_displayBuffer.data(), // raw pixel data to display - bufferWidth, - bufferHeight, - QImage::Format_RGB32 - ).scaled(displayWidth, // scale to fit view.. - displayHeight, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation)); - - // always draw the outline - painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); - painter.drawRoundedRect(displayLeft, displayTop, displayWidth, displayBottom, 2.0, 2.0); + QImage temp = QImage(m_displayBuffer.data(), // raw pixel data to display + m_displaySize, + m_displaySize, + QImage::Format_RGB32); + temp.setDevicePixelRatio(devicePixelRatio()); + painter.drawImage(displayLeft, displayTop, + temp.scaled(displayWidth * devicePixelRatio(), + displayHeight * devicePixelRatio(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + + // draw the grid + painter.setPen(QPen(m_controls->m_colorGrid, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.drawEllipse(QPoint(centerX, centerY), 8, 8); + painter.drawEllipse(QPoint(centerX, centerY), displayWidth/2, displayWidth/2); + + // draw the outline +// painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); +// painter.drawRoundedRect(displayLeft, displayTop, displayWidth, displayBottom, 2.0, 2.0); #ifdef VEC_DEBUG + //init: 40/10; disable print: 11.5/2.2; no dimming: 10/0.6; no addition: no difference + // → qpainter: 10/0.6 dimming 1.5/1.5 // display what FPS would be achieved if vectorscope ran in a loop - start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - start_time; + draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - draw_time; + m_execution_avg = 0.95 * m_execution_avg + 0.05 * draw_time / 1000000.0; painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); painter.drawText(displayRight -100, 10, 100, 16, Qt::AlignLeft, - QString(std::string("Max FPS: " + std::to_string(1000000000.0 / start_time)).c_str())); + QString("Max FPS: ").append(std::to_string(1000.0 / m_execution_avg).c_str())); + painter.drawText(displayRight -100, 20, 100, 16, Qt::AlignLeft, + QString("Exec avg.: ").append(std::to_string(m_execution_avg).substr(0, 5).c_str()).append(" ms")); #endif } diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h index 09b0b1e6179..a3b8dd53581 100644 --- a/plugins/Vectorscope/VectorView.h +++ b/plugins/Vectorscope/VectorView.h @@ -26,18 +26,23 @@ #include #include +#include "Knob.h" +#include "LedCheckbox.h" +#include "LocklessRingBuffer.h" #include "VecControls.h" +#define VEC_DEBUG + // Widget that displays a vectorscope visualization of stereo signal. class VectorView : public QWidget { Q_OBJECT public: - explicit VectorView(VecControls *controls, QWidget *_parent = 0); + explicit VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *_parent = 0); virtual ~VectorView() {} - QSize sizeHint() const override {return QSize(300, 200);} + QSize sizeHint() const override {return QSize(300, 300);} protected: void paintEvent(QPaintEvent *event) override; @@ -48,6 +53,19 @@ private slots: private: VecControls *m_controls; + LocklessRingBuffer *m_inputBuffer; + LocklessRingBufferReader m_bufferReader; + + std::vector m_displayBuffer; + bool m_visible; + const unsigned short m_displaySize; + +unsigned int x_old = m_displaySize / 2; +unsigned int y_old = m_displaySize / 2; + + #ifdef VEC_DEBUG + float m_execution_avg = 0; + #endif }; #endif // VECTORVIEW_H diff --git a/plugins/Vectorscope/Vectorscope.cpp b/plugins/Vectorscope/Vectorscope.cpp index 2429d171184..f8bc30c40df 100644 --- a/plugins/Vectorscope/Vectorscope.cpp +++ b/plugins/Vectorscope/Vectorscope.cpp @@ -46,19 +46,25 @@ extern "C" { Vectorscope::Vectorscope(Model *parent, const Plugin::Descriptor::SubPluginFeatures::Key *key) : Effect(&vectorscope_plugin_descriptor, parent, key), - m_controls(this) + m_controls(this), + // Buffer is sized to cover 4* the current maximum LMMS audio buffer size, + // so that it has some reserve space in case GUI thresd is busy. + m_inputBuffer(4 * m_maxBufferSize) { } -// Take audio data and save them to a list for processing when redraw comes. -// Skip processing if the controls dialog isn't visible, it would only waste CPU cycles. +// Take audio data and store them for processing and display in the GUI thread. bool Vectorscope::processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) { if (!isEnabled() || !isRunning ()) {return false;} + + // Skip processing if the controls dialog isn't visible, it would only waste CPU cycles. if (m_controls.isViewVisible()) { - m_dataBuffer.insert(m_dataBuffer.end(), buffer, buffer + frame_count); + // To avoid processing spikes on audio thread, data are stored in + // a lockless ringbuffer and processed in a separate thread. + m_inputBuffer.write(buffer, frame_count); } return isRunning(); } diff --git a/plugins/Vectorscope/Vectorscope.h b/plugins/Vectorscope/Vectorscope.h index 40cdd5f7bf8..b45ff6de4ab 100644 --- a/plugins/Vectorscope/Vectorscope.h +++ b/plugins/Vectorscope/Vectorscope.h @@ -24,9 +24,8 @@ #ifndef VECTORSCOPE_H #define VECTORSCOPE_H -#include - #include "Effect.h" +#include "LocklessRingBuffer.h" #include "VecControls.h" @@ -39,10 +38,14 @@ class Vectorscope : public Effect bool processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) override; EffectControls *controls() override {return &m_controls;} + LocklessRingBuffer *getBuffer() {return &m_inputBuffer;} private: - SaControls m_controls; - std::list m_dataBuffer; + VecControls m_controls; + + // Maximum LMMS buffer size (hard coded, the actual constant is hard to get) + const unsigned int m_maxBufferSize = 4096; + LocklessRingBuffer m_inputBuffer; }; #endif // VECTORSCOPE_H From e8fbae68de48572e898e2bbe74dfb87c66de4d0b Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Mon, 11 Nov 2019 01:31:06 +0100 Subject: [PATCH 03/17] Finished HQ rendering mode and most of the basics --- plugins/Vectorscope/VecControls.cpp | 9 +- plugins/Vectorscope/VecControls.h | 2 +- plugins/Vectorscope/VecControlsDialog.cpp | 23 +-- plugins/Vectorscope/VectorView.cpp | 199 ++++++++++++++-------- plugins/Vectorscope/VectorView.h | 11 +- 5 files changed, 153 insertions(+), 91 deletions(-) diff --git a/plugins/Vectorscope/VecControls.cpp b/plugins/Vectorscope/VecControls.cpp index 37c82a82322..45e76ec76a7 100644 --- a/plugins/Vectorscope/VecControls.cpp +++ b/plugins/Vectorscope/VecControls.cpp @@ -37,11 +37,10 @@ VecControls::VecControls(Vectorscope *effect) : // initialize models and set default values m_persistenceModel(0.5f, 0.0f, 1.0f, 0.05f, this, tr("Display persistence amount")), m_logarithmicModel(false, this, tr("Logarithmic scale")), - m_fullViewModel(false, this, tr("Full view")) + m_highQualityModel(false, this, tr("High quality")) { // Colors - m_colorFG = QColor(51, 148, 204, 204); - m_colorBG = QColor(7, 7, 7, 255); // ~20 % gray (after gamma correction) + m_colorFG = QColor(60, 255, 130, 255); m_colorGrid = QColor(30, 34, 38, 255); // ~40 % gray (slightly cold / blue) m_colorLabels = QColor(192, 202, 212, 255); // ~90 % gray (slightly cold / blue) } @@ -58,7 +57,7 @@ void VecControls::loadSettings(const QDomElement &_this) { m_persistenceModel.loadSettings(_this, "Persistence"); m_logarithmicModel.loadSettings(_this, "Logarithmic"); - m_fullViewModel.loadSettings(_this, "FullView"); + m_highQualityModel.loadSettings(_this, "HighQuality"); } @@ -66,5 +65,5 @@ void VecControls::saveSettings(QDomDocument &doc, QDomElement &parent) { m_persistenceModel.saveSettings(doc, parent, "Persistence"); m_logarithmicModel.saveSettings(doc, parent, "Logarithmic"); - m_fullViewModel.saveSettings(doc, parent, "FullView"); + m_highQualityModel.saveSettings(doc, parent, "HighQuality"); } diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h index a9257430dab..02e629f8524 100644 --- a/plugins/Vectorscope/VecControls.h +++ b/plugins/Vectorscope/VecControls.h @@ -52,7 +52,7 @@ class VecControls : public EffectControls FloatModel m_persistenceModel; BoolModel m_logarithmicModel; - BoolModel m_fullViewModel; + BoolModel m_highQualityModel; QColor m_colorFG; QColor m_colorBG; diff --git a/plugins/Vectorscope/VecControlsDialog.cpp b/plugins/Vectorscope/VecControlsDialog.cpp index 9f2ea535439..5273c03bd40 100644 --- a/plugins/Vectorscope/VecControlsDialog.cpp +++ b/plugins/Vectorscope/VecControlsDialog.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -42,10 +43,11 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : m_controls(controls) { QVBoxLayout *master_layout = new QVBoxLayout; + master_layout->setContentsMargins(0, 2, 0, 0); setLayout(master_layout); // Visualizer widget - VectorView *display = new VectorView(controls, m_controls->m_effect->getBuffer(), 512, this); + VectorView *display = new VectorView(controls, m_controls->m_effect->getBuffer(), 1024, this); master_layout->addWidget(display); // Config area located inside visualizer @@ -56,22 +58,22 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : internal_layout->addLayout(config_layout); config_layout->addLayout(switch_layout); + // High-quality switch + LedCheckBox *highQualityButton = new LedCheckBox(tr("HQ"), this); + highQualityButton->setToolTip(tr("Double the resolution and simulate continuous analog-like trace.")); + highQualityButton->setCheckable(true); + highQualityButton->setMinimumSize(70, 12); + highQualityButton->setModel(&controls->m_highQualityModel); + switch_layout->addWidget(highQualityButton); + // Log. scale switch LedCheckBox *logarithmicButton = new LedCheckBox(tr("Log. scale"), this); - logarithmicButton->setToolTip(tr("Display amplitude on logarithmic scale to better see small values")); + logarithmicButton->setToolTip(tr("Display amplitude on logarithmic scale to better see small values.")); logarithmicButton->setCheckable(true); logarithmicButton->setMinimumSize(70, 12); logarithmicButton->setModel(&controls->m_logarithmicModel); switch_layout->addWidget(logarithmicButton); - // Full-view switch - LedCheckBox *fullViewButton = new LedCheckBox(tr("Full view"), this); - fullViewButton->setToolTip(tr("Display amplitude on logarithmic scale to better see small values")); - fullViewButton->setCheckable(true); - fullViewButton->setMinimumSize(70, 12); - fullViewButton->setModel(&controls->m_fullViewModel); - switch_layout->addWidget(fullViewButton); - config_layout->addStretch(); // Persistence knob @@ -89,3 +91,4 @@ QSize VecControlsDialog::sizeHint() const { return QSize(300, 300); } + diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index d2251d9d2eb..56c610b8be2 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -22,7 +22,9 @@ #include "VectorView.h" +#include #include +#include #include #include @@ -32,14 +34,19 @@ #define VEC_DEBUG #include + VectorView::VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *_parent) : QWidget(_parent), m_controls(controls), m_inputBuffer(inputBuffer), m_bufferReader(*inputBuffer), - m_displaySize(displaySize) + m_displaySize(displaySize), + m_persistTimestamp(0), + m_oldHQ(m_controls->m_highQualityModel.value()), + m_oldX(m_displaySize / 2), + m_oldY(m_displaySize / 2) { - setMinimumSize(128, 128); + setMinimumSize(200, 200); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(gui->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(periodicUpdate())); @@ -47,7 +54,7 @@ VectorView::VectorView(VecControls *controls, LocklessRingBuffer *i m_displayBuffer.resize(sizeof qRgb(0,0,0) * m_displaySize * m_displaySize, 0); #ifdef SA_DEBUG - m_execution_avg = 0; + m_executionAvg = 0; #endif } @@ -56,103 +63,153 @@ VectorView::VectorView(VecControls *controls, LocklessRingBuffer *i void VectorView::paintEvent(QPaintEvent *event) { #ifdef VEC_DEBUG - unsigned int draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + unsigned int drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count(); #endif - // all drawing done in this method, local variables are sufficient for the boundary - const int displayTop = 1; - const int displayBottom = height() -2; - const int displayLeft = 1; - const int displayRight = width() -2; + // All drawing done in this method, local variables are sufficient for the boundary + const int displayTop = 0; + const int displayBottom = height() -1; + const int displayLeft = 0; + const int displayRight = width() -1; const int displayWidth = displayRight - displayLeft; const int displayHeight = displayBottom - displayTop; const int centerX = displayLeft + (displayWidth / 2) + 1; - const int centerY = displayTop + (displayHeight / 2) + 1; + const int centerY = displayTop + (displayWidth / 2) + 1; - float label_width = 20; - float label_height = 16; - float margin = 2; + float labelWidth = 32; + float labelHeight = 32; + float margin = 4; QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); - // dim stored image based on persistence and elapsed time - // check timestamp, limit dimming to 10 FPS - const float persist = m_controls->m_persistenceModel.value(); - for (std::size_t i = 0; i < m_displayBuffer.size(); i++) + QFont normalFont, boldFont; + boldFont.setPixelSize(32); + boldFont.setBold(true); + + // Clear display buffer if quality setting was changed + bool hq = m_controls->m_highQualityModel.value(); + if (hq != m_oldHQ) { - m_displayBuffer.data()[i] = m_displayBuffer.data()[i] * persist; + m_oldHQ = hq; + for (std::size_t i = 0; i < m_displayBuffer.size(); i++) + { + m_displayBuffer.data()[i] = 0; + } } - // get input data using a lockless FIFO buffer - auto in_buffer = m_bufferReader.read_max(m_inputBuffer->capacity()); - std::size_t frame_count = in_buffer.size(); - - - //todo: idealne by to chtelo nejakou drawing function, old + new + pocet pixelu mezi nima - // mozna i podle vzdalenosti (tj. rychlosti) tlumit intenzitu drahy, to by byl ultimatni simulacni rezim - unsigned int x = m_displaySize / 2; - unsigned int y = m_displaySize / 2; - // draw new points on top - for (; frame_count; frame_count--) // twice smaller + // Dim stored image based on persistence and elapsed time. + // In non-HQ mode, dimming pass is limited to once every 100 ms. + unsigned int currentTimestamp = std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + const unsigned int threshold = hq ? 10 : 100; + if (currentTimestamp - m_persistTimestamp > threshold) { - float left = in_buffer[frame_count][0] * (m_displaySize -1) * 0.25; - float right = in_buffer[frame_count][1] * (m_displaySize -1) * 0.25; - - x = fmax(fmin((3*x_old + (right - left + m_displaySize / 2)) / 4, m_displaySize - 1), 0); - y = fmax(fmin((3*y_old + (m_displaySize - (right + left + m_displaySize / 2))) / 4, m_displaySize - 1), 0); - ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); - - x = fmax(fmin((x_old + (right - left + m_displaySize / 2)) / 2, m_displaySize - 1), 0); - y = fmax(fmin((y_old + (m_displaySize - (right + left + m_displaySize / 2))) / 2, m_displaySize - 1), 0); - ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); - - x = fmax(fmin((x_old + 3*(right - left + m_displaySize / 2)) / 4, m_displaySize - 1), 0); - y = fmax(fmin((y_old + 3*(m_displaySize - (right + left + m_displaySize / 2))) / 4, m_displaySize - 1), 0); - ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); + m_persistTimestamp = currentTimestamp; + const std::size_t useableBuffer = hq ? m_displayBuffer.size() : m_displayBuffer.size() / 4; + const float persist = pow(log10(1 + 9 * m_controls->m_persistenceModel.value()), threshold / 10); + for (std::size_t i = 0; i < useableBuffer; i++) + { + m_displayBuffer.data()[i] = m_displayBuffer.data()[i] * persist; + } + } - x = fmax(fmin(right - left + m_displaySize / 2, m_displaySize - 1), 0); - y = fmax(fmin(m_displaySize - (right + left + m_displaySize / 2), m_displaySize - 1), 0); - x_old = x; - y_old = y; -// std::cout << "point " << frame_count << " left " << left << " right " << right << std::endl; + // Get new data from lockless FIFO buffer + auto inBuffer = m_bufferReader.read_max(m_inputBuffer->capacity()); + std::size_t frameCount = inBuffer.size(); - ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = m_controls->m_colorFG.rgb(); + int x; + int y; + // Draw new points on top + if (hq) + { + // High quality mode: check distance between points and draw a line. + // The longer the line is, the dimmer, simulating real electron trace on luminescent screen. + auto saturate = [=](unsigned short pixel) {return fmax(fmin(pixel, m_displaySize - 1), 0);}; + for (std::size_t frame = 0; frame < frameCount; frame++) + { + float left = inBuffer[frame][0] * (m_displaySize -1) / 4; + float right = inBuffer[frame][1] * (m_displaySize -1) / 4; + + x = saturate(right - left + m_displaySize / 2); + y = saturate(m_displaySize - (right + left + m_displaySize / 2)); + + unsigned char points = fmin(sqrt(pow(m_oldX - x, 2.0) + pow(m_oldY - y, 2.0)), 100); + QColor added_color = m_controls->m_colorFG.darker(100 + 10 * points).rgb(); + + // Draw the new point + QColor current_color = ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize]; + current_color.setRed(fmin(current_color.red() + added_color.red(), 255)); + current_color.setGreen(fmin(current_color.green() + added_color.green(), 255)); + current_color.setBlue(fmin(current_color.blue() + added_color.blue(), 255)); + ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = current_color.rgb(); + + // Draw interpolated points between the old one and the new one + for (unsigned char i = 1; i < points; i++) + { + x = saturate(((points - i) * m_oldX + i * (right - left + m_displaySize / 2)) / points); + y = saturate(((points - i) * m_oldY + i * (m_displaySize - (right + left + m_displaySize / 2))) / points); + QColor current_color = ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize]; + current_color.setRed(fmin(current_color.red() + added_color.red(), 255)); + current_color.setGreen(fmin(current_color.green() + added_color.green(), 255)); + current_color.setBlue(fmin(current_color.blue() + added_color.blue(), 255)); + ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = current_color.rgb(); + } + m_oldX = x; + m_oldY = y; + } + } + else + { + // To improve performance, non-HQ mode uses smaller buffer / display size. + const unsigned short activeSize = m_displaySize / 2; + auto saturate = [=](unsigned short pixel) {return fmax(fmin(pixel, activeSize - 1), 0);}; + for (std::size_t frame = 0; frame < frameCount; frame++) + { + float left = inBuffer[frame][0] * (activeSize -1) / 4; + float right = inBuffer[frame][1] * (activeSize -1) / 4; + x = saturate(right - left + activeSize / 2); + y = saturate(activeSize - (right + left + activeSize / 2)); + ((QRgb*)m_displayBuffer.data())[x + y * activeSize] = m_controls->m_colorFG.rgb(); + } } - // draw the final image - QImage temp = QImage(m_displayBuffer.data(), // raw pixel data to display - m_displaySize, - m_displaySize, + // Draw background + painter.fillRect(displayLeft, displayTop, displayWidth, displayHeight, QColor(0,0,0)); + + // Draw the final image. + QImage temp = QImage(m_displayBuffer.data(), + hq ? m_displaySize : m_displaySize / 2, + hq ? m_displaySize : m_displaySize / 2, QImage::Format_RGB32); temp.setDevicePixelRatio(devicePixelRatio()); painter.drawImage(displayLeft, displayTop, - temp.scaled(displayWidth * devicePixelRatio(), - displayHeight * devicePixelRatio(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation)); + temp.scaledToWidth(displayWidth * devicePixelRatio(), Qt::SmoothTransformation)); - // draw the grid + // Draw the grid and labels. painter.setPen(QPen(m_controls->m_colorGrid, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); - painter.drawEllipse(QPoint(centerX, centerY), 8, 8); - painter.drawEllipse(QPoint(centerX, centerY), displayWidth/2, displayWidth/2); - - // draw the outline + painter.drawEllipse(QPoint(centerX, centerY), displayWidth / 2, displayWidth / 2); + painter.drawLine(QPoint(centerX, centerY), QPoint(displayLeft + 50, displayTop + 50)); + painter.drawLine(QPoint(centerX, centerY), QPoint(displayRight - 50, displayTop + 50)); + + painter.setFont(boldFont); + painter.drawText(displayLeft + margin, displayTop + margin, + labelWidth, labelHeight, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, + QString("L")); + painter.drawText(displayRight - margin - labelWidth, displayTop + margin, + labelWidth, labelHeight, Qt::AlignRight| Qt::AlignTop | Qt::TextDontClip, + QString("R")); + // Draw the outline. // painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); // painter.drawRoundedRect(displayLeft, displayTop, displayWidth, displayBottom, 2.0, 2.0); #ifdef VEC_DEBUG - //init: 40/10; disable print: 11.5/2.2; no dimming: 10/0.6; no addition: no difference - // → qpainter: 10/0.6 dimming 1.5/1.5 - // display what FPS would be achieved if vectorscope ran in a loop - draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - draw_time; - m_execution_avg = 0.95 * m_execution_avg + 0.05 * draw_time / 1000000.0; + drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count() - drawTime; + m_executionAvg = 0.95 * m_executionAvg + 0.05 * drawTime / 1000000.0; painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); - painter.drawText(displayRight -100, 10, 100, 16, Qt::AlignLeft, - QString("Max FPS: ").append(std::to_string(1000.0 / m_execution_avg).c_str())); - painter.drawText(displayRight -100, 20, 100, 16, Qt::AlignLeft, - QString("Exec avg.: ").append(std::to_string(m_execution_avg).substr(0, 5).c_str()).append(" ms")); + painter.setFont(normalFont); + painter.drawText(displayWidth / 2 - 50, margin, 100, 16, Qt::AlignLeft, + QString("Exec avg.: ").append(std::to_string(m_executionAvg).substr(0, 5).c_str()).append(" ms")); #endif } diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h index a3b8dd53581..c9ac735a922 100644 --- a/plugins/Vectorscope/VectorView.h +++ b/plugins/Vectorscope/VectorView.h @@ -57,15 +57,18 @@ private slots: LocklessRingBufferReader m_bufferReader; std::vector m_displayBuffer; + const unsigned short m_displaySize; bool m_visible; - const unsigned short m_displaySize; -unsigned int x_old = m_displaySize / 2; -unsigned int y_old = m_displaySize / 2; + // State variables for comparison with previous repaint + unsigned int m_persistTimestamp; + bool m_oldHQ; + int m_oldX; + int m_oldY; #ifdef VEC_DEBUG - float m_execution_avg = 0; + float m_executionAvg = 0; #endif }; #endif // VECTORVIEW_H From 5afa2eeb0210d1c8b5d5d4b3f1b52d64d1d43f58 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Mon, 18 Nov 2019 01:44:19 +0100 Subject: [PATCH 04/17] Add log scale, fine-tune interface, clean up code --- plugins/Vectorscope/VecControls.cpp | 9 +- plugins/Vectorscope/VecControls.h | 1 + plugins/Vectorscope/VecControlsDialog.cpp | 5 +- plugins/Vectorscope/VectorView.cpp | 190 ++++++++++++++-------- plugins/Vectorscope/VectorView.h | 5 +- 5 files changed, 133 insertions(+), 77 deletions(-) diff --git a/plugins/Vectorscope/VecControls.cpp b/plugins/Vectorscope/VecControls.cpp index 45e76ec76a7..fdb21b4efe0 100644 --- a/plugins/Vectorscope/VecControls.cpp +++ b/plugins/Vectorscope/VecControls.cpp @@ -39,10 +39,11 @@ VecControls::VecControls(Vectorscope *effect) : m_logarithmicModel(false, this, tr("Logarithmic scale")), m_highQualityModel(false, this, tr("High quality")) { - // Colors - m_colorFG = QColor(60, 255, 130, 255); - m_colorGrid = QColor(30, 34, 38, 255); // ~40 % gray (slightly cold / blue) - m_colorLabels = QColor(192, 202, 212, 255); // ~90 % gray (slightly cold / blue) + // Colors (percentages include sRGB gamma correction) + m_colorFG = QColor(60, 255, 130, 255); // ~LMMS green + m_colorGrid = QColor(76, 80, 84, 128); // ~60 % gray (slightly cold / blue), 50 % transparent + m_colorLabels = QColor(76, 80, 84, 255); // ~60 % gray (slightly cold / blue) + m_colorOutline = QColor(30, 34, 38, 255); // ~40 % gray (slightly cold / blue) } diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h index 02e629f8524..b2f02e122a0 100644 --- a/plugins/Vectorscope/VecControls.h +++ b/plugins/Vectorscope/VecControls.h @@ -58,6 +58,7 @@ class VecControls : public EffectControls QColor m_colorBG; QColor m_colorGrid; QColor m_colorLabels; + QColor m_colorOutline; friend class VecControlsDialog; friend class VectorView; diff --git a/plugins/Vectorscope/VecControlsDialog.cpp b/plugins/Vectorscope/VecControlsDialog.cpp index 5273c03bd40..71cd36df928 100644 --- a/plugins/Vectorscope/VecControlsDialog.cpp +++ b/plugins/Vectorscope/VecControlsDialog.cpp @@ -47,7 +47,7 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : setLayout(master_layout); // Visualizer widget - VectorView *display = new VectorView(controls, m_controls->m_effect->getBuffer(), 1024, this); + VectorView *display = new VectorView(controls, m_controls->m_effect->getBuffer(), 768, this); master_layout->addWidget(display); // Config area located inside visualizer @@ -89,6 +89,5 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : // Suggest the best current widget size. QSize VecControlsDialog::sizeHint() const { - return QSize(300, 300); + return QSize(275, 300); } - diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 56c610b8be2..525e33ffbc4 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -31,12 +31,9 @@ #include "GuiApplication.h" #include "MainWindow.h" -#define VEC_DEBUG -#include - -VectorView::VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *_parent) : - QWidget(_parent), +VectorView::VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *parent) : + QWidget(parent), m_controls(controls), m_inputBuffer(inputBuffer), m_bufferReader(*inputBuffer), @@ -53,7 +50,7 @@ VectorView::VectorView(VecControls *controls, LocklessRingBuffer *i m_displayBuffer.resize(sizeof qRgb(0,0,0) * m_displaySize * m_displaySize, 0); - #ifdef SA_DEBUG + #ifdef VEC_DEBUG m_executionAvg = 0; #endif } @@ -67,26 +64,28 @@ void VectorView::paintEvent(QPaintEvent *event) #endif // All drawing done in this method, local variables are sufficient for the boundary - const int displayTop = 0; - const int displayBottom = height() -1; - const int displayLeft = 0; - const int displayRight = width() -1; + const int displayTop = 2; + const int displayBottom = height() - 2; + const int displayLeft = 2; + const int displayRight = width() - 2; const int displayWidth = displayRight - displayLeft; const int displayHeight = displayBottom - displayTop; - const int centerX = displayLeft + (displayWidth / 2) + 1; - const int centerY = displayTop + (displayWidth / 2) + 1; + const float centerX = displayLeft + (displayWidth / 2.f); + const float centerY = displayTop + (displayWidth / 2.f); - float labelWidth = 32; - float labelHeight = 32; - float margin = 4; + const int margin = 4; + const int gridCorner = 30; + // Setup QPainter and font sizes QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QFont normalFont, boldFont; - boldFont.setPixelSize(32); + boldFont.setPixelSize(26); boldFont.setBold(true); + const int labelWidth = 26; + const int labelHeight = 26; // Clear display buffer if quality setting was changed bool hq = m_controls->m_highQualityModel.value(); @@ -99,61 +98,106 @@ void VectorView::paintEvent(QPaintEvent *event) } } - // Dim stored image based on persistence and elapsed time. - // In non-HQ mode, dimming pass is limited to once every 100 ms. - unsigned int currentTimestamp = std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); - const unsigned int threshold = hq ? 10 : 100; - if (currentTimestamp - m_persistTimestamp > threshold) + // Dim stored image based on persistence setting and elapsed time. + // Update period is limited to 50 ms (20 FPS) for non-HQ mode and 10 ms (100 FPS) for HQ mode. + const unsigned int currentTimestamp = std::chrono::duration_cast + ( + std::chrono::high_resolution_clock::now().time_since_epoch() + ).count(); + const unsigned int elapsed = currentTimestamp - m_persistTimestamp; + const unsigned int threshold = hq ? 10 : 50; + if (elapsed > threshold) { m_persistTimestamp = currentTimestamp; const std::size_t useableBuffer = hq ? m_displayBuffer.size() : m_displayBuffer.size() / 4; - const float persist = pow(log10(1 + 9 * m_controls->m_persistenceModel.value()), threshold / 10); + // The knob value is interpreted on log. scale, otherwise the effect would ramp up too slowly. + // Persistence value specifies fraction of light intensity that remains after 10 ms. + // → Compensate it based on elapsed time (exponential decay). + const float persist = log10(1 + 9 * m_controls->m_persistenceModel.value()); + const float persistPerFrame = pow(persist, elapsed / 10.f); + // Note that for simplicity and performance reasons, this implementation only dims all stored + // values by a given factor. A true simulation would also do the inverse of desaturation that + // occurs in high-intensity traces in HQ mode. for (std::size_t i = 0; i < useableBuffer; i++) { - m_displayBuffer.data()[i] = m_displayBuffer.data()[i] * persist; + m_displayBuffer.data()[i] = m_displayBuffer.data()[i] * persistPerFrame; } } - // Get new data from lockless FIFO buffer + // Get new samples from the lockless input FIFO buffer auto inBuffer = m_bufferReader.read_max(m_inputBuffer->capacity()); std::size_t frameCount = inBuffer.size(); - int x; - int y; // Draw new points on top + float left, right; + int x, y; + + const bool logScale = m_controls->m_logarithmicModel.value(); + const unsigned short activeSize = hq ? m_displaySize : m_displaySize / 2; + + // Helper lambda functions for better readability: + // Make sure pixel stays within display bounds + auto saturate = [=](short pixelPos) {return fmax(fmin(pixelPos, activeSize - 1), 0);}; + // Take existing pixel and brigthen it. Very bright light should reduce saturation and become + // white. This effect is easily approximated by capping elementary colors to 255 individually. + auto updatePixel = [&](unsigned int x, unsigned int y, QColor addedColor) + { + QColor currentColor = ((QRgb*)m_displayBuffer.data())[x + y * activeSize]; + currentColor.setRed(fmin(currentColor.red() + addedColor.red(), 255)); + currentColor.setGreen(fmin(currentColor.green() + addedColor.green(), 255)); + currentColor.setBlue(fmin(currentColor.blue() + addedColor.blue(), 255)); + ((QRgb*)m_displayBuffer.data())[x + y * activeSize] = currentColor.rgb(); + }; + if (hq) { // High quality mode: check distance between points and draw a line. // The longer the line is, the dimmer, simulating real electron trace on luminescent screen. - auto saturate = [=](unsigned short pixel) {return fmax(fmin(pixel, m_displaySize - 1), 0);}; for (std::size_t frame = 0; frame < frameCount; frame++) { - float left = inBuffer[frame][0] * (m_displaySize -1) / 4; - float right = inBuffer[frame][1] * (m_displaySize -1) / 4; + // Scale left and right channel from (-1.0, 1.0) to display range + if (logScale) + { + // To better preserve shapes, the log scale is applied to the distance from origin, + // not the individual channels. + const float distance = sqrt(pow(inBuffer[frame][0], 2) + pow(inBuffer[frame][1], 2)); + const float distanceLog = log10(1 + 9 * abs(distance)); + const float angleCos = inBuffer[frame][0] / distance; + const float angleSin = inBuffer[frame][1] / distance; + left = distanceLog * angleCos * (activeSize - 1) / 4; + right = distanceLog * angleSin * (activeSize - 1) / 4; + } + else + { + left = inBuffer[frame][0] * (activeSize - 1) / 4; + right = inBuffer[frame][1] * (activeSize - 1) / 4; + } + + // Rotate display coordinates 45 degrees, flip Y axis and make sure the result stays within bounds + x = saturate(right - left + activeSize / 2.f); + y = saturate(activeSize - (right + left + activeSize / 2.f)); - x = saturate(right - left + m_displaySize / 2); - y = saturate(m_displaySize - (right + left + m_displaySize / 2)); + // Estimate number of points needed to fill space between the old and new pixel. Cap at 100. + unsigned char points = fmin(sqrt(pow(m_oldX - x, 2.f) + pow(m_oldY - y, 2.f)), 100); - unsigned char points = fmin(sqrt(pow(m_oldX - x, 2.0) + pow(m_oldY - y, 2.0)), 100); - QColor added_color = m_controls->m_colorFG.darker(100 + 10 * points).rgb(); + // Large distance = dim trace. The curve for darker() is choosen so that: + // - no movement (0 points) actually _increases_ brightness slightly, + // - one point between samples = returns exactly the specified color, + // - one to 99 points between samples = follows a sharp "1/x" decaying curve, + // - 100 points between samples = returns approximately 5 % brightness. + // Everything else is discarded because there is not much to see anyway. + QColor addedColor = m_controls->m_colorFG.darker(75 + 20 * points).rgb(); - // Draw the new point - QColor current_color = ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize]; - current_color.setRed(fmin(current_color.red() + added_color.red(), 255)); - current_color.setGreen(fmin(current_color.green() + added_color.green(), 255)); - current_color.setBlue(fmin(current_color.blue() + added_color.blue(), 255)); - ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = current_color.rgb(); + // Draw the new pixel: the beam sweeps across area that may have been excited before + // → add new value to existing pixel state. + updatePixel(x, y, addedColor); - // Draw interpolated points between the old one and the new one + // Draw interpolated points between the old pixel and the new one for (unsigned char i = 1; i < points; i++) { - x = saturate(((points - i) * m_oldX + i * (right - left + m_displaySize / 2)) / points); - y = saturate(((points - i) * m_oldY + i * (m_displaySize - (right + left + m_displaySize / 2))) / points); - QColor current_color = ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize]; - current_color.setRed(fmin(current_color.red() + added_color.red(), 255)); - current_color.setGreen(fmin(current_color.green() + added_color.green(), 255)); - current_color.setBlue(fmin(current_color.blue() + added_color.blue(), 255)); - ((QRgb*)m_displayBuffer.data())[x + y * m_displaySize] = current_color.rgb(); + x = saturate(((points - i) * m_oldX + i * (right - left + activeSize / 2.f)) / points); + y = saturate(((points - i) * m_oldY + i * (activeSize - (right + left + activeSize / 2.f))) / points); + updatePixel(x, y, addedColor); } m_oldX = x; m_oldY = y; @@ -161,15 +205,23 @@ void VectorView::paintEvent(QPaintEvent *event) } else { - // To improve performance, non-HQ mode uses smaller buffer / display size. - const unsigned short activeSize = m_displaySize / 2; - auto saturate = [=](unsigned short pixel) {return fmax(fmin(pixel, activeSize - 1), 0);}; + // To improve performance, non-HQ mode uses smaller display size and only + // one full-color pixel per sample. for (std::size_t frame = 0; frame < frameCount; frame++) { - float left = inBuffer[frame][0] * (activeSize -1) / 4; - float right = inBuffer[frame][1] * (activeSize -1) / 4; - x = saturate(right - left + activeSize / 2); - y = saturate(activeSize - (right + left + activeSize / 2)); + if (logScale) { + const float distance = sqrt(pow(inBuffer[frame][0], 2) + pow(inBuffer[frame][1], 2)); + const float distanceLog = log10(1 + 9 * abs(distance)); + const float angleCos = inBuffer[frame][0] / distance; + const float angleSin = inBuffer[frame][1] / distance; + left = distanceLog * angleCos * (activeSize - 1) / 4; + right = distanceLog * angleSin * (activeSize - 1) / 4; + } else { + left = inBuffer[frame][0] * (activeSize - 1) / 4; + right = inBuffer[frame][1] * (activeSize - 1) / 4; + } + x = saturate(right - left + activeSize / 2.f); + y = saturate(activeSize - (right + left + activeSize / 2.f)); ((QRgb*)m_displayBuffer.data())[x + y * activeSize] = m_controls->m_colorFG.rgb(); } } @@ -177,7 +229,7 @@ void VectorView::paintEvent(QPaintEvent *event) // Draw background painter.fillRect(displayLeft, displayTop, displayWidth, displayHeight, QColor(0,0,0)); - // Draw the final image. + // Draw the final image QImage temp = QImage(m_displayBuffer.data(), hq ? m_displaySize : m_displaySize / 2, hq ? m_displaySize : m_displaySize / 2, @@ -186,35 +238,39 @@ void VectorView::paintEvent(QPaintEvent *event) painter.drawImage(displayLeft, displayTop, temp.scaledToWidth(displayWidth * devicePixelRatio(), Qt::SmoothTransformation)); - // Draw the grid and labels. - painter.setPen(QPen(m_controls->m_colorGrid, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); - painter.drawEllipse(QPoint(centerX, centerY), displayWidth / 2, displayWidth / 2); - painter.drawLine(QPoint(centerX, centerY), QPoint(displayLeft + 50, displayTop + 50)); - painter.drawLine(QPoint(centerX, centerY), QPoint(displayRight - 50, displayTop + 50)); + // Draw the grid and labels + painter.setPen(QPen(m_controls->m_colorGrid, 1.5, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.drawEllipse(QPointF(centerX, centerY), displayWidth / 2.f, displayWidth / 2.f); + painter.setPen(QPen(m_controls->m_colorGrid, 1.5, Qt::DotLine, Qt::RoundCap, Qt::BevelJoin)); + painter.drawLine(QPointF(centerX, centerY), QPointF(displayLeft + gridCorner, displayTop + gridCorner)); + painter.drawLine(QPointF(centerX, centerY), QPointF(displayRight - gridCorner, displayTop + gridCorner)); + painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); painter.setFont(boldFont); - painter.drawText(displayLeft + margin, displayTop + margin, + painter.drawText(displayLeft + margin, displayTop, labelWidth, labelHeight, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, QString("L")); - painter.drawText(displayRight - margin - labelWidth, displayTop + margin, + painter.drawText(displayRight - margin - labelWidth, displayTop, labelWidth, labelHeight, Qt::AlignRight| Qt::AlignTop | Qt::TextDontClip, QString("R")); - // Draw the outline. -// painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); -// painter.drawRoundedRect(displayLeft, displayTop, displayWidth, displayBottom, 2.0, 2.0); + // Draw the outline + painter.setPen(QPen(m_controls->m_colorOutline, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 2.0, 2.0); + + // Optionally measure drawing performance #ifdef VEC_DEBUG drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count() - drawTime; m_executionAvg = 0.95 * m_executionAvg + 0.05 * drawTime / 1000000.0; painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); painter.setFont(normalFont); - painter.drawText(displayWidth / 2 - 50, margin, 100, 16, Qt::AlignLeft, + painter.drawText(displayWidth / 2 - 50, displayBottom - 16, 100, 16, Qt::AlignLeft, QString("Exec avg.: ").append(std::to_string(m_executionAvg).substr(0, 5).c_str()).append(" ms")); #endif } -// Periodically trigger repaint and check if the widget is visible. +// Periodically trigger repaint and check if the widget is visible void VectorView::periodicUpdate() { m_visible = isVisible(); diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h index c9ac735a922..6c1d2e9c3aa 100644 --- a/plugins/Vectorscope/VectorView.h +++ b/plugins/Vectorscope/VectorView.h @@ -23,7 +23,6 @@ #ifndef VECTORVIEW_H #define VECTORVIEW_H -#include #include #include "Knob.h" @@ -31,7 +30,7 @@ #include "LocklessRingBuffer.h" #include "VecControls.h" -#define VEC_DEBUG +//#define VEC_DEBUG // Widget that displays a vectorscope visualization of stereo signal. @@ -39,7 +38,7 @@ class VectorView : public QWidget { Q_OBJECT public: - explicit VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *_parent = 0); + explicit VectorView(VecControls *controls, LocklessRingBuffer *inputBuffer, unsigned short displaySize, QWidget *parent = 0); virtual ~VectorView() {} QSize sizeHint() const override {return QSize(300, 300);} From dee9624fc1aff7f206be3d04981aad187aba779c Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Thu, 21 Nov 2019 16:10:15 +0100 Subject: [PATCH 05/17] Final cleanup --- plugins/Vectorscope/CMakeLists.txt | 4 +-- plugins/Vectorscope/README.md | 9 +++---- plugins/Vectorscope/VecControls.cpp | 2 +- plugins/Vectorscope/VecControls.h | 4 +-- plugins/Vectorscope/VecControlsDialog.cpp | 3 ++- plugins/Vectorscope/VectorView.cpp | 33 +++++++++++++---------- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/plugins/Vectorscope/CMakeLists.txt b/plugins/Vectorscope/CMakeLists.txt index 4ad76b6034d..b73ff76d589 100644 --- a/plugins/Vectorscope/CMakeLists.txt +++ b/plugins/Vectorscope/CMakeLists.txt @@ -1,5 +1,3 @@ INCLUDE(BuildPlugin) -INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS}) -LINK_LIBRARIES(${FFTW3F_LIBRARIES}) BUILD_PLUGIN(vectorscope Vectorscope.cpp VecControls.cpp VecControlsDialog.cpp VectorView.cpp -MOCFILES VecControls.h VecControlsDialog.h VectorView.h EMBEDDED_RESOURCES *.svg logo.png) +MOCFILES VecControls.h VecControlsDialog.h VectorView.h EMBEDDED_RESOURCES logo.png) diff --git a/plugins/Vectorscope/README.md b/plugins/Vectorscope/README.md index e0741241f33..18b218f6d9d 100644 --- a/plugins/Vectorscope/README.md +++ b/plugins/Vectorscope/README.md @@ -2,14 +2,13 @@ ## Overview -This plugin consists of one widget and back-end code to provide it with required data. +Vectorscope is a simple stereo field visualizer. Samples are plotted into a graph, with left and right channels providing the coordinates. Previously drawn samples quickly fade away and are continuously replaced by new samples, creating a real-time plot of the most recently played samples. -The top-level widget is VecControlDialog. It displays configuration knobs and the VectorView widget. The back-end class for its configuration is VecControls, which holds all configuration values. - -VectorView computes and shows the visualization results. Data for processing are received from the Vectorscope class, which handles the interface with LMMS. +Similar to other effect plugins, the top-level widget is VecControlDialog. It displays configuration knobs and the main VectorView widget. The back-end configuration class is VecControls, which holds all models and configuration values. +VectorView computes and shows the plot. It gets data for processing from the Vectorscope class, which handles the interface with LMMS. In order to avoid any stalling of the realtime-sensitive audio thread, data are exchanged through a lockless ring buffer. ## Changelog - 1.0.0 2019-00-00 + 1.0.0 2019-11-21 - initial release diff --git a/plugins/Vectorscope/VecControls.cpp b/plugins/Vectorscope/VecControls.cpp index fdb21b4efe0..dbdd95a69c0 100644 --- a/plugins/Vectorscope/VecControls.cpp +++ b/plugins/Vectorscope/VecControls.cpp @@ -26,8 +26,8 @@ #include -#include "Vectorscope.h" #include "VecControlsDialog.h" +#include "Vectorscope.h" VecControls::VecControls(Vectorscope *effect) : diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h index b2f02e122a0..8dded0ba10f 100644 --- a/plugins/Vectorscope/VecControls.h +++ b/plugins/Vectorscope/VecControls.h @@ -25,9 +25,10 @@ #ifndef VECCONTROLS_H #define VECCONTROLS_H +#include + #include "EffectControls.h" -#include class Vectorscope; @@ -55,7 +56,6 @@ class VecControls : public EffectControls BoolModel m_highQualityModel; QColor m_colorFG; - QColor m_colorBG; QColor m_colorGrid; QColor m_colorLabels; QColor m_colorOutline; diff --git a/plugins/Vectorscope/VecControlsDialog.cpp b/plugins/Vectorscope/VecControlsDialog.cpp index 71cd36df928..9916d775605 100644 --- a/plugins/Vectorscope/VecControlsDialog.cpp +++ b/plugins/Vectorscope/VecControlsDialog.cpp @@ -47,6 +47,7 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : setLayout(master_layout); // Visualizer widget + // The size of 768 pixels seems to offer a good balance of speed, accuracy and trace thickness. VectorView *display = new VectorView(controls, m_controls->m_effect->getBuffer(), 768, this); master_layout->addWidget(display); @@ -86,7 +87,7 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : } -// Suggest the best current widget size. +// Suggest the best widget size. QSize VecControlsDialog::sizeHint() const { return QSize(275, 300); diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 525e33ffbc4..a1d952d6bf8 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -23,8 +23,8 @@ #include "VectorView.h" #include -#include #include +#include #include #include @@ -87,8 +87,9 @@ void VectorView::paintEvent(QPaintEvent *event) const int labelWidth = 26; const int labelHeight = 26; - // Clear display buffer if quality setting was changed bool hq = m_controls->m_highQualityModel.value(); + + // Clear display buffer if quality setting was changed if (hq != m_oldHQ) { m_oldHQ = hq; @@ -109,6 +110,7 @@ void VectorView::paintEvent(QPaintEvent *event) if (elapsed > threshold) { m_persistTimestamp = currentTimestamp; + // Non-HQ mode uses half the resolution → use limited buffer space. const std::size_t useableBuffer = hq ? m_displayBuffer.size() : m_displayBuffer.size() / 4; // The knob value is interpreted on log. scale, otherwise the effect would ramp up too slowly. // Persistence value specifies fraction of light intensity that remains after 10 ms. @@ -135,8 +137,8 @@ void VectorView::paintEvent(QPaintEvent *event) const bool logScale = m_controls->m_logarithmicModel.value(); const unsigned short activeSize = hq ? m_displaySize : m_displaySize / 2; - // Helper lambda functions for better readability: - // Make sure pixel stays within display bounds + // Helper lambda functions for better readability + // Make sure pixel stays within display bounds: auto saturate = [=](short pixelPos) {return fmax(fmin(pixelPos, activeSize - 1), 0);}; // Take existing pixel and brigthen it. Very bright light should reduce saturation and become // white. This effect is easily approximated by capping elementary colors to 255 individually. @@ -185,7 +187,7 @@ void VectorView::paintEvent(QPaintEvent *event) // - one point between samples = returns exactly the specified color, // - one to 99 points between samples = follows a sharp "1/x" decaying curve, // - 100 points between samples = returns approximately 5 % brightness. - // Everything else is discarded because there is not much to see anyway. + // Everything else is discarded (by the 100 point cap) because there is not much to see anyway. QColor addedColor = m_controls->m_colorFG.darker(75 + 20 * points).rgb(); // Draw the new pixel: the beam sweeps across area that may have been excited before @@ -193,14 +195,16 @@ void VectorView::paintEvent(QPaintEvent *event) updatePixel(x, y, addedColor); // Draw interpolated points between the old pixel and the new one + int newX = right - left + activeSize / 2.f; + int newY = activeSize - (right + left + activeSize / 2.f); for (unsigned char i = 1; i < points; i++) { - x = saturate(((points - i) * m_oldX + i * (right - left + activeSize / 2.f)) / points); - y = saturate(((points - i) * m_oldY + i * (activeSize - (right + left + activeSize / 2.f))) / points); + x = saturate(((points - i) * m_oldX + i * newX) / points); + y = saturate(((points - i) * m_oldY + i * newY) / points); updatePixel(x, y, addedColor); } - m_oldX = x; - m_oldY = y; + m_oldX = newX; + m_oldY = newY; } } else @@ -231,12 +235,13 @@ void VectorView::paintEvent(QPaintEvent *event) // Draw the final image QImage temp = QImage(m_displayBuffer.data(), - hq ? m_displaySize : m_displaySize / 2, - hq ? m_displaySize : m_displaySize / 2, + activeSize, + activeSize, QImage::Format_RGB32); temp.setDevicePixelRatio(devicePixelRatio()); painter.drawImage(displayLeft, displayTop, - temp.scaledToWidth(displayWidth * devicePixelRatio(), Qt::SmoothTransformation)); + temp.scaledToWidth(displayWidth * devicePixelRatio(), + Qt::SmoothTransformation)); // Draw the grid and labels painter.setPen(QPen(m_controls->m_colorGrid, 1.5, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); @@ -256,12 +261,12 @@ void VectorView::paintEvent(QPaintEvent *event) // Draw the outline painter.setPen(QPen(m_controls->m_colorOutline, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); - painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 2.0, 2.0); + painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 2.f, 2.f); // Optionally measure drawing performance #ifdef VEC_DEBUG drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count() - drawTime; - m_executionAvg = 0.95 * m_executionAvg + 0.05 * drawTime / 1000000.0; + m_executionAvg = 0.95f * m_executionAvg + 0.05f * drawTime / 1000000.f; painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); painter.setFont(normalFont); painter.drawText(displayWidth / 2 - 50, displayBottom - 16, 100, 16, Qt::AlignLeft, From 1b33965665b5348612d3ce078c5100b9bb1592d6 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Fri, 22 Nov 2019 11:09:02 +0100 Subject: [PATCH 06/17] Fix class and file name in VectorView.cpp header comment Co-Authored-By: Hyunjin Song --- plugins/Vectorscope/VectorView.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index a1d952d6bf8..04eda4af798 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -1,4 +1,4 @@ -/* VectorViewView.cpp - implementation of VectorViewView class. +/* VectorView.cpp - implementation of VectorView class. * * Copyright (c) 2019 Martin Pavelek * @@ -281,4 +281,3 @@ void VectorView::periodicUpdate() m_visible = isVisible(); if (m_visible) {update();} } - From 2d6efecefe040297679ac64a0a6fdb97971c4498 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Fri, 22 Nov 2019 12:18:22 +0100 Subject: [PATCH 07/17] Code style changes --- plugins/Vectorscope/VecControls.cpp | 16 +++++++-------- plugins/Vectorscope/VecControls.h | 8 ++++---- plugins/Vectorscope/VectorView.cpp | 30 ++++++++++++++--------------- plugins/Vectorscope/VectorView.h | 6 +++--- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/Vectorscope/VecControls.cpp b/plugins/Vectorscope/VecControls.cpp index dbdd95a69c0..0e7a2d06160 100644 --- a/plugins/Vectorscope/VecControls.cpp +++ b/plugins/Vectorscope/VecControls.cpp @@ -54,17 +54,17 @@ EffectControlDialog* VecControls::createView() } -void VecControls::loadSettings(const QDomElement &_this) +void VecControls::loadSettings(const QDomElement &element) { - m_persistenceModel.loadSettings(_this, "Persistence"); - m_logarithmicModel.loadSettings(_this, "Logarithmic"); - m_highQualityModel.loadSettings(_this, "HighQuality"); + m_persistenceModel.loadSettings(element, "Persistence"); + m_logarithmicModel.loadSettings(element, "Logarithmic"); + m_highQualityModel.loadSettings(element, "HighQuality"); } -void VecControls::saveSettings(QDomDocument &doc, QDomElement &parent) +void VecControls::saveSettings(QDomDocument &document, QDomElement &element) { - m_persistenceModel.saveSettings(doc, parent, "Persistence"); - m_logarithmicModel.saveSettings(doc, parent, "Logarithmic"); - m_highQualityModel.saveSettings(doc, parent, "HighQuality"); + m_persistenceModel.saveSettings(document, element, "Persistence"); + m_logarithmicModel.saveSettings(document, element, "Logarithmic"); + m_highQualityModel.saveSettings(document, element, "HighQuality"); } diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h index 8dded0ba10f..99ec00b73d8 100644 --- a/plugins/Vectorscope/VecControls.h +++ b/plugins/Vectorscope/VecControls.h @@ -37,13 +37,13 @@ class VecControls : public EffectControls { Q_OBJECT public: - explicit VecControls(Vectorscope* effect); + explicit VecControls(Vectorscope *effect); virtual ~VecControls() {} - EffectControlDialog* createView() override; + EffectControlDialog *createView() override; - void saveSettings (QDomDocument& doc, QDomElement& parent) override; - void loadSettings (const QDomElement &_this) override; + void saveSettings (QDomDocument &document, QDomElement &element) override; + void loadSettings (const QDomElement &element) override; QString nodeName() const override {return "Vectorscope";} int controlCount() override {return 3;} diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 04eda4af798..a8c78c6f2fc 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -50,18 +50,18 @@ VectorView::VectorView(VecControls *controls, LocklessRingBuffer *i m_displayBuffer.resize(sizeof qRgb(0,0,0) * m_displaySize * m_displaySize, 0); - #ifdef VEC_DEBUG - m_executionAvg = 0; - #endif +#ifdef VEC_DEBUG + m_executionAvg = 0; +#endif } // Compose and draw all the content; called by Qt. void VectorView::paintEvent(QPaintEvent *event) { - #ifdef VEC_DEBUG - unsigned int drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - #endif +#ifdef VEC_DEBUG + unsigned int drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count(); +#endif // All drawing done in this method, local variables are sufficient for the boundary const int displayTop = 2; @@ -122,7 +122,7 @@ void VectorView::paintEvent(QPaintEvent *event) // occurs in high-intensity traces in HQ mode. for (std::size_t i = 0; i < useableBuffer; i++) { - m_displayBuffer.data()[i] = m_displayBuffer.data()[i] * persistPerFrame; + m_displayBuffer.data()[i] *= persistPerFrame; } } @@ -264,14 +264,14 @@ void VectorView::paintEvent(QPaintEvent *event) painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 2.f, 2.f); // Optionally measure drawing performance - #ifdef VEC_DEBUG - drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count() - drawTime; - m_executionAvg = 0.95f * m_executionAvg + 0.05f * drawTime / 1000000.f; - painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); - painter.setFont(normalFont); - painter.drawText(displayWidth / 2 - 50, displayBottom - 16, 100, 16, Qt::AlignLeft, - QString("Exec avg.: ").append(std::to_string(m_executionAvg).substr(0, 5).c_str()).append(" ms")); - #endif +#ifdef VEC_DEBUG + drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count() - drawTime; + m_executionAvg = 0.95f * m_executionAvg + 0.05f * drawTime / 1000000.f; + painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.setFont(normalFont); + painter.drawText(displayWidth / 2 - 50, displayBottom - 16, 100, 16, Qt::AlignLeft, + QString("Exec avg.: ").append(std::to_string(m_executionAvg).substr(0, 5).c_str()).append(" ms")); +#endif } diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h index 6c1d2e9c3aa..7c6b147780b 100644 --- a/plugins/Vectorscope/VectorView.h +++ b/plugins/Vectorscope/VectorView.h @@ -66,8 +66,8 @@ private slots: int m_oldX; int m_oldY; - #ifdef VEC_DEBUG - float m_executionAvg = 0; - #endif +#ifdef VEC_DEBUG + float m_executionAvg = 0; +#endif }; #endif // VECTORVIEW_H From b4ca67523a1cca89f2adc8d851d7f656bc2c4183 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Fri, 22 Nov 2019 13:57:14 +0100 Subject: [PATCH 08/17] Optimizations --- plugins/Vectorscope/VectorView.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index a8c78c6f2fc..251e29b65fe 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -139,15 +139,15 @@ void VectorView::paintEvent(QPaintEvent *event) // Helper lambda functions for better readability // Make sure pixel stays within display bounds: - auto saturate = [=](short pixelPos) {return fmax(fmin(pixelPos, activeSize - 1), 0);}; + auto saturate = [=](short pixelPos) {return qBound((short)0, pixelPos, (short)(activeSize - 1));}; // Take existing pixel and brigthen it. Very bright light should reduce saturation and become // white. This effect is easily approximated by capping elementary colors to 255 individually. - auto updatePixel = [&](unsigned int x, unsigned int y, QColor addedColor) + auto updatePixel = [&](unsigned short x, unsigned short y, QColor addedColor) { QColor currentColor = ((QRgb*)m_displayBuffer.data())[x + y * activeSize]; - currentColor.setRed(fmin(currentColor.red() + addedColor.red(), 255)); - currentColor.setGreen(fmin(currentColor.green() + addedColor.green(), 255)); - currentColor.setBlue(fmin(currentColor.blue() + addedColor.blue(), 255)); + currentColor.setRed(std::min(currentColor.red() + addedColor.red(), 255)); + currentColor.setGreen(std::min(currentColor.green() + addedColor.green(), 255)); + currentColor.setBlue(std::min(currentColor.blue() + addedColor.blue(), 255)); ((QRgb*)m_displayBuffer.data())[x + y * activeSize] = currentColor.rgb(); }; @@ -162,7 +162,8 @@ void VectorView::paintEvent(QPaintEvent *event) { // To better preserve shapes, the log scale is applied to the distance from origin, // not the individual channels. - const float distance = sqrt(pow(inBuffer[frame][0], 2) + pow(inBuffer[frame][1], 2)); + const float distance = sqrt(inBuffer[frame][0] * inBuffer[frame][0] + + inBuffer[frame][1] * inBuffer[frame][1]); const float distanceLog = log10(1 + 9 * abs(distance)); const float angleCos = inBuffer[frame][0] / distance; const float angleSin = inBuffer[frame][1] / distance; @@ -180,7 +181,7 @@ void VectorView::paintEvent(QPaintEvent *event) y = saturate(activeSize - (right + left + activeSize / 2.f)); // Estimate number of points needed to fill space between the old and new pixel. Cap at 100. - unsigned char points = fmin(sqrt(pow(m_oldX - x, 2.f) + pow(m_oldY - y, 2.f)), 100); + unsigned char points = std::min((int)sqrt((m_oldX - x) * (m_oldX - x) + (m_oldY - y) * (m_oldY - y)), 100); // Large distance = dim trace. The curve for darker() is choosen so that: // - no movement (0 points) actually _increases_ brightness slightly, @@ -214,7 +215,8 @@ void VectorView::paintEvent(QPaintEvent *event) for (std::size_t frame = 0; frame < frameCount; frame++) { if (logScale) { - const float distance = sqrt(pow(inBuffer[frame][0], 2) + pow(inBuffer[frame][1], 2)); + const float distance = sqrt(inBuffer[frame][0] * inBuffer[frame][0] + + inBuffer[frame][1] * inBuffer[frame][1]); const float distanceLog = log10(1 + 9 * abs(distance)); const float angleCos = inBuffer[frame][0] / distance; const float angleSin = inBuffer[frame][1] / distance; From 763eae6230ce6c53318aa255191991aa58dcb10f Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Fri, 22 Nov 2019 14:34:29 +0100 Subject: [PATCH 09/17] Allow color changes --- plugins/Vectorscope/VectorView.cpp | 9 +++++++++ plugins/Vectorscope/VectorView.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 251e29b65fe..a2723e55f7f 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -283,3 +284,11 @@ void VectorView::periodicUpdate() m_visible = isVisible(); if (m_visible) {update();} } + + +// Allow to change color on double-click. +// More of an Easter egg, to avoid cluttering the interface with non-essential functionality. +void VectorView::mouseDoubleClickEvent(QMouseEvent *event) +{ + m_controls->m_colorFG = QColorDialog::getColor(m_controls->m_colorFG); +} diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h index 7c6b147780b..b5939a4557a 100644 --- a/plugins/Vectorscope/VectorView.h +++ b/plugins/Vectorscope/VectorView.h @@ -23,6 +23,7 @@ #ifndef VECTORVIEW_H #define VECTORVIEW_H +#include #include #include "Knob.h" @@ -45,6 +46,7 @@ class VectorView : public QWidget protected: void paintEvent(QPaintEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; private slots: void periodicUpdate(); From 9f12710e1668f47a27c5377f7a98f761ad7962b7 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Sat, 23 Nov 2019 15:20:43 +0100 Subject: [PATCH 10/17] Implement mouse wheel zoom --- plugins/Vectorscope/VectorView.cpp | 54 +++++++++++++++++++++++------- plugins/Vectorscope/VectorView.h | 5 +++ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index a2723e55f7f..9aab43cad44 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -39,7 +39,9 @@ VectorView::VectorView(VecControls *controls, LocklessRingBuffer *i m_inputBuffer(inputBuffer), m_bufferReader(*inputBuffer), m_displaySize(displaySize), + m_zoom(1.f), m_persistTimestamp(0), + m_zoomTimestamp(0), m_oldHQ(m_controls->m_highQualityModel.value()), m_oldX(m_displaySize / 2), m_oldY(m_displaySize / 2) @@ -158,23 +160,24 @@ void VectorView::paintEvent(QPaintEvent *event) // The longer the line is, the dimmer, simulating real electron trace on luminescent screen. for (std::size_t frame = 0; frame < frameCount; frame++) { + float inLeft = inBuffer[frame][0] * m_zoom; + float inRight = inBuffer[frame][1] * m_zoom; // Scale left and right channel from (-1.0, 1.0) to display range if (logScale) { // To better preserve shapes, the log scale is applied to the distance from origin, // not the individual channels. - const float distance = sqrt(inBuffer[frame][0] * inBuffer[frame][0] + - inBuffer[frame][1] * inBuffer[frame][1]); + const float distance = sqrt(inLeft * inLeft + inRight * inRight); const float distanceLog = log10(1 + 9 * abs(distance)); - const float angleCos = inBuffer[frame][0] / distance; - const float angleSin = inBuffer[frame][1] / distance; + const float angleCos = inLeft / distance; + const float angleSin = inRight / distance; left = distanceLog * angleCos * (activeSize - 1) / 4; right = distanceLog * angleSin * (activeSize - 1) / 4; } else { - left = inBuffer[frame][0] * (activeSize - 1) / 4; - right = inBuffer[frame][1] * (activeSize - 1) / 4; + left = inLeft * (activeSize - 1) / 4; + right = inRight * (activeSize - 1) / 4; } // Rotate display coordinates 45 degrees, flip Y axis and make sure the result stays within bounds @@ -215,17 +218,18 @@ void VectorView::paintEvent(QPaintEvent *event) // one full-color pixel per sample. for (std::size_t frame = 0; frame < frameCount; frame++) { + float inLeft = inBuffer[frame][0] * m_zoom; + float inRight = inBuffer[frame][1] * m_zoom; if (logScale) { - const float distance = sqrt(inBuffer[frame][0] * inBuffer[frame][0] + - inBuffer[frame][1] * inBuffer[frame][1]); + const float distance = sqrt(inLeft * inLeft + inRight * inRight); const float distanceLog = log10(1 + 9 * abs(distance)); - const float angleCos = inBuffer[frame][0] / distance; - const float angleSin = inBuffer[frame][1] / distance; + const float angleCos = inLeft / distance; + const float angleSin = inRight / distance; left = distanceLog * angleCos * (activeSize - 1) / 4; right = distanceLog * angleSin * (activeSize - 1) / 4; } else { - left = inBuffer[frame][0] * (activeSize - 1) / 4; - right = inBuffer[frame][1] * (activeSize - 1) / 4; + left = inLeft * (activeSize - 1) / 4; + right = inRight * (activeSize - 1) / 4; } x = saturate(right - left + activeSize / 2.f); y = saturate(activeSize - (right + left + activeSize / 2.f)); @@ -266,6 +270,15 @@ void VectorView::paintEvent(QPaintEvent *event) painter.setPen(QPen(m_controls->m_colorOutline, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 2.f, 2.f); + // Draw zoom info if changed within last second (re-using timestamp acquired for dimming) + if (currentTimestamp - m_zoomTimestamp < 1000) + { + painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin)); + painter.setFont(normalFont); + painter.drawText(displayWidth / 2 - 50, displayBottom - 20, 100, 16, Qt::AlignCenter, + QString("Zoom: ").append(std::to_string((int)round(m_zoom * 100)).c_str()).append(" %")); + } + // Optionally measure drawing performance #ifdef VEC_DEBUG drawTime = std::chrono::high_resolution_clock::now().time_since_epoch().count() - drawTime; @@ -292,3 +305,20 @@ void VectorView::mouseDoubleClickEvent(QMouseEvent *event) { m_controls->m_colorFG = QColorDialog::getColor(m_controls->m_colorFG); } + + +// Change zoom level using the mouse wheel +void VectorView::wheelEvent(QWheelEvent *event) +{ + // Go through integers to avoid accumulating errors + const unsigned short old_zoom = round(100 * m_zoom); + // Min-max bounds are 20 and 1000 %, step for 15°-increment mouse wheel is 20 % + const unsigned short new_zoom = qBound(20, old_zoom + event->angleDelta().y() / 6, 1000); + m_zoom = new_zoom / 100.f; + event->accept(); + m_zoomTimestamp = std::chrono::duration_cast + ( + std::chrono::high_resolution_clock::now().time_since_epoch() + ).count(); + +} diff --git a/plugins/Vectorscope/VectorView.h b/plugins/Vectorscope/VectorView.h index b5939a4557a..066e306a0a0 100644 --- a/plugins/Vectorscope/VectorView.h +++ b/plugins/Vectorscope/VectorView.h @@ -24,6 +24,7 @@ #define VECTORVIEW_H #include +#include #include #include "Knob.h" @@ -47,6 +48,7 @@ class VectorView : public QWidget protected: void paintEvent(QPaintEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; private slots: void periodicUpdate(); @@ -62,8 +64,11 @@ private slots: bool m_visible; + float m_zoom; + // State variables for comparison with previous repaint unsigned int m_persistTimestamp; + unsigned int m_zoomTimestamp; bool m_oldHQ; int m_oldX; int m_oldY; From 84b441858eda321016cd6947e69b05dc5d4c89ed Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Sat, 23 Nov 2019 17:28:27 +0100 Subject: [PATCH 11/17] Check color validity when setting new color --- plugins/Vectorscope/VectorView.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 9aab43cad44..953b2a633af 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -303,7 +303,11 @@ void VectorView::periodicUpdate() // More of an Easter egg, to avoid cluttering the interface with non-essential functionality. void VectorView::mouseDoubleClickEvent(QMouseEvent *event) { - m_controls->m_colorFG = QColorDialog::getColor(m_controls->m_colorFG); + QColor new_color = QColorDialog::getColor(m_controls->m_colorFG); + if (new_color.isValid()) + { + m_controls->m_colorFG = new_color; + } } From 447c6b0421e0be392bd00a2f3fbbdc84173dd82a Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Sun, 24 Nov 2019 21:30:04 +0100 Subject: [PATCH 12/17] Forward key release to parent (by CYBERDEViLNL) --- include/ColorChooser.h | 40 ++++++++++++++++++++++++++++++ plugins/Vectorscope/VectorView.cpp | 4 +-- 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 include/ColorChooser.h diff --git a/include/ColorChooser.h b/include/ColorChooser.h new file mode 100644 index 00000000000..c91d27c887c --- /dev/null +++ b/include/ColorChooser.h @@ -0,0 +1,40 @@ +/* ColorChooser.h - declaration and definition of ColorChooser class. + * + * Copyright (c) 2019 CYBERDEViLNL + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include + +class ColorChooser: public QColorDialog +{ +public: + ColorChooser(QWidget *parent): QColorDialog(parent) {}; + +protected: + // Forward key events to the parent to prevent stuck notes when the dialog gets focus + void keyReleaseEvent(QKeyEvent *event) override + { + QKeyEvent ke(*event); + QApplication::sendEvent(parentWidget(), &ke); + } +}; diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 953b2a633af..4ad1e717079 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -25,10 +25,10 @@ #include #include #include -#include #include #include +#include "ColorChooser.h" #include "GuiApplication.h" #include "MainWindow.h" @@ -303,7 +303,7 @@ void VectorView::periodicUpdate() // More of an Easter egg, to avoid cluttering the interface with non-essential functionality. void VectorView::mouseDoubleClickEvent(QMouseEvent *event) { - QColor new_color = QColorDialog::getColor(m_controls->m_colorFG); + QColor new_color = ColorChooser::getColor(m_controls->m_colorFG); if (new_color.isValid()) { m_controls->m_colorFG = new_color; From 48cfb3317890b108c4663c9ba16e55f7935a46c7 Mon Sep 17 00:00:00 2001 From: CYBERDEViLNL Date: Tue, 26 Nov 2019 18:21:58 +0000 Subject: [PATCH 13/17] * ColorChooser: initial color. * Replace dummy email. * Use `exec()` instead of `::getColor()` --- include/ColorChooser.h | 3 ++- plugins/Vectorscope/VectorView.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/ColorChooser.h b/include/ColorChooser.h index c91d27c887c..fe5b7a22a4e 100644 --- a/include/ColorChooser.h +++ b/include/ColorChooser.h @@ -1,6 +1,6 @@ /* ColorChooser.h - declaration and definition of ColorChooser class. * - * Copyright (c) 2019 CYBERDEViLNL + * Copyright (c) 2019 CYBERDEViLNL * * This file is part of LMMS - https://lmms.io * @@ -28,6 +28,7 @@ class ColorChooser: public QColorDialog { public: + ColorChooser(const QColor &initial, QWidget *parent): QColorDialog(initial, parent) {}; ColorChooser(QWidget *parent): QColorDialog(parent) {}; protected: diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index 4ad1e717079..9a3f855eb98 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -303,10 +303,10 @@ void VectorView::periodicUpdate() // More of an Easter egg, to avoid cluttering the interface with non-essential functionality. void VectorView::mouseDoubleClickEvent(QMouseEvent *event) { - QColor new_color = ColorChooser::getColor(m_controls->m_colorFG); - if (new_color.isValid()) + ColorChooser *colorDialog = new ColorChooser(m_controls->m_colorFG, this); + if (colorDialog->exec()) { - m_controls->m_colorFG = new_color; + m_controls->m_colorFG = colorDialog->currentColor(); } } From b043792025b54e8a2cdd9a30aedba1dc12f00fae Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Tue, 3 Dec 2019 09:31:44 +0100 Subject: [PATCH 14/17] TEMP: print debug info around locks with explicit unlocks --- plugins/SpectrumAnalyzer/SaControls.h | 2 +- plugins/SpectrumAnalyzer/SaProcessor.cpp | 4 ++++ plugins/SpectrumAnalyzer/SaWaterfallView.cpp | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/SpectrumAnalyzer/SaControls.h b/plugins/SpectrumAnalyzer/SaControls.h index 4673416bc20..27c5efa836e 100644 --- a/plugins/SpectrumAnalyzer/SaControls.h +++ b/plugins/SpectrumAnalyzer/SaControls.h @@ -29,7 +29,7 @@ #include "EffectControls.h" #include "lmms_constants.h" -//#define SA_DEBUG 1 // define SA_DEBUG to enable performance measurements +#define SA_DEBUG 1 // define SA_DEBUG to enable performance measurements class Analyzer; diff --git a/plugins/SpectrumAnalyzer/SaProcessor.cpp b/plugins/SpectrumAnalyzer/SaProcessor.cpp index 9d83f2916f0..c92999b6e1d 100644 --- a/plugins/SpectrumAnalyzer/SaProcessor.cpp +++ b/plugins/SpectrumAnalyzer/SaProcessor.cpp @@ -392,6 +392,7 @@ void SaProcessor::reallocateBuffers() // Lock data shared with SaSpectrumView and SaWaterfallView. // Reallocation lock must be acquired first to avoid deadlock (a view class // may already have it and request the "stronger" data lock on top of that). +std::cout<<"get locks"< #ifdef SA_DEBUG #include + #include #endif #include #include @@ -138,12 +139,15 @@ void SaWaterfallView::paintEvent(QPaintEvent *event) // draw the spectrogram precomputed in SaProcessor if (m_processor->waterfallNotEmpty()) { +std::cout<<"get waterfall lock"<m_reallocationAccess); QImage temp = QImage(m_processor->getHistory(), // raw pixel data to display m_processor->waterfallWidth(), // width = number of frequency bins m_processor->waterfallHeight(), // height = number of history lines QImage::Format_RGB32); +std::cout<<"going to unlock reloc from waterfall"< Date: Tue, 3 Dec 2019 19:59:34 +0100 Subject: [PATCH 15/17] Revert "TEMP: print debug info around locks with explicit unlocks" This reverts commit b043792025b54e8a2cdd9a30aedba1dc12f00fae. --- plugins/SpectrumAnalyzer/SaControls.h | 2 +- plugins/SpectrumAnalyzer/SaProcessor.cpp | 4 ---- plugins/SpectrumAnalyzer/SaWaterfallView.cpp | 4 ---- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/plugins/SpectrumAnalyzer/SaControls.h b/plugins/SpectrumAnalyzer/SaControls.h index 27c5efa836e..4673416bc20 100644 --- a/plugins/SpectrumAnalyzer/SaControls.h +++ b/plugins/SpectrumAnalyzer/SaControls.h @@ -29,7 +29,7 @@ #include "EffectControls.h" #include "lmms_constants.h" -#define SA_DEBUG 1 // define SA_DEBUG to enable performance measurements +//#define SA_DEBUG 1 // define SA_DEBUG to enable performance measurements class Analyzer; diff --git a/plugins/SpectrumAnalyzer/SaProcessor.cpp b/plugins/SpectrumAnalyzer/SaProcessor.cpp index c92999b6e1d..9d83f2916f0 100644 --- a/plugins/SpectrumAnalyzer/SaProcessor.cpp +++ b/plugins/SpectrumAnalyzer/SaProcessor.cpp @@ -392,7 +392,6 @@ void SaProcessor::reallocateBuffers() // Lock data shared with SaSpectrumView and SaWaterfallView. // Reallocation lock must be acquired first to avoid deadlock (a view class // may already have it and request the "stronger" data lock on top of that). -std::cout<<"get locks"< #ifdef SA_DEBUG #include - #include #endif #include #include @@ -139,15 +138,12 @@ void SaWaterfallView::paintEvent(QPaintEvent *event) // draw the spectrogram precomputed in SaProcessor if (m_processor->waterfallNotEmpty()) { -std::cout<<"get waterfall lock"<m_reallocationAccess); QImage temp = QImage(m_processor->getHistory(), // raw pixel data to display m_processor->waterfallWidth(), // width = number of frequency bins m_processor->waterfallHeight(), // height = number of history lines QImage::Format_RGB32); -std::cout<<"going to unlock reloc from waterfall"< Date: Tue, 3 Dec 2019 20:03:58 +0100 Subject: [PATCH 16/17] fix locking of the useless_lock in LocklessRingBuffer.h --- include/LocklessRingBuffer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/LocklessRingBuffer.h b/include/LocklessRingBuffer.h index 3b18dd475d6..d313fd72288 100644 --- a/include/LocklessRingBuffer.h +++ b/include/LocklessRingBuffer.h @@ -122,6 +122,7 @@ class LocklessRingBufferReader : public ringbuffer_reader_t void waitForData() { QMutex useless_lock; + useless_lock.lock(); m_notifier->wait(&useless_lock); useless_lock.unlock(); } From 0e3359e0ec95d364201ecf68c4b41bca8fd72c8d Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Thu, 6 Feb 2020 09:06:16 +0100 Subject: [PATCH 17/17] Fix tabs in SaControls.h and VecControls.h --- plugins/SpectrumAnalyzer/SaControls.h | 14 +++++++------- plugins/Vectorscope/VecControls.h | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/SpectrumAnalyzer/SaControls.h b/plugins/SpectrumAnalyzer/SaControls.h index 4673416bc20..ee8a9e001fe 100644 --- a/plugins/SpectrumAnalyzer/SaControls.h +++ b/plugins/SpectrumAnalyzer/SaControls.h @@ -81,13 +81,13 @@ class SaControls : public EffectControls FloatModel m_zeroPaddingModel; // colors (hard-coded, values must add up to specific numbers) - QColor m_colorL; //!< color of the left channel - QColor m_colorR; //!< color of the right channel - QColor m_colorMono; //!< mono color for spectrum display - QColor m_colorMonoW; //!< mono color for waterfall display - QColor m_colorBG; //!< spectrum display background color - QColor m_colorGrid; //!< color of grid lines - QColor m_colorLabels; //!< color of axis labels + QColor m_colorL; //!< color of the left channel + QColor m_colorR; //!< color of the right channel + QColor m_colorMono; //!< mono color for spectrum display + QColor m_colorMonoW; //!< mono color for waterfall display + QColor m_colorBG; //!< spectrum display background color + QColor m_colorGrid; //!< color of grid lines + QColor m_colorLabels; //!< color of axis labels friend class SaControlsDialog; friend class SaSpectrumView; diff --git a/plugins/Vectorscope/VecControls.h b/plugins/Vectorscope/VecControls.h index 99ec00b73d8..04b688e5a5b 100644 --- a/plugins/Vectorscope/VecControls.h +++ b/plugins/Vectorscope/VecControls.h @@ -55,10 +55,10 @@ class VecControls : public EffectControls BoolModel m_logarithmicModel; BoolModel m_highQualityModel; - QColor m_colorFG; - QColor m_colorGrid; - QColor m_colorLabels; - QColor m_colorOutline; + QColor m_colorFG; + QColor m_colorGrid; + QColor m_colorLabels; + QColor m_colorOutline; friend class VecControlsDialog; friend class VectorView;