diff --git a/CMakeLists.txt b/CMakeLists.txt
index e39d72cc133..5187dfad6ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1002,6 +1002,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/widget/wtracktext.cpp
src/widget/wtrackwidgetgroup.cpp
src/widget/wvumeter.cpp
+ src/widget/wvumetergl.cpp
src/widget/wwaveformviewer.cpp
src/widget/wwidget.cpp
src/widget/wwidgetgroup.cpp
diff --git a/res/skins/Shade/auxiliary.xml b/res/skins/Shade/auxiliary.xml
index 521a9258232..7a5e3be3c52 100644
--- a/res/skins/Shade/auxiliary.xml
+++ b/res/skins/Shade/auxiliary.xml
@@ -24,19 +24,25 @@
Visual - Volume level display
**********************************************
-->
-
- auxiliary_VuMeter
- skin:/style/volume_display_microphone_over.png
- skin:/style/volume_display_microphone.png
+
99,14
- 5
- 500
- 50
- 2
-
- [Auxiliary],VuMeter
-
-
+ 5f,35f
+ horizontal
+
+
+ auxiliary_VuMeter
+ skin:/style/volume_display_microphone_over.png
+ skin:/style/volume_display_microphone.png
+ 5
+ 500
+ 50
+ 2
+
+ [Auxiliary],VuMeter
+
+
+
+
auxiliary_pregain
diff --git a/res/skins/Shade/microphone.xml b/res/skins/Shade/microphone.xml
index 5c1606433fc..3a51a598b74 100644
--- a/res/skins/Shade/microphone.xml
+++ b/res/skins/Shade/microphone.xml
@@ -25,19 +25,25 @@
Visual - Volume level display
**********************************************
-->
-
- microphone_VuMeter
- skin:/style/volume_display_microphone_over.png
- skin:/style/volume_display_microphone.png
+
82,14
- 5
- 500
- 50
- 2
-
- [Microphone],VuMeter
-
-
+ 5f,35f
+ horizontal
+
+
+ microphone_VuMeter
+ skin:/style/volume_display_microphone_over.png
+ skin:/style/volume_display_microphone.png
+ 5
+ 500
+ 50
+ 2
+
+ [Microphone],VuMeter
+
+
+
+
-
- channel_VuMeter
- skin:/style/volume_display_over.png
- skin:/style/volume_display.png
+
107,76
- false
- 5
- 500
- 50
- 2
-
- [Channel1],VuMeter
-
-
-
- channel_VuMeter
- skin:/style/volume_display_over.png
- skin:/style/volume_display.png
+ 5f,81f
+ horizontal
+
+
+ channel_VuMeter
+ skin:/style/volume_display_over.png
+ skin:/style/volume_display.png
+ false
+ 5
+ 500
+ 50
+ 2
+ 1
+ vinyl_spinny_background.png
+ vinyl_spinny_foreground.png
+ vinyl_spinny_foreground_ghost.png
+
+ [Channel1],VuMeter
+
+
+
+
+
143,76
- false
- 5
- 500
- 50
- 2
-
- [Channel2],VuMeter
-
-
-
-
- master_VuMeterL
- skin:/style/volume_display_master_over.png
- skin:/style/volume_display_master.png
+ 5f,81f
+ horizontal
+
+
+ channel_VuMeter
+ skin:/style/volume_display_over.png
+ skin:/style/volume_display.png
+ false
+ 5
+ 500
+ 50
+ 2
+ 1
+ vinyl_spinny_background.png
+ vinyl_spinny_foreground.png
+ vinyl_spinny_foreground_ghost.png
+
+ [Channel2],VuMeter
+
+
+
+
+
122,76
- 5
- 500
- 50
- 2
-
- [Master],VuMeterL
-
-
-
- master_VuMeterR
- skin:/style/volume_display_master_over.png
- skin:/style/volume_display_master.png
+ 5f,81f
+ horizontal
+
+
+ master_VuMeterL
+ skin:/style/volume_display_master_over.png
+ skin:/style/volume_display_master.png
+ 5
+ 500
+ 50
+ 2
+ 1
+ vinyl_spinny_background.png
+ vinyl_spinny_foreground.png
+ vinyl_spinny_foreground_ghost.png
+
+ [Master],VuMeterL
+
+
+
+
+
128,76
- 5
- 500
- 50
- 2
-
- [Master],VuMeterR
-
-
-
+ 5f,81f
+ horizontal
+
+
+ master_VuMeterR
+ skin:/style/volume_display_master_over.png
+ skin:/style/volume_display_master.png
+ 5
+ 500
+ 50
+ 2
+ 1
+ vinyl_spinny_background.png
+ vinyl_spinny_foreground.png
+ vinyl_spinny_foreground_ghost.png
+
+ [Master],VuMeterR
+
+
+
+
diff --git a/src/main.cpp b/src/main.cpp
index 1393a3fde0f..13264af1995 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,8 @@
#include
#include
#include
+#include
+#include
#include "errordialoghandler.h"
#include "mixxx.h"
diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp
index 23b04855836..3da36b6e51d 100644
--- a/src/skin/legacy/legacyskinparser.cpp
+++ b/src/skin/legacy/legacyskinparser.cpp
@@ -79,6 +79,7 @@
#include "widget/wtracktext.h"
#include "widget/wtrackwidgetgroup.h"
#include "widget/wvumeter.h"
+#include "widget/wvumetergl.h"
#include "widget/wwaveformviewer.h"
#include "widget/wwidget.h"
#include "widget/wwidgetgroup.h"
@@ -527,9 +528,7 @@ QList LegacySkinParser::parseNode(const QDomElement& node) {
} else if (nodeName == "StarRating") {
result = wrapWidget(parseStarRating(node));
} else if (nodeName == "VuMeter") {
- WVuMeter* pVuMeterWidget = parseStandardWidget(node);
- WaveformWidgetFactory::instance()->addTimerListener(pVuMeterWidget);
- result = wrapWidget(pVuMeterWidget);
+ result = wrapWidget(parseVuMeter(node));
} else if (nodeName == "StatusLight") {
result = wrapWidget(parseStandardWidget(node));
} else if (nodeName == "Display") {
@@ -1270,22 +1269,85 @@ QWidget* LegacySkinParser::parseSpinny(const QDomElement& node) {
SKIN_WARNING(node, *m_pContext) << "No player found for group:" << group;
return nullptr;
}
- WSpinny* spinny = new WSpinny(m_pParent, group, m_pConfig, m_pVCManager, pPlayer);
- commonWidgetSetup(node, spinny);
+ // Note: For some reasons on X11 we need to create the widget without a parent to avoid to
+ // create two platform windows (QXcbWindow) in QWidget::create() for this widget.
+ // This happens, because the QWidget::create() of a parent() will populate all children
+ // with platform windows q_createNativeChildrenAndSetParent() while another window is already
+ // under construction. The ID for the first window is not cleared and leads to a segfault
+ // during on shutdown. This has been tested with Qt 5.12.8 and 5.15.3
+ WSpinny* pSpinny;
+ if (qApp->platformName() == QLatin1String("xcb")) {
+ pSpinny = new WSpinny(nullptr, group, m_pConfig, m_pVCManager, pPlayer);
+ pSpinny->setParent(m_pParent);
+ } else {
+ pSpinny = new WSpinny(m_pParent, group, m_pConfig, m_pVCManager, pPlayer);
+ }
+ commonWidgetSetup(node, pSpinny);
connect(waveformWidgetFactory,
&WaveformWidgetFactory::renderSpinnies,
- spinny,
+ pSpinny,
&WSpinny::render);
- connect(waveformWidgetFactory, &WaveformWidgetFactory::swapSpinnies, spinny, &WSpinny::swap);
- connect(spinny, &WSpinny::trackDropped, m_pPlayerManager, &PlayerManager::slotLoadToPlayer);
- connect(spinny, &WSpinny::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck);
-
- spinny->setup(node, *m_pContext);
- spinny->installEventFilter(m_pKeyboard);
- spinny->installEventFilter(m_pControllerManager->getControllerLearningEventFilter());
- spinny->Init();
- return spinny;
+ connect(waveformWidgetFactory, &WaveformWidgetFactory::swapSpinnies, pSpinny, &WSpinny::swap);
+ connect(pSpinny, &WSpinny::trackDropped, m_pPlayerManager, &PlayerManager::slotLoadToPlayer);
+ connect(pSpinny, &WSpinny::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck);
+
+ pSpinny->setup(node, *m_pContext);
+ pSpinny->installEventFilter(m_pKeyboard);
+ pSpinny->installEventFilter(m_pControllerManager->getControllerLearningEventFilter());
+ pSpinny->Init();
+ return pSpinny;
+}
+
+QWidget* LegacySkinParser::parseVuMeter(const QDomElement& node) {
+ if (!CmdlineArgs::Instance().getUseVuMeterGL()) {
+ // Standard WVuMeter
+ WVuMeter* pVuMeterWidget = parseStandardWidget(node);
+ WaveformWidgetFactory::instance()->addVuMeter(pVuMeterWidget);
+ return pVuMeterWidget;
+ }
+
+ // QGLWidget derived WVuMeterGL
+
+ if (CmdlineArgs::Instance().getSafeMode()) {
+ WLabel* dummy = new WLabel(m_pParent);
+ //: Shown when Mixxx is running in safe mode.
+ dummy->setText(tr("Safe Mode Enabled"));
+ return dummy;
+ }
+
+ auto* waveformWidgetFactory = WaveformWidgetFactory::instance();
+ if (!waveformWidgetFactory->isOpenGlAvailable() &&
+ !waveformWidgetFactory->isOpenGlesAvailable()) {
+ WLabel* dummy = new WLabel(m_pParent);
+ //: Shown when VuMeter can not be displayed. Please keep \n unchanged
+ dummy->setText(tr("No OpenGL\nsupport."));
+ return dummy;
+ }
+ // Note: For some reasons on X11 we need to create the widget without a parent to avoid to
+ // create two platform windows (QXcbWindow) in QWidget::create() for this widget.
+ // This happens, because the QWidget::create() of a parent() will populate all children
+ // with platform windows q_createNativeChildrenAndSetParent() while another window is already
+ // under construction. The ID for the first window is not cleared and leads to a segfault
+ // during on shutdown. This has been tested with Qt 5.12.8 and 5.15.3
+ WVuMeterGL* pVuMeterWidget;
+ if (qApp->platformName() == QLatin1String("xcb")) {
+ pVuMeterWidget = new WVuMeterGL();
+ pVuMeterWidget->setParent(m_pParent);
+ } else {
+ pVuMeterWidget = new WVuMeterGL(m_pParent);
+ }
+ commonWidgetSetup(node, pVuMeterWidget);
+
+ waveformWidgetFactory->addVuMeter(pVuMeterWidget);
+
+ pVuMeterWidget->setup(node, *m_pContext);
+ pVuMeterWidget->installEventFilter(m_pKeyboard);
+ pVuMeterWidget->installEventFilter(
+ m_pControllerManager->getControllerLearningEventFilter());
+ pVuMeterWidget->Init();
+
+ return pVuMeterWidget;
}
QWidget* LegacySkinParser::parseSearchBox(const QDomElement& node) {
diff --git a/src/skin/legacy/legacyskinparser.h b/src/skin/legacy/legacyskinparser.h
index 69fc1393651..347cc3a82e6 100644
--- a/src/skin/legacy/legacyskinparser.h
+++ b/src/skin/legacy/legacyskinparser.h
@@ -110,6 +110,7 @@ class LegacySkinParser : public QObject, public SkinParser {
QWidget* parseVisual(const QDomElement& node);
QWidget* parseOverview(const QDomElement& node);
QWidget* parseSpinny(const QDomElement& node);
+ QWidget* parseVuMeter(const QDomElement& node);
// Library widgets.
QWidget* parseTableView(const QDomElement& node);
diff --git a/src/util/cmdlineargs.cpp b/src/util/cmdlineargs.cpp
index 36ecb2f0185..b643174b9fa 100644
--- a/src/util/cmdlineargs.cpp
+++ b/src/util/cmdlineargs.cpp
@@ -14,6 +14,7 @@ CmdlineArgs::CmdlineArgs()
m_midiDebug(false),
m_developer(false),
m_safeMode(false),
+ m_useVuMeterGL(true),
m_debugAssertBreak(false),
m_settingsPathSet(false),
m_logLevel(mixxx::kLogLevelDefault),
@@ -97,14 +98,22 @@ warnings and errors to the console unless this is set properly.\n", stdout);
when a critical error occurs unless this is set properly.\n", stdout);
}
i++;
- } else if (QString::fromLocal8Bit(argv[i]).contains("--midiDebug", Qt::CaseInsensitive) ||
- QString::fromLocal8Bit(argv[i]).contains("--controllerDebug", Qt::CaseInsensitive)) {
+ } else if (QString::fromLocal8Bit(argv[i]).contains(
+ "--disableVuMeterGL", Qt::CaseInsensitive)) {
+ m_useVuMeterGL = false;
+ } else if (QString::fromLocal8Bit(argv[i]).contains(
+ "--midiDebug", Qt::CaseInsensitive) ||
+ QString::fromLocal8Bit(argv[i]).contains(
+ "--controllerDebug", Qt::CaseInsensitive)) {
m_midiDebug = true;
- } else if (QString::fromLocal8Bit(argv[i]).contains("--developer", Qt::CaseInsensitive)) {
+ } else if (QString::fromLocal8Bit(argv[i]).contains(
+ "--developer", Qt::CaseInsensitive)) {
m_developer = true;
- } else if (QString::fromLocal8Bit(argv[i]).contains("--safeMode", Qt::CaseInsensitive)) {
+ } else if (QString::fromLocal8Bit(argv[i]).contains(
+ "--safeMode", Qt::CaseInsensitive)) {
m_safeMode = true;
- } else if (QString::fromLocal8Bit(argv[i]).contains("--debugAssertBreak", Qt::CaseInsensitive)) {
+ } else if (QString::fromLocal8Bit(argv[i]).contains(
+ "--debugAssertBreak", Qt::CaseInsensitive)) {
m_debugAssertBreak = true;
} else if (i > 0) {
// Don't try to load the program name to a deck
@@ -152,6 +161,8 @@ void CmdlineArgs::printUsage() {
and spinning vinyl widgets. Try this option if\n\
Mixxx is crashing on startup.\n\
\n\
+--disableVuMeterGL Do not use OpenGL vu meter.\n\
+\n\
--locale LOCALE Use a custom locale for loading translations\n\
(e.g 'fr')\n\
\n\
@@ -169,14 +180,15 @@ void CmdlineArgs::printUsage() {
defined at --logLevel above.\n\
\n"
#ifdef MIXXX_BUILD_DEBUG
-"\
+ "\
--debugAssertBreak Breaks (SIGINT) Mixxx, if a DEBUG_ASSERT\n\
evaluates to false. Under a debugger you can\n\
continue afterwards.\
\n"
#endif
-"\
--h, --help Display this help message and exit", stdout);
+ "\
+-h, --help Display this help message and exit",
+ stdout);
fputs("\n\n(For more information, see " MIXXX_MANUAL_COMMANDLINEOPTIONS_URL ")\n", stdout);
}
diff --git a/src/util/cmdlineargs.h b/src/util/cmdlineargs.h
index 1aa10034983..f98de23d163 100644
--- a/src/util/cmdlineargs.h
+++ b/src/util/cmdlineargs.h
@@ -28,6 +28,9 @@ class CmdlineArgs final {
bool getMidiDebug() const { return m_midiDebug; }
bool getDeveloper() const { return m_developer; }
bool getSafeMode() const { return m_safeMode; }
+ bool getUseVuMeterGL() const {
+ return m_useVuMeterGL;
+ }
bool getDebugAssertBreak() const { return m_debugAssertBreak; }
bool getSettingsPathSet() const { return m_settingsPathSet; }
mixxx::LogLevel getLogLevel() const { return m_logLevel; }
@@ -47,6 +50,7 @@ class CmdlineArgs final {
bool m_midiDebug;
bool m_developer; // Developer Mode
bool m_safeMode;
+ bool m_useVuMeterGL;
bool m_debugAssertBreak;
bool m_settingsPathSet; // has --settingsPath been set on command line ?
mixxx::LogLevel m_logLevel; // Level of stderr logging message verbosity
diff --git a/src/util/widgethelper.cpp b/src/util/widgethelper.cpp
index d0fda107b17..72254788b71 100644
--- a/src/util/widgethelper.cpp
+++ b/src/util/widgethelper.cpp
@@ -47,6 +47,17 @@ QWindow* getWindow(
return nullptr;
}
+// get the base color of a widget, or recursively search the parent tree for one
+QColor findBaseColor(QWidget* pWidget) {
+ while (pWidget) {
+ if (pWidget->palette().isBrushSet(QPalette::Normal, QPalette::Base)) {
+ return pWidget->palette().color(QPalette::Base);
+ }
+ pWidget = qobject_cast(pWidget->parent());
+ }
+ return QColor(0, 0, 0);
+}
+
} // namespace widgethelper
} // namespace mixxx
diff --git a/src/util/widgethelper.h b/src/util/widgethelper.h
index 7ede58dfb31..81a4009969a 100644
--- a/src/util/widgethelper.h
+++ b/src/util/widgethelper.h
@@ -49,6 +49,12 @@ inline QScreen* getScreen(
#endif
}
+// Get the base color of a widget, or recursively search the parent tree for one.
+//
+// Returns QColor(0,0,0) when none is found. As the recursion can go quite deep,
+// avoid calling repeatedly, but only when needed, e.g in a showEvent.
+QColor findBaseColor(QWidget* pWidget);
+
} // namespace widgethelper
} // namespace mixxx
diff --git a/src/waveform/vsyncthread.cpp b/src/waveform/vsyncthread.cpp
index 2105d9221a2..ebb54e9bf18 100644
--- a/src/waveform/vsyncthread.cpp
+++ b/src/waveform/vsyncthread.cpp
@@ -49,7 +49,7 @@ void VSyncThread::run() {
emit vsyncSwap(); // swaps the new waveform to front
m_semaVsyncSlot.acquire();
- m_timer.restart();
+ m_sinceLastSwap = m_timer.restart();
m_waitToSwapMicros = 1000;
usleep(1000);
} else { // if (m_vSyncMode == ST_TIMER) {
@@ -75,7 +75,8 @@ void VSyncThread::run() {
m_semaVsyncSlot.acquire();
// <- Assume we are VSynced here ->
- int lastSwapTime = static_cast(m_timer.restart().toIntegerMicros());
+ m_sinceLastSwap = m_timer.restart();
+ int lastSwapTime = static_cast(m_sinceLastSwap.toIntegerMicros());
if (remainingForSwap < 0) {
// Our swapping call was already delayed
// The real swap might happens on the following VSync, depending on driver settings
@@ -162,3 +163,7 @@ void VSyncThread::getAvailableVSyncTypes(QList >* pList) {
}
}
}
+
+mixxx::Duration VSyncThread::sinceLastSwap() const {
+ return m_sinceLastSwap;
+}
diff --git a/src/waveform/vsyncthread.h b/src/waveform/vsyncthread.h
index f7310aee56d..4632fe0e79f 100644
--- a/src/waveform/vsyncthread.h
+++ b/src/waveform/vsyncthread.h
@@ -37,7 +37,7 @@ class VSyncThread : public QThread {
void getAvailableVSyncTypes(QList >* list);
void setupSync(QGLWidget* glw, int index);
void waitUntilSwap(QGLWidget* glw);
-
+ mixxx::Duration sinceLastSwap() const;
signals:
void vsyncRender();
void vsyncSwap();
@@ -55,4 +55,5 @@ class VSyncThread : public QThread {
QSemaphore m_semaVsyncSlot;
double m_displayFrameRate;
int m_vSyncPerRendering;
+ mixxx::Duration m_sinceLastSwap;
};
diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp
index 0ae08e33aeb..7d404b09b50 100644
--- a/src/waveform/waveformwidgetfactory.cpp
+++ b/src/waveform/waveformwidgetfactory.cpp
@@ -36,6 +36,7 @@
#include "waveform/widgets/softwarewaveformwidget.h"
#include "waveform/widgets/waveformwidgetabstract.h"
#include "widget/wvumeter.h"
+#include "widget/wvumetergl.h"
#include "widget/wwaveformviewer.h"
namespace {
@@ -353,17 +354,29 @@ void WaveformWidgetFactory::destroyWidgets() {
m_waveformWidgetHolders.clear();
}
-void WaveformWidgetFactory::addTimerListener(WVuMeter* pWidget) {
+void WaveformWidgetFactory::addVuMeter(WVuMeter* pVuMeter) {
// Do not hold the pointer to of timer listeners since they may be deleted.
// We don't activate update() or repaint() directly so listener widgets
// can decide whether to paint or not.
connect(this,
&WaveformWidgetFactory::waveformUpdateTick,
- pWidget,
+ pVuMeter,
&WVuMeter::maybeUpdate,
Qt::DirectConnection);
}
+void WaveformWidgetFactory::addVuMeter(WVuMeterGL* pVuMeter) {
+ // WVuMeterGLs to be rendered and swapped from the vsync thread
+ connect(this,
+ &WaveformWidgetFactory::renderVuMeters,
+ pVuMeter,
+ &WVuMeterGL::render);
+ connect(this,
+ &WaveformWidgetFactory::swapVuMeters,
+ pVuMeter,
+ &WVuMeterGL::swap);
+}
+
void WaveformWidgetFactory::slotSkinLoaded() {
setWidgetTypeFromConfig();
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && defined __WINDOWS__
@@ -673,6 +686,9 @@ void WaveformWidgetFactory::render() {
// WSpinnys are also double-buffered QGLWidgets, like all the waveform
// renderers. Render all the WSpinny widgets now.
emit renderSpinnies(m_vsyncThread);
+ // Same for WVuMeterGL. Note that we are either using WVuMeter or WVuMeterGL.
+ // If we are using WVuMeter, this does nothing
+ emit renderVuMeters(m_vsyncThread);
// Notify all other waveform-like widgets (e.g. WSpinny's) that they should
// update.
@@ -729,6 +745,9 @@ void WaveformWidgetFactory::swap() {
// WSpinnys are also double-buffered QGLWidgets, like all the waveform
// renderers. Swap all the WSpinny widgets now.
emit swapSpinnies();
+ // Same for WVuMeterGL. Note that we are either using WVuMeter or WVuMeterGL
+ // If we are using WVuMeter, this does nothing
+ emit swapVuMeters();
}
//qDebug() << "swap end" << m_vsyncThread->elapsed();
m_vsyncThread->vsyncSlotFinished();
diff --git a/src/waveform/waveformwidgetfactory.h b/src/waveform/waveformwidgetfactory.h
index 483ea4caad8..53961480a02 100644
--- a/src/waveform/waveformwidgetfactory.h
+++ b/src/waveform/waveformwidgetfactory.h
@@ -12,6 +12,7 @@
#include "waveform/widgets/waveformwidgettype.h"
class WVuMeter;
+class WVuMeterGL;
class WWaveformViewer;
class WaveformWidgetAbstract;
class VSyncThread;
@@ -122,7 +123,8 @@ class WaveformWidgetFactory : public QObject, public Singleton >* list);
void destroyWidgets();
- void addTimerListener(WVuMeter* pWidget);
+ void addVuMeter(WVuMeter* pWidget);
+ void addVuMeter(WVuMeterGL* pWidget);
void startVSync(GuiTick* pGuiTick, VisualsManager* pVisualsManager);
void setVSyncType(int vsType);
@@ -140,6 +142,8 @@ class WaveformWidgetFactory : public QObject, public Singleton
-#include
#include
#include
-#include
#include "moc_wvumeter.cpp"
#include "util/math.h"
@@ -51,9 +48,10 @@ void WVuMeter::setup(const QDomNode& node, const SkinContext& context) {
QDomElement vuNode = context.selectElement(node, "PathVu");
// The implicit default in <1.12.0 was FIXED so we keep it for backwards
// compatibility.
- setPixmaps(context.getPixmapSource(vuNode), bHorizontal,
- context.selectScaleMode(vuNode, Paintable::FIXED),
- context.getScaleFactor());
+ setPixmaps(context.getPixmapSource(vuNode),
+ bHorizontal,
+ context.selectScaleMode(vuNode, Paintable::FIXED),
+ context.getScaleFactor());
m_iPeakHoldSize = context.selectInt(node, "PeakHoldSize");
if (m_iPeakHoldSize < 0 || m_iPeakHoldSize > 100) {
@@ -154,7 +152,7 @@ void WVuMeter::maybeUpdate() {
}
}
-void WVuMeter::paintEvent(QPaintEvent * /*unused*/) {
+void WVuMeter::paintEvent(QPaintEvent* /*unused*/) {
ScopedTimer t("WVuMeter::paintEvent");
QStyleOption option;
@@ -176,12 +174,14 @@ void WVuMeter::paintEvent(QPaintEvent * /*unused*/) {
// Draw (part of) vu
if (m_bHorizontal) {
const double widgetPosition = math_clamp(widgetWidth * m_dParameter,
- 0.0, widgetWidth);
+ 0.0,
+ widgetWidth);
QRectF targetRect(0, 0, widgetPosition, widgetHeight);
const double pixmapPosition = math_clamp(pixmapWidth * m_dParameter,
- 0.0, pixmapWidth);
- QRectF sourceRect(0, 0, pixmapPosition, m_pPixmapVu->height());
+ 0.0,
+ pixmapWidth);
+ QRectF sourceRect(0, 0, pixmapPosition, m_pPixmapVu->height());
m_pPixmapVu->draw(targetRect, &p, sourceRect);
if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0) {
@@ -194,22 +194,26 @@ void WVuMeter::paintEvent(QPaintEvent * /*unused*/) {
pixmapWidth * m_dPeakParameter, 0.0, pixmapWidth);
const double pixmapPeakHoldSize = m_iPeakHoldSize;
- targetRect = QRectF(widgetPeakPosition - widgetPeakHoldSize, 0,
- widgetPeakHoldSize, widgetHeight);
- sourceRect = QRectF(pixmapPeakPosition - pixmapPeakHoldSize, 0,
- pixmapPeakHoldSize, pixmapHeight);
+ targetRect = QRectF(widgetPeakPosition - widgetPeakHoldSize,
+ 0,
+ widgetPeakHoldSize,
+ widgetHeight);
+ sourceRect = QRectF(pixmapPeakPosition - pixmapPeakHoldSize,
+ 0,
+ pixmapPeakHoldSize,
+ pixmapHeight);
m_pPixmapVu->draw(targetRect, &p, sourceRect);
}
} else {
const double widgetPosition = math_clamp(widgetHeight * m_dParameter,
- 0.0, widgetHeight);
- QRectF targetRect(0, widgetHeight - widgetPosition,
- widgetWidth, widgetPosition);
+ 0.0,
+ widgetHeight);
+ QRectF targetRect(0, widgetHeight - widgetPosition, widgetWidth, widgetPosition);
const double pixmapPosition = math_clamp(pixmapHeight * m_dParameter,
- 0.0, pixmapHeight);
- QRectF sourceRect(0, pixmapHeight - pixmapPosition,
- pixmapWidth, pixmapPosition);
+ 0.0,
+ pixmapHeight);
+ QRectF sourceRect(0, pixmapHeight - pixmapPosition, pixmapWidth, pixmapPosition);
m_pPixmapVu->draw(targetRect, &p, sourceRect);
if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0) {
@@ -222,10 +226,14 @@ void WVuMeter::paintEvent(QPaintEvent * /*unused*/) {
pixmapHeight * m_dPeakParameter, 0.0, pixmapHeight);
const double pixmapPeakHoldSize = m_iPeakHoldSize;
- targetRect = QRectF(0, widgetHeight - widgetPeakPosition,
- widgetWidth, widgetPeakHoldSize);
- sourceRect = QRectF(0, pixmapHeight - pixmapPeakPosition,
- pixmapWidth, pixmapPeakHoldSize);
+ targetRect = QRectF(0,
+ widgetHeight - widgetPeakPosition,
+ widgetWidth,
+ widgetPeakHoldSize);
+ sourceRect = QRectF(0,
+ pixmapHeight - pixmapPeakPosition,
+ pixmapWidth,
+ pixmapPeakHoldSize);
m_pPixmapVu->draw(targetRect, &p, sourceRect);
}
}
diff --git a/src/widget/wvumeter.h b/src/widget/wvumeter.h
index 988f0c463e3..df16bf482e8 100644
--- a/src/widget/wvumeter.h
+++ b/src/widget/wvumeter.h
@@ -1,20 +1,14 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
-
-#include "widget/wwidget.h"
-#include "widget/wpixmapstore.h"
#include "skin/legacy/skincontext.h"
#include "util/performancetimer.h"
+#include "widget/wpixmapstore.h"
+#include "widget/wwidget.h"
-class WVuMeter : public WWidget {
- Q_OBJECT
+class WVuMeter : public WWidget {
+ Q_OBJECT
public:
- explicit WVuMeter(QWidget *parent=nullptr);
+ explicit WVuMeter(QWidget* parent = nullptr);
void setup(const QDomNode& node, const SkinContext& context);
void setPixmapBackground(
@@ -35,7 +29,7 @@ class WVuMeter : public WWidget {
void updateState(mixxx::Duration elapsed);
private:
- void paintEvent(QPaintEvent * /*unused*/) override;
+ void paintEvent(QPaintEvent* /*unused*/) override;
void setPeak(double parameter);
// Current parameter and peak parameter.
diff --git a/src/widget/wvumetergl.cpp b/src/widget/wvumetergl.cpp
new file mode 100644
index 00000000000..09f2a29ca56
--- /dev/null
+++ b/src/widget/wvumetergl.cpp
@@ -0,0 +1,314 @@
+#include "widget/wvumetergl.h"
+
+#include "moc_wvumetergl.cpp"
+#include "util/math.h"
+#include "util/timer.h"
+#include "util/widgethelper.h"
+#include "waveform/sharedglcontext.h"
+#include "waveform/vsyncthread.h"
+#include "widget/wpixmapstore.h"
+
+#define DEFAULT_FALLTIME 20
+#define DEFAULT_FALLSTEP 1
+#define DEFAULT_HOLDTIME 400
+#define DEFAULT_HOLDSIZE 5
+
+WVuMeterGL::WVuMeterGL(QWidget* parent)
+ : QGLWidget(parent, SharedGLContext::getWidget()),
+ WBaseWidget(this),
+ m_bHasRendered(false),
+ m_bSwapNeeded(false),
+ m_dParameter(0),
+ m_dPeakParameter(0),
+ m_dLastParameter(0),
+ m_dLastPeakParameter(0),
+ m_iPixmapLength(0),
+ m_bHorizontal(false),
+ m_iPeakHoldSize(0),
+ m_iPeakFallStep(0),
+ m_iPeakHoldTime(0),
+ m_iPeakFallTime(0),
+ m_dPeakHoldCountdownMs(0) {
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+
+ setAutoFillBackground(false);
+ setAutoBufferSwap(false);
+
+ // Not interested in repaint or update calls, as we draw from the vsync thread
+ setUpdatesEnabled(false);
+}
+
+void WVuMeterGL::setup(const QDomNode& node, const SkinContext& context) {
+ // Set pixmaps
+ bool bHorizontal = false;
+ (void)context.hasNodeSelectBool(node, "Horizontal", &bHorizontal);
+
+ // Set background pixmap if available
+ QDomElement backPathNode = context.selectElement(node, "PathBack");
+ if (!backPathNode.isNull()) {
+ // The implicit default in <1.12.0 was FIXED so we keep it for backwards
+ // compatibility.
+ setPixmapBackground(
+ context.getPixmapSource(backPathNode),
+ context.selectScaleMode(backPathNode, Paintable::FIXED),
+ context.getScaleFactor());
+ }
+
+ QDomElement vuNode = context.selectElement(node, "PathVu");
+ // The implicit default in <1.12.0 was FIXED so we keep it for backwards
+ // compatibility.
+ setPixmaps(context.getPixmapSource(vuNode),
+ bHorizontal,
+ context.selectScaleMode(vuNode, Paintable::FIXED),
+ context.getScaleFactor());
+
+ m_iPeakHoldSize = context.selectInt(node, "PeakHoldSize");
+ if (m_iPeakHoldSize < 0 || m_iPeakHoldSize > 100) {
+ m_iPeakHoldSize = DEFAULT_HOLDSIZE;
+ }
+
+ m_iPeakFallStep = context.selectInt(node, "PeakFallStep");
+ if (m_iPeakFallStep < 1 || m_iPeakFallStep > 1000) {
+ m_iPeakFallStep = DEFAULT_FALLSTEP;
+ }
+
+ m_iPeakHoldTime = context.selectInt(node, "PeakHoldTime");
+ if (m_iPeakHoldTime < 1 || m_iPeakHoldTime > 3000) {
+ m_iPeakHoldTime = DEFAULT_HOLDTIME;
+ }
+
+ m_iPeakFallTime = context.selectInt(node, "PeakFallTime");
+ if (m_iPeakFallTime < 1 || m_iPeakFallTime > 1000) {
+ m_iPeakFallTime = DEFAULT_FALLTIME;
+ }
+
+ setFocusPolicy(Qt::NoFocus);
+}
+
+void WVuMeterGL::setPixmapBackground(
+ const PixmapSource& source,
+ Paintable::DrawMode mode,
+ double scaleFactor) {
+ m_pPixmapBack = WPixmapStore::getPaintable(source, mode, scaleFactor);
+ if (m_pPixmapBack.isNull()) {
+ qDebug() << metaObject()->className()
+ << "Error loading background pixmap:" << source.getPath();
+ } else if (mode == Paintable::FIXED) {
+ setFixedSize(m_pPixmapBack->size());
+ }
+}
+
+void WVuMeterGL::setPixmaps(const PixmapSource& source,
+ bool bHorizontal,
+ Paintable::DrawMode mode,
+ double scaleFactor) {
+ m_pPixmapVu = WPixmapStore::getPaintable(source, mode, scaleFactor);
+ if (m_pPixmapVu.isNull()) {
+ qDebug() << "WVuMeterGL: Error loading vu pixmap" << source.getPath();
+ } else {
+ m_bHorizontal = bHorizontal;
+ if (m_bHorizontal) {
+ m_iPixmapLength = m_pPixmapVu->width();
+ } else {
+ m_iPixmapLength = m_pPixmapVu->height();
+ }
+ }
+}
+
+void WVuMeterGL::onConnectedControlChanged(double dParameter, double dValue) {
+ Q_UNUSED(dValue);
+ m_dParameter = math_clamp(dParameter, 0.0, 1.0);
+
+ if (dParameter > 0.0) {
+ setPeak(dParameter);
+ } else {
+ // A 0.0 value is very unlikely except when the VU Meter is disabled
+ m_dPeakParameter = 0;
+ }
+}
+
+void WVuMeterGL::setPeak(double parameter) {
+ if (parameter > m_dPeakParameter) {
+ m_dPeakParameter = parameter;
+ m_dPeakHoldCountdownMs = m_iPeakHoldTime;
+ }
+}
+
+void WVuMeterGL::updateState(mixxx::Duration elapsed) {
+ double msecsElapsed = elapsed.toDoubleMillis();
+ // If we're holding at a peak then don't update anything
+ m_dPeakHoldCountdownMs -= msecsElapsed;
+ if (m_dPeakHoldCountdownMs > 0) {
+ return;
+ } else {
+ m_dPeakHoldCountdownMs = 0;
+ }
+
+ // Otherwise, decrement the peak position by the fall step size times the
+ // milliseconds elapsed over the fall time multiplier. The peak will fall
+ // FallStep times (out of 128 steps) every FallTime milliseconds.
+ m_dPeakParameter -= static_cast(m_iPeakFallStep) *
+ msecsElapsed /
+ static_cast(m_iPeakFallTime * m_iPixmapLength);
+ m_dPeakParameter = math_clamp(m_dPeakParameter, 0.0, 1.0);
+}
+
+void WVuMeterGL::paintEvent(QPaintEvent* e) {
+ Q_UNUSED(e);
+ // Force a rerender when render is called from the vsync thread, e.g. to
+ // git rid artifacts after hiding and showing the mixer or incomplete
+ // initial drawing.
+ m_bHasRendered = false;
+}
+
+void WVuMeterGL::showEvent(QShowEvent* e) {
+ Q_UNUSED(e);
+ // Find the base color recursively in parent widget.
+ m_qBgColor = mixxx::widgethelper::findBaseColor(this);
+}
+
+void WVuMeterGL::render(VSyncThread* vSyncThread) {
+ ScopedTimer t("WVuMeterGL::render");
+
+ updateState(vSyncThread->sinceLastSwap());
+
+ if (m_bHasRendered && m_dParameter == m_dLastParameter &&
+ m_dPeakParameter == m_dLastPeakParameter) {
+ return;
+ }
+
+ if (!isValid() || !isVisible()) {
+ return;
+ }
+
+ auto* window = windowHandle();
+ if (window == nullptr || !window->isExposed()) {
+ return;
+ }
+
+ QPainter p(this);
+ // fill the background, in case the image contains transparency
+ p.fillRect(rect(), m_qBgColor);
+
+ if (!m_pPixmapBack.isNull()) {
+ // Draw background.
+ QRectF sourceRect(0, 0, m_pPixmapBack->width(), m_pPixmapBack->height());
+ m_pPixmapBack->draw(rect(), &p, sourceRect);
+ }
+
+ const double widgetWidth = width();
+ const double widgetHeight = height();
+ const double pixmapWidth = m_pPixmapVu.isNull() ? 0 : m_pPixmapVu->width();
+ const double pixmapHeight = m_pPixmapVu.isNull() ? 0 : m_pPixmapVu->height();
+
+ // Draw (part of) vu
+ if (m_bHorizontal) {
+ {
+ const double widgetPosition = math_clamp(widgetWidth * m_dParameter, 0.0, widgetWidth);
+ QRectF targetRect(0, 0, widgetPosition, widgetHeight);
+
+ if (!m_pPixmapVu.isNull()) {
+ const double pixmapPosition = math_clamp(
+ pixmapWidth * m_dParameter, 0.0, pixmapWidth);
+ QRectF sourceRect(0, 0, pixmapPosition, pixmapHeight);
+ m_pPixmapVu->draw(targetRect, &p, sourceRect);
+ } else {
+ // fallback to green rectangle
+ p.fillRect(targetRect, QColor(0, 255, 0));
+ }
+ }
+
+ if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0 &&
+ m_dPeakParameter > m_dParameter) {
+ const double widgetPeakPosition = math_clamp(
+ widgetWidth * m_dPeakParameter, 0.0, widgetWidth);
+ const double pixmapPeakHoldSize = static_cast(m_iPeakHoldSize);
+ const double widgetPeakHoldSize = widgetWidth * pixmapPeakHoldSize / pixmapHeight;
+
+ QRectF targetRect(widgetPeakPosition - widgetPeakHoldSize,
+ 0,
+ widgetPeakHoldSize,
+ widgetHeight);
+
+ if (!m_pPixmapVu.isNull()) {
+ const double pixmapPeakPosition = math_clamp(
+ pixmapWidth * m_dPeakParameter, 0.0, pixmapWidth);
+
+ QRectF sourceRect =
+ QRectF(pixmapPeakPosition - pixmapPeakHoldSize,
+ 0,
+ pixmapPeakHoldSize,
+ pixmapHeight);
+ m_pPixmapVu->draw(targetRect, &p, sourceRect);
+ } else {
+ // fallback to green rectangle
+ p.fillRect(targetRect, QColor(0, 255, 0));
+ }
+ }
+ } else {
+ // vertical
+ {
+ const double widgetPosition =
+ math_clamp(widgetHeight * m_dParameter, 0.0, widgetHeight);
+ QRectF targetRect(0, widgetHeight - widgetPosition, widgetWidth, widgetPosition);
+
+ if (!m_pPixmapVu.isNull()) {
+ const double pixmapPosition = math_clamp(
+ pixmapHeight * m_dParameter, 0.0, pixmapHeight);
+ QRectF sourceRect(0, pixmapHeight - pixmapPosition, pixmapWidth, pixmapPosition);
+ m_pPixmapVu->draw(targetRect, &p, sourceRect);
+ } else {
+ // fallback to green rectangle
+ p.fillRect(targetRect, QColor(0, 255, 0));
+ }
+ }
+
+ if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0 &&
+ m_dPeakParameter > m_dParameter) {
+ const double widgetPeakPosition = math_clamp(
+ widgetHeight * m_dPeakParameter, 0.0, widgetHeight);
+ const double pixmapPeakHoldSize = static_cast(m_iPeakHoldSize);
+ const double widgetPeakHoldSize = widgetHeight * pixmapPeakHoldSize / pixmapHeight;
+
+ QRectF targetRect(0,
+ widgetHeight - widgetPeakPosition,
+ widgetWidth,
+ widgetPeakHoldSize);
+
+ if (!m_pPixmapVu.isNull()) {
+ const double pixmapPeakPosition = math_clamp(
+ pixmapHeight * m_dPeakParameter, 0.0, pixmapHeight);
+
+ QRectF sourceRect = QRectF(0,
+ pixmapHeight - pixmapPeakPosition,
+ pixmapWidth,
+ pixmapPeakHoldSize);
+ m_pPixmapVu->draw(targetRect, &p, sourceRect);
+ } else {
+ // fallback to green rectangle
+ p.fillRect(targetRect, QColor(0, 255, 0));
+ }
+ }
+ }
+
+ m_dLastParameter = m_dParameter;
+ m_dLastPeakParameter = m_dPeakParameter;
+ m_bHasRendered = true;
+ m_bSwapNeeded = true;
+}
+
+void WVuMeterGL::swap() {
+ if (!isValid() || !isVisible() || !m_bSwapNeeded) {
+ return;
+ }
+ auto* window = windowHandle();
+ if (window == nullptr || !window->isExposed()) {
+ return;
+ }
+ if (context() != QGLContext::currentContext()) {
+ makeCurrent();
+ }
+ swapBuffers();
+ m_bSwapNeeded = false;
+}
diff --git a/src/widget/wvumetergl.h b/src/widget/wvumetergl.h
new file mode 100644
index 00000000000..43437489d80
--- /dev/null
+++ b/src/widget/wvumetergl.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include
+
+#include "skin/legacy/skincontext.h"
+#include "util/duration.h"
+#include "widget/wpixmapstore.h"
+#include "widget/wwidget.h"
+
+class VSyncThread;
+
+class WVuMeterGL : public QGLWidget, public WBaseWidget {
+ Q_OBJECT
+ public:
+ explicit WVuMeterGL(QWidget* parent = nullptr);
+
+ void setup(const QDomNode& node, const SkinContext& context);
+ void setPixmapBackground(
+ const PixmapSource& source,
+ Paintable::DrawMode mode,
+ double scaleFactor);
+ void setPixmaps(
+ const PixmapSource& source,
+ bool bHorizontal,
+ Paintable::DrawMode mode,
+ double scaleFactor);
+ void onConnectedControlChanged(double dParameter, double dValue) override;
+
+ public slots:
+ void render(VSyncThread* vSyncThread);
+ void swap();
+
+ protected slots:
+ void updateState(mixxx::Duration elapsed);
+
+ private:
+ void paintEvent(QPaintEvent* /*unused*/) override;
+ void showEvent(QShowEvent* /*unused*/) override;
+ void setPeak(double parameter);
+
+ // To make sure we render at least once even when we have no signal
+ bool m_bHasRendered;
+ // To indicate that we rendered so we need to swap
+ bool m_bSwapNeeded;
+ // Current parameter and peak parameter.
+ double m_dParameter;
+ double m_dPeakParameter;
+
+ // The last parameter and peak parameter values at the time of
+ // rendering. Used to check whether the widget state has changed since the
+ // last render in maybeUpdate.
+ double m_dLastParameter;
+ double m_dLastPeakParameter;
+
+ // Length of the VU-meter pixmap along the relevant axis.
+ int m_iPixmapLength;
+
+ // Associated pixmaps
+ PaintablePointer m_pPixmapBack;
+ PaintablePointer m_pPixmapVu;
+
+ // True if it's a horizontal vu meter
+ bool m_bHorizontal;
+
+ int m_iPeakHoldSize;
+ int m_iPeakFallStep;
+ int m_iPeakHoldTime;
+ int m_iPeakFallTime;
+
+ // The peak hold time remaining in milliseconds.
+ double m_dPeakHoldCountdownMs;
+
+ QColor m_qBgColor;
+};