Add the vectorscope plugin (#5328)

The credit for the `ColorChooser` class goes to CYBERDEViLNL.
This commit is contained in:
Martin Pavelek
2020-02-07 07:00:42 +01:00
committed by GitHub
parent 8679f79e2b
commit 89d8363218
14 changed files with 883 additions and 7 deletions

View File

@@ -64,6 +64,7 @@ SET(LMMS_PLUGIN_LIST
VstEffect
watsyn
waveshaper
Vectorscope
vibed
Xpressive
zynaddsubfx

41
include/ColorChooser.h Normal file
View File

@@ -0,0 +1,41 @@
/* ColorChooser.h - declaration and definition of ColorChooser class.
*
* Copyright (c) 2019 CYBERDEViLNL <cyberdevilnl/at/protonmail/dot/ch>
*
* 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 <QColorDialog>
#include <QApplication>
#include <QKeyEvent>
class ColorChooser: public QColorDialog
{
public:
ColorChooser(const QColor &initial, QWidget *parent): QColorDialog(initial, parent) {};
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);
}
};

View File

@@ -81,13 +81,13 @@ private:
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;

View File

@@ -0,0 +1,3 @@
INCLUDE(BuildPlugin)
BUILD_PLUGIN(vectorscope Vectorscope.cpp VecControls.cpp VecControlsDialog.cpp VectorView.cpp
MOCFILES VecControls.h VecControlsDialog.h VectorView.h EMBEDDED_RESOURCES logo.png)

View File

@@ -0,0 +1,14 @@
# Vectorscope plugin
## Overview
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.
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-11-21
- initial release

View File

@@ -0,0 +1,70 @@
/*
* VecControls.cpp - definition of VecControls class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 <QtXml/QDomElement>
#include "VecControlsDialog.h"
#include "Vectorscope.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")),
m_highQualityModel(false, this, tr("High quality"))
{
// 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)
}
// Create the VecControlDialog widget which handles display of GUI elements.
EffectControlDialog* VecControls::createView()
{
return new VecControlsDialog(this);
}
void VecControls::loadSettings(const QDomElement &element)
{
m_persistenceModel.loadSettings(element, "Persistence");
m_logarithmicModel.loadSettings(element, "Logarithmic");
m_highQualityModel.loadSettings(element, "HighQuality");
}
void VecControls::saveSettings(QDomDocument &document, QDomElement &element)
{
m_persistenceModel.saveSettings(document, element, "Persistence");
m_logarithmicModel.saveSettings(document, element, "Logarithmic");
m_highQualityModel.saveSettings(document, element, "HighQuality");
}

View File

@@ -0,0 +1,66 @@
/*
* VecControls.h - declaration of VecControls class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 <QColor>
#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 &document, QDomElement &element) override;
void loadSettings (const QDomElement &element) override;
QString nodeName() const override {return "Vectorscope";}
int controlCount() override {return 3;}
private:
Vectorscope *m_effect;
FloatModel m_persistenceModel;
BoolModel m_logarithmicModel;
BoolModel m_highQualityModel;
QColor m_colorFG;
QColor m_colorGrid;
QColor m_colorLabels;
QColor m_colorOutline;
friend class VecControlsDialog;
friend class VectorView;
};
#endif // VECCONTROLS_H

View File

@@ -0,0 +1,94 @@
/*
* VecControlsDialog.cpp - definition of VecControlsDialog class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 <QGridLayout>
#include <QLabel>
#include <QResizeEvent>
#include <QSizePolicy>
#include <QWidget>
#include "embed.h"
#include "LedCheckbox.h"
#include "VecControls.h"
#include "Vectorscope.h"
#include "VectorView.h"
// The entire GUI layout is built here.
VecControlsDialog::VecControlsDialog(VecControls *controls) :
EffectControlDialog(controls),
m_controls(controls)
{
QVBoxLayout *master_layout = new QVBoxLayout;
master_layout->setContentsMargins(0, 2, 0, 0);
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);
// 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);
// 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->setCheckable(true);
logarithmicButton->setMinimumSize(70, 12);
logarithmicButton->setModel(&controls->m_logarithmicModel);
switch_layout->addWidget(logarithmicButton);
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 widget size.
QSize VecControlsDialog::sizeHint() const
{
return QSize(275, 300);
}

View File

@@ -0,0 +1,47 @@
/*
* VecControlsDialog.h - declatation of VecControlsDialog class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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

View File

@@ -0,0 +1,328 @@
/* VectorView.cpp - implementation of VectorView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 <algorithm>
#include <chrono>
#include <cmath>
#include <QImage>
#include <QPainter>
#include "ColorChooser.h"
#include "GuiApplication.h"
#include "MainWindow.h"
VectorView::VectorView(VecControls *controls, LocklessRingBuffer<sampleFrame> *inputBuffer, unsigned short displaySize, QWidget *parent) :
QWidget(parent),
m_controls(controls),
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)
{
setMinimumSize(200, 200);
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 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
// All drawing done in this method, local variables are sufficient for the boundary
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 float centerX = displayLeft + (displayWidth / 2.f);
const float centerY = displayTop + (displayWidth / 2.f);
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(26);
boldFont.setBold(true);
const int labelWidth = 26;
const int labelHeight = 26;
bool hq = m_controls->m_highQualityModel.value();
// Clear display buffer if quality setting was changed
if (hq != m_oldHQ)
{
m_oldHQ = hq;
for (std::size_t i = 0; i < m_displayBuffer.size(); i++)
{
m_displayBuffer.data()[i] = 0;
}
}
// 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::milliseconds>
(
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;
// 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.
// → 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] *= persistPerFrame;
}
}
// Get new samples from the lockless input FIFO buffer
auto inBuffer = m_bufferReader.read_max(m_inputBuffer->capacity());
std::size_t frameCount = inBuffer.size();
// 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 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 short x, unsigned short y, QColor addedColor)
{
QColor currentColor = ((QRgb*)m_displayBuffer.data())[x + y * activeSize];
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();
};
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.
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(inLeft * inLeft + inRight * inRight);
const float distanceLog = log10(1 + 9 * abs(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 = 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
x = saturate(right - left + activeSize / 2.f);
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 = 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,
// - 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 (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
// → add new value to existing pixel state.
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 * newX) / points);
y = saturate(((points - i) * m_oldY + i * newY) / points);
updatePixel(x, y, addedColor);
}
m_oldX = newX;
m_oldY = newY;
}
}
else
{
// 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 inLeft = inBuffer[frame][0] * m_zoom;
float inRight = inBuffer[frame][1] * m_zoom;
if (logScale) {
const float distance = sqrt(inLeft * inLeft + inRight * inRight);
const float distanceLog = log10(1 + 9 * abs(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 = inLeft * (activeSize - 1) / 4;
right = inRight * (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();
}
}
// Draw background
painter.fillRect(displayLeft, displayTop, displayWidth, displayHeight, QColor(0,0,0));
// Draw the final image
QImage temp = QImage(m_displayBuffer.data(),
activeSize,
activeSize,
QImage::Format_RGB32);
temp.setDevicePixelRatio(devicePixelRatio());
painter.drawImage(displayLeft, displayTop,
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));
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,
labelWidth, labelHeight, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip,
QString("L"));
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_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;
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
}
// Periodically trigger repaint and check if the widget is visible
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)
{
ColorChooser *colorDialog = new ColorChooser(m_controls->m_colorFG, this);
if (colorDialog->exec())
{
m_controls->m_colorFG = colorDialog->currentColor();
}
}
// 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::milliseconds>
(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count();
}

View File

@@ -0,0 +1,80 @@
/* VectorView.h - declaration of VectorView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 <QMouseEvent>
#include <QWheelEvent>
#include <QWidget>
#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, LocklessRingBuffer<sampleFrame> *inputBuffer, unsigned short displaySize, QWidget *parent = 0);
virtual ~VectorView() {}
QSize sizeHint() const override {return QSize(300, 300);}
protected:
void paintEvent(QPaintEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private slots:
void periodicUpdate();
private:
VecControls *m_controls;
LocklessRingBuffer<sampleFrame> *m_inputBuffer;
LocklessRingBufferReader<sampleFrame> m_bufferReader;
std::vector<uchar> m_displayBuffer;
const unsigned short m_displaySize;
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;
#ifdef VEC_DEBUG
float m_executionAvg = 0;
#endif
};
#endif // VECTORVIEW_H

View File

@@ -0,0 +1,80 @@
/*
* Vectorscope.cpp - definition of Vectorscope class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 <he29/dot/HS/at/gmail/dot/com>",
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),
// 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 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())
{
// 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();
}
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<const Plugin::Descriptor::SubPluginFeatures::Key *>(data));
}
}

View File

@@ -0,0 +1,52 @@
/* Vectorscope.h - declaration of Vectorscope class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* 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 "Effect.h"
#include "LocklessRingBuffer.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;}
LocklessRingBuffer<sampleFrame> *getBuffer() {return &m_inputBuffer;}
private:
VecControls m_controls;
// Maximum LMMS buffer size (hard coded, the actual constant is hard to get)
const unsigned int m_maxBufferSize = 4096;
LocklessRingBuffer<sampleFrame> m_inputBuffer;
};
#endif // VECTORSCOPE_H

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B