From fb057a5cda57dc96a30e678a386f3a779987205a Mon Sep 17 00:00:00 2001 From: Antoine C Date: Tue, 22 Oct 2024 23:45:01 +0100 Subject: [PATCH] feat: add scrolling waveform in QML using scenegraph --- CMakeLists.txt | 41 +- res/controllers/DummyDeviceDefaultScreen.qml | 163 ++++- res/qml/Deck.qml | 48 ++ res/qml/IntroOutroButton.qml | 45 ++ res/qml/WaveformDisplay.qml | 247 ++++++++ res/qml/main.qml | 8 +- src/analyzer/analyzerwaveform.cpp | 61 +- src/analyzer/analyzerwaveform.h | 27 +- src/main.cpp | 30 +- src/mixxxmainwindow.cpp | 4 +- src/preferences/dialog/dlgprefwaveform.cpp | 16 +- src/qml/qmlapplication.cpp | 49 +- src/qml/qmlapplication.h | 10 +- src/qml/qmldlgpreferencesproxy.cpp | 4 +- src/qml/qmldlgpreferencesproxy.h | 2 +- src/qml/qmlwaveformdisplay.cpp | 258 ++++++++ src/qml/qmlwaveformdisplay.h | 125 ++++ src/qml/qmlwaveformrenderer.cpp | 186 ++++++ src/qml/qmlwaveformrenderer.h | 582 ++++++++++++++++++ src/rendergraph/CMakeLists.txt | 5 +- src/rendergraph/opengl/texture.cpp | 3 - src/soundio/soundmanager.cpp | 2 +- src/waveform/ivisualgainprovider.h | 8 + .../renderers/allshader/waveformrenderbeat.h | 4 + .../allshader/waveformrendererendoftrack.cpp | 103 ++-- .../allshader/waveformrendererendoftrack.h | 30 +- .../allshader/waveformrendererfiltered.cpp | 6 +- .../allshader/waveformrendererfiltered.h | 6 +- .../allshader/waveformrendererhsv.cpp | 5 +- .../renderers/allshader/waveformrendererhsv.h | 5 +- .../allshader/waveformrendererpreroll.h | 4 + .../allshader/waveformrendererrgb.cpp | 17 +- .../renderers/allshader/waveformrendererrgb.h | 10 +- .../allshader/waveformrenderersignalbase.cpp | 4 +- .../allshader/waveformrenderersignalbase.h | 3 +- .../allshader/waveformrenderersimple.cpp | 6 +- .../allshader/waveformrenderersimple.h | 5 +- .../allshader/waveformrendererstem.cpp | 5 +- .../allshader/waveformrendererstem.h | 3 +- .../allshader/waveformrenderertextured.cpp | 5 +- .../allshader/waveformrenderertextured.h | 3 +- .../allshader/waveformrendermark.cpp | 57 +- .../renderers/allshader/waveformrendermark.h | 21 + src/waveform/renderers/waveformmark.cpp | 69 +++ src/waveform/renderers/waveformmark.h | 14 + src/waveform/renderers/waveformmarkrange.cpp | 60 ++ src/waveform/renderers/waveformmarkrange.h | 12 + src/waveform/renderers/waveformmarkset.cpp | 65 +- src/waveform/renderers/waveformmarkset.h | 22 +- .../renderers/waveformrendererabstract.h | 4 + .../renderers/waveformrenderersignalbase.cpp | 23 +- .../renderers/waveformrenderersignalbase.h | 23 +- .../renderers/waveformrendermarkbase.h | 12 + .../renderers/waveformwidgetrenderer.cpp | 26 +- .../renderers/waveformwidgetrenderer.h | 22 +- src/waveform/waveform.h | 6 +- src/waveform/waveformwidgetfactory.cpp | 35 +- src/waveform/waveformwidgetfactory.h | 10 +- .../widgets/waveformwidgetabstract.cpp | 1 + src/widget/woverview.cpp | 2 +- src/widget/wspinnybase.cpp | 1 + src/widget/wwaveformviewer.cpp | 4 +- 62 files changed, 2374 insertions(+), 263 deletions(-) create mode 100644 res/qml/IntroOutroButton.qml create mode 100644 res/qml/WaveformDisplay.qml create mode 100644 src/qml/qmlwaveformdisplay.cpp create mode 100644 src/qml/qmlwaveformdisplay.h create mode 100644 src/qml/qmlwaveformrenderer.cpp create mode 100644 src/qml/qmlwaveformrenderer.h create mode 100644 src/waveform/ivisualgainprovider.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a9a01b25497..9dbab3151d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1762,6 +1762,7 @@ else() ) endif() if(QOPENGL) + target_compile_definitions(mixxx-lib PRIVATE __RENDERGRAPH_IS_OPENGL) target_sources( mixxx-lib PRIVATE @@ -2105,7 +2106,7 @@ if(QT6) # below that takes care of the correct object order in the resulting binary # According to https://doc.qt.io/qt-6/qt-finalize-target.html it is importand for # builds with Qt < 3.21 - qt_add_executable(mixxx WIN32 src/main.cpp MANUAL_FINALIZATION) + qt_add_executable(mixxx WIN32 MACOSX_BUNDLE src/main.cpp MANUAL_FINALIZATION) else() find_package(Qt5 COMPONENTS Core) # For Qt Core cmake functions # This is the first package form the environment, if this fails give hints how to install the environment @@ -3318,9 +3319,11 @@ if(QML) res/qml/Mixxx/Controls/WaveformOverviewHotcueMarker.qml res/qml/Mixxx/Controls/WaveformOverviewMarker.qml res/qml/Mixxx/Controls/WaveformOverview.qml + res/qml/Mixxx/Controls/WaveformDisplay.qml ) target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-mixxxcontrolsplugin) + target_compile_definitions(mixxx-qml-lib PRIVATE __RENDERGRAPH_IS_SCENEGRAPH) target_sources( mixxx-qml-lib PRIVATE @@ -3343,6 +3346,21 @@ if(QML) src/qml/qmlchainpresetmodel.cpp src/qml/qmlwaveformoverview.cpp src/qml/qmlmixxxcontrollerscreen.cpp + src/qml/qmlwaveformdisplay.cpp + src/qml/qmlwaveformrenderer.cpp + src/waveform/renderers/allshader/digitsrenderer.cpp + src/waveform/renderers/allshader/waveformrenderbeat.cpp + src/waveform/renderers/allshader/waveformrenderer.cpp + src/waveform/renderers/allshader/waveformrendererendoftrack.cpp + src/waveform/renderers/allshader/waveformrendererpreroll.cpp + src/waveform/renderers/allshader/waveformrendererrgb.cpp + src/waveform/renderers/allshader/waveformrenderersignalbase.cpp + src/waveform/renderers/allshader/waveformrendermark.cpp + src/waveform/renderers/allshader/waveformrendermarkrange.cpp + src/waveform/renderers/allshader/waveformrendererslipmode.cpp + src/waveform/renderers/allshader/waveformrendererfiltered.cpp + src/waveform/renderers/allshader/waveformrendererhsv.cpp + src/waveform/renderers/allshader/waveformrenderersimple.cpp # The following sources need to be in this target to get QML_ELEMENT properly interpreted src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp @@ -4296,7 +4314,12 @@ if(STEM) endif() if(QML) target_compile_definitions(mixxx-qml-lib PUBLIC __STEM__) - target_sources(mixxx-qml-lib PRIVATE src/qml/qmlstemsmodel.cpp) + target_sources( + mixxx-qml-lib + PRIVATE + src/waveform/renderers/allshader/waveformrendererstem.cpp + src/qml/qmlstemsmodel.cpp + ) endif() endif() @@ -4651,8 +4674,22 @@ endif() # rendergraph add_subdirectory(src/rendergraph) +target_compile_definitions( + rendergraph_gl + PUBLIC $<$:MIXXX_DEBUG_ASSERTIONS_ENABLED> +) target_link_libraries(mixxx-lib PUBLIC rendergraph_gl) target_compile_definitions(mixxx-lib PUBLIC rendergraph=rendergraph_gl) +target_compile_definitions(mixxx-lib PRIVATE allshader=allshader_gl) +if(QML) + target_compile_definitions( + rendergraph_sg + PUBLIC $<$:MIXXX_DEBUG_ASSERTIONS_ENABLED> + ) + target_link_libraries(mixxx-qml-lib PRIVATE rendergraph_sg) + target_compile_definitions(mixxx-qml-lib PRIVATE rendergraph=rendergraph_sg) + target_compile_definitions(mixxx-qml-lib PRIVATE allshader=allshader_sg) +endif() # WavPack audio file support find_package(wavpack) diff --git a/res/controllers/DummyDeviceDefaultScreen.qml b/res/controllers/DummyDeviceDefaultScreen.qml index 06179054927..eadcac13e2c 100755 --- a/res/controllers/DummyDeviceDefaultScreen.qml +++ b/res/controllers/DummyDeviceDefaultScreen.qml @@ -170,7 +170,7 @@ Mixxx.ControllerScreen { Layout.fillHeight: true Text { text: qsTr("Group") - font.pixelSize: 24 + font.pixelSize: 18 font.family: "Noto Sans" font.letterSpacing: -1 color: fontColor @@ -183,7 +183,7 @@ Mixxx.ControllerScreen { Layout.fillHeight: true Text { text: `${root.group}` - font.pixelSize: 24 + font.pixelSize: 18 font.family: "Noto Sans" font.letterSpacing: -1 color: fontColor @@ -207,7 +207,7 @@ Mixxx.ControllerScreen { Layout.fillHeight: true Text { text: qsTr("Widget") - font.pixelSize: 24 + font.pixelSize: 18 font.family: "Noto Sans" font.letterSpacing: -1 color: fontColor @@ -258,7 +258,7 @@ Mixxx.ControllerScreen { anchors.bottomMargin: 6 Layout.fillWidth: true Layout.fillHeight: true - spacing: 6 + spacing: 4 required property var modelData Mixxx.ControlProxy { @@ -273,7 +273,7 @@ Mixxx.ControllerScreen { Layout.fillHeight: true Text { text: qsTr(modelData.title) - font.pixelSize: 24 + font.pixelSize: 18 font.family: "Noto Sans" font.letterSpacing: -1 color: fontColor @@ -286,7 +286,7 @@ Mixxx.ControllerScreen { Layout.fillHeight: true Text { text: `${mixxxValue.value}` - font.pixelSize: 24 + font.pixelSize: 18 font.family: "Noto Sans" font.letterSpacing: -1 color: fontColor @@ -294,6 +294,157 @@ Mixxx.ControllerScreen { } } } + RowLayout { + anchors.leftMargin: 6 + anchors.rightMargin: 6 + anchors.topMargin: 6 + anchors.bottomMargin: 6 + + Layout.fillWidth: true + Layout.fillHeight: true + + Mixxx.ControlProxy { + id: zoomControl + + group: root.group + key: "waveform_zoom" + } + + MixxxControls.WaveformDisplay { + group: root.group + x: 0 + width: root.width + height: 100 + + zoom: zoomControl.value + backgroundColor: "#36000000" + + Mixxx.WaveformRendererEndOfTrack { + color: 'blue' + endOfTrackWarningTime: 30 + } + + Mixxx.WaveformRendererPreroll { + color: '#998977' + } + + Mixxx.WaveformRendererMarkRange { + // + Mixxx.WaveformMarkRange { + startControl: "loop_start_position" + endControl: "loop_end_position" + enabledControl: "loop_enabled" + color: '#00b400' + opacity: 0.7 + disabledColor: '#FFFFFF' + disabledOpacity: 0.6 + } + // + Mixxx.WaveformMarkRange { + startControl: "intro_start_position" + endControl: "intro_end_position" + color: '#2c5c9a' + opacity: 0.6 + durationTextColor: '#ffffff' + durationTextLocation: 'after' + } + // + Mixxx.WaveformMarkRange { + startControl: "outro_start_position" + endControl: "outro_end_position" + color: '#2c5c9a' + opacity: 0.6 + durationTextColor: '#ffffff' + durationTextLocation: 'before' + } + } + + Mixxx.WaveformRendererRGB { + axesColor: '#00ffffff' + lowColor: 'red' + midColor: 'green' + highColor: 'blue' + + gainAll: 1.0 + gainLow: 1.0 + gainMid: 1.0 + gainHigh: 1.0 + } + + Mixxx.WaveformRendererStem { + gainAll: 1.0 + } + + Mixxx.WaveformRendererBeat { + color: '#cfcfcf' + } + + Mixxx.WaveformRendererMark { + playMarkerColor: 'cyan' + playMarkerBackground: 'transparent' + defaultMark: Mixxx.WaveformMark { + align: "bottom|center" + color: "#FF0000" + textColor: "#FFFFFF" + text: " %1 " + } + + untilMark.showTime: false + untilMark.showBeats: false + untilMark.align: Mixxx.WaveformUntilMark.AlignCenter + untilMark.textSize: 14 + + Mixxx.WaveformMark { + control: "cue_point" + text: 'CUE' + align: 'top|right' + color: '#FF8000' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "loop_start_position" + text: '↻' + align: 'top|left' + color: 'green' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "loop_end_position" + align: 'bottom|right' + color: 'green' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "intro_start_position" + text: '◢' + align: 'top|right' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "intro_end_position" + text: '◢' + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "outro_start_position" + text: '◣' + align: 'top|right' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "outro_end_position" + text: '◣' + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + } + } + } } } } diff --git a/res/qml/Deck.qml b/res/qml/Deck.qml index 46ff28bd465..68b575973b7 100644 --- a/res/qml/Deck.qml +++ b/res/qml/Deck.qml @@ -326,6 +326,54 @@ Item { activeColor: Theme.deckActiveColor } + Row { + anchors.left: playButton.right + anchors.leftMargin: 10 + anchors.bottom: playButton.bottom + anchors.topMargin: 5 + spacing: -1 + + Skin.IntroOutroButton { + keyPrefix: "intro_start" + group: root.group + + text: "Intro\nStart" + + width: playButton.height * 2 - 1 + height: playButton.height + } + + Skin.IntroOutroButton { + keyPrefix: "intro_end" + group: root.group + + text: "Intro\nEnd" + + width: playButton.height * 2 - 1 + height: playButton.height + } + + Skin.IntroOutroButton { + keyPrefix: "outro_start" + group: root.group + + text: "Outro\nStart" + + width: playButton.height * 2 - 1 + height: playButton.height + } + + Skin.IntroOutroButton { + keyPrefix: "outro_end" + group: root.group + + text: "Outro\nEnd" + + width: playButton.height * 2 - 1 + height: playButton.height + } + } + Row { anchors.left: cueButton.right anchors.top: parent.top diff --git a/res/qml/IntroOutroButton.qml b/res/qml/IntroOutroButton.qml new file mode 100644 index 00000000000..9d585d3d25b --- /dev/null +++ b/res/qml/IntroOutroButton.qml @@ -0,0 +1,45 @@ +import "." as Skin +import Mixxx 1.0 as Mixxx +import QtQuick 2.12 + +import "Theme" + +Skin.Button { + id: root + + required property string keyPrefix + required property string group + + activeColor: Theme.deckActiveColor + highlight: enabledControl.value + + Mixxx.ControlProxy { + id: control + + group: root.group + key: `${root.keyPrefix}_activate` + value: root.down + } + + Mixxx.ControlProxy { + id: enabledControl + + group: root.group + key: `${root.keyPrefix}_enabled` + } + + Mixxx.ControlProxy { + id: cleanControl + + group: root.group + key: `${root.keyPrefix}_clear` + value: mousearea.pressed && enabledControl.value + } + + MouseArea { + id: mousearea + + anchors.fill: parent + acceptedButtons: Qt.RightButton + } +} diff --git a/res/qml/WaveformDisplay.qml b/res/qml/WaveformDisplay.qml new file mode 100644 index 00000000000..ca5129304b8 --- /dev/null +++ b/res/qml/WaveformDisplay.qml @@ -0,0 +1,247 @@ +import "." as Skin +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls +import QtQuick 2.12 +import "Theme" + +Item { + id: root + + required property string group + + enum MouseStatus { + Normal, + Bending, + Scratching + } + + MixxxControls.WaveformDisplay { + anchors.fill: parent + group: root.group + zoom: zoomControl.value + backgroundColor: "#5e000000" + + Mixxx.WaveformRendererEndOfTrack { + color: '#ff8872' + endOfTrackWarningTime: 30 + } + + Mixxx.WaveformRendererPreroll { + color: '#ff8872' + } + + Mixxx.WaveformRendererMarkRange { + // + Mixxx.WaveformMarkRange { + startControl: "loop_start_position" + endControl: "loop_end_position" + enabledControl: "loop_enabled" + color: '#00b400' + opacity: 0.7 + disabledColor: '#FFFFFF' + disabledOpacity: 0.6 + } + // + Mixxx.WaveformMarkRange { + startControl: "intro_start_position" + endControl: "intro_end_position" + color: '#2c5c9a' + opacity: 0.6 + durationTextColor: '#ffffff' + durationTextLocation: 'after' + } + // + Mixxx.WaveformMarkRange { + startControl: "outro_start_position" + endControl: "outro_end_position" + color: '#2c5c9a' + opacity: 0.6 + durationTextColor: '#ffffff' + durationTextLocation: 'before' + } + } + + Mixxx.WaveformRendererRGB { + axesColor: '#a1a1a1a1' + lowColor: '#ff2154d7' + midColor: '#cfb26606' + highColor: '#e5029c5c' + + gainAll: 1.0 + gainLow: 1.0 + gainMid: 1.0 + gainHigh: 1.0 + } + + Mixxx.WaveformRendererStem { + gainAll: 1.0 + } + + Mixxx.WaveformRendererBeat { + color: '#a1a1a1a1' + } + + Mixxx.WaveformRendererMark { + playMarkerColor: 'cyan' + playMarkerBackground: 'orange' + defaultMark: Mixxx.WaveformMark { + align: "bottom|right" + color: "#00d9ff" + textColor: "#1a1a1a" + text: " %1 " + } + + untilMark.showTime: true + untilMark.showBeats: true + untilMark.align: Mixxx.WaveformUntilMark.AlignBottom + untilMark.textSize: 11 + + Mixxx.WaveformMark { + control: "cue_point" + text: 'CUE' + align: 'top|right' + color: 'red' + textColor: '#1a1a1a' + } + Mixxx.WaveformMark { + control: "loop_start_position" + text: '↻' + align: 'top|left' + color: 'green' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "loop_end_position" + align: 'bottom|right' + color: 'green' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "intro_start_position" + text: '◢' + align: 'top|right' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "intro_end_position" + text: '◢' + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "outro_start_position" + text: '◣' + align: 'top|right' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "outro_end_position" + text: '◣' + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + } + } + + Mixxx.ControlProxy { + id: scratchPositionEnableControl + + group: root.group + key: "scratch_position_enable" + } + + Mixxx.ControlProxy { + id: scratchPositionControl + + group: root.group + key: "scratch_position" + } + + Mixxx.ControlProxy { + id: wheelControl + + group: root.group + key: "wheel" + } + + Mixxx.ControlProxy { + id: rateRatioControl + + group: root.group + key: "rate_ratio" + } + + Mixxx.ControlProxy { + id: zoomControl + + group: root.group + key: "waveform_zoom" + } + + MouseArea { + property int mouseStatus: WaveformDisplay.MouseStatus.Normal + property point mouseAnchor: Qt.point(0, 0) + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onPressed: { + mouseAnchor = Qt.point(mouse.x, mouse.y); + if (mouse.button == Qt.LeftButton) { + if (mouseStatus == WaveformDisplay.MouseStatus.Bending) + wheelControl.parameter = 0.5; + + mouseStatus = WaveformDisplay.MouseStatus.Scratching; + scratchPositionEnableControl.value = 1; + // TODO: Calculate position properly + scratchPositionControl.value = -mouse.x * zoomControl.value * 50; + } else { + if (mouseStatus == WaveformDisplay.MouseStatus.Scratching) + scratchPositionEnableControl.value = 0; + + wheelControl.parameter = 0.5; + mouseStatus = WaveformDisplay.MouseStatus.Bending; + } + } + onPositionChanged: { + switch (mouseStatus) { + case WaveformDisplay.MouseStatus.Bending: { + const diff = mouse.x - mouseAnchor.x; + // Start at the middle of [0.0, 1.0], and emit values based on how far + // the mouse has traveled horizontally. Note, for legacy (MIDI) reasons, + // this is tuned to 127. + const v = 0.5 + (diff / root.width); + // clamp to [0.0, 1.0] + wheelControl.parameter = Math.max(Math.min(v, 1), 0); + break; + }; + case WaveformDisplay.MouseStatus.Scratching: + // TODO: Calculate position properly + scratchPositionControl.value = -mouse.x * zoomControl.value * 50; + break; + } + } + onReleased: { + switch (mouseStatus) { + case WaveformDisplay.MouseStatus.Bending: + wheelControl.parameter = 0.5; + break; + case WaveformDisplay.MouseStatus.Scratching: + scratchPositionEnableControl.value = 0; + break; + } + mouseStatus = WaveformDisplay.MouseStatus.Normal; + } + + onWheel: { + if (wheel.angleDelta.y < 0 && zoomControl.value > 1) { + zoomControl.value -= 1; + } else if (wheel.angleDelta.y > 0 && zoomControl.value < 10.0) { + zoomControl.value += 1; + } + } + } +} diff --git a/res/qml/main.qml b/res/qml/main.qml index 15578d303cb..5270fa054fa 100644 --- a/res/qml/main.qml +++ b/res/qml/main.qml @@ -98,7 +98,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck3waveform group: "[Channel3]" @@ -111,7 +111,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck1waveform group: "[Channel1]" @@ -124,7 +124,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck2waveform group: "[Channel2]" @@ -137,7 +137,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck4waveform group: "[Channel4]" diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index f55e18a186b..836f2cccdf0 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -1,10 +1,14 @@ #include "analyzer/analyzerwaveform.h" +#include +#include + #include "analyzer/analyzertrack.h" #include "analyzer/constants.h" #include "engine/filters/enginefilterbessel4.h" #include "track/track.h" #include "util/logger.h" +#include "waveform/waveform.h" #include "waveform/waveformfactory.h" namespace { @@ -26,9 +30,6 @@ AnalyzerWaveform::AnalyzerWaveform( m_stride(0, 0, 0), m_currentStride(0), m_currentSummaryStride(0) { - m_filter[0] = nullptr; - m_filter[1] = nullptr; - m_filter[2] = nullptr; m_analysisDao.initialize(dbConnection); } @@ -176,22 +177,19 @@ void AnalyzerWaveform::createFilters(mixxx::audio::SampleRate sampleRate) { // m_filter[Low] = new EngineFilterButterworth8Low(sampleRate, kLowMidFreqHz); // m_filter[Mid] = new EngineFilterButterworth8Band(sampleRate, kLowMidFreqHz, kMidHighFreqHz); // m_filter[High] = new EngineFilterButterworth8High(sampleRate, kMidHighFreqHz); - m_filter[Low] = new EngineFilterBessel4Low(sampleRate, kLowMidFreqHz); - m_filter[Mid] = new EngineFilterBessel4Band(sampleRate, kLowMidFreqHz, kMidHighFreqHz); - m_filter[High] = new EngineFilterBessel4High(sampleRate, kMidHighFreqHz); + m_filters = { + std::make_unique(sampleRate, kLowMidFreqHz), + std::make_unique(sampleRate, kLowMidFreqHz, kMidHighFreqHz), + std::make_unique(sampleRate, kMidHighFreqHz)}; + // settle filters for silence in preroll to avoids ramping (Issue #7776) - for (int i = 0; i < FilterCount; ++i) { - m_filter[i]->assumeSettled(); - } + m_filters.low->assumeSettled(); + m_filters.mid->assumeSettled(); + m_filters.high->assumeSettled(); } void AnalyzerWaveform::destroyFilters() { - for (int i = 0; i < FilterCount; ++i) { - if (m_filter[i]) { - delete m_filter[i]; - m_filter[i] = nullptr; - } - } + m_filters = {}; } bool AnalyzerWaveform::processSamples(const CSAMPLE* pIn, SINT count) { @@ -222,15 +220,16 @@ bool AnalyzerWaveform::processSamples(const CSAMPLE* pIn, SINT count) { } // This should only append once if count is constant - if (count > static_cast(m_buffers[0].size())) { - m_buffers[Low].resize(count); - m_buffers[Mid].resize(count); - m_buffers[High].resize(count); + if (count > m_buffers.size) { + m_buffers.low.resize(count); + m_buffers.mid.resize(count); + m_buffers.high.resize(count); + m_buffers.size = count; } - m_filter[Low]->process(pWaveformInput, &m_buffers[Low][0], count); - m_filter[Mid]->process(pWaveformInput, &m_buffers[Mid][0], count); - m_filter[High]->process(pWaveformInput, &m_buffers[High][0], count); + m_filters.low->process(pWaveformInput, &m_buffers.low[0], count); + m_filters.mid->process(pWaveformInput, &m_buffers.mid[0], count); + m_filters.high->process(pWaveformInput, &m_buffers.high[0], count); m_waveform->setSaveState(Waveform::SaveState::NotSaved); m_waveformSummary->setSaveState(Waveform::SaveState::NotSaved); @@ -238,20 +237,20 @@ bool AnalyzerWaveform::processSamples(const CSAMPLE* pIn, SINT count) { for (SINT i = 0; i < count; i += 2) { // Take max value, not average of data CSAMPLE cover[2] = {fabs(pWaveformInput[i]), fabs(pWaveformInput[i + 1])}; - CSAMPLE clow[2] = {fabs(m_buffers[Low][i]), fabs(m_buffers[Low][i + 1])}; - CSAMPLE cmid[2] = {fabs(m_buffers[Mid][i]), fabs(m_buffers[Mid][i + 1])}; - CSAMPLE chigh[2] = {fabs(m_buffers[High][i]), fabs(m_buffers[High][i + 1])}; + CSAMPLE clow[2] = {fabs(m_buffers.low[i]), fabs(m_buffers.low[i + 1])}; + CSAMPLE cmid[2] = {fabs(m_buffers.mid[i]), fabs(m_buffers.mid[i + 1])}; + CSAMPLE chigh[2] = {fabs(m_buffers.high[i]), fabs(m_buffers.high[i + 1])}; // This is for if you want to experiment with averaging instead of // maxing. // m_stride.m_overallData[Right] += buffer[i]*buffer[i]; // m_stride.m_overallData[Left] += buffer[i + 1]*buffer[i + 1]; - // m_stride.m_filteredData[Right][Low] += m_buffers[Low][i]*m_buffers[Low][i]; - // m_stride.m_filteredData[Left][Low] += m_buffers[Low][i + 1]*m_buffers[Low][i + 1]; - // m_stride.m_filteredData[Right][Mid] += m_buffers[Mid][i]*m_buffers[Mid][i]; - // m_stride.m_filteredData[Left][Mid] += m_buffers[Mid][i + 1]*m_buffers[Mid][i + 1]; - // m_stride.m_filteredData[Right][High] += m_buffers[High][i]*m_buffers[High][i]; - // m_stride.m_filteredData[Left][High] += m_buffers[High][i + 1]*m_buffers[High][i + 1]; + // m_stride.m_filteredData[Right][Low] += m_buffers.low[i]*m_buffers.low[i]; + // m_stride.m_filteredData[Left][Low] += m_buffers.low[i + 1]*m_buffers.low[i + 1]; + // m_stride.m_filteredData[Right][Mid] += m_buffers.mid[i]*m_buffers.mid[i]; + // m_stride.m_filteredData[Left][Mid] += m_buffers.mid[i + 1]*m_buffers.mid[i + 1]; + // m_stride.m_filteredData[Right][High] += m_buffers.high[i]*m_buffers.high[i]; + // m_stride.m_filteredData[Left][High] += m_buffers.high[i + 1]*m_buffers.high[i + 1]; // Record the max across this stride. storeIfGreater(&m_stride.m_overallData[Left], cover[Left]); diff --git a/src/analyzer/analyzerwaveform.h b/src/analyzer/analyzerwaveform.h index ccdafd5242d..aa5a77d1336 100644 --- a/src/analyzer/analyzerwaveform.h +++ b/src/analyzer/analyzerwaveform.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "analyzer/analyzer.h" @@ -172,8 +173,30 @@ class AnalyzerWaveform : public Analyzer { int m_currentSummaryStride; mixxx::audio::ChannelCount m_channelCount; - EngineFilterIIRBase* m_filter[FilterCount]; - std::vector m_buffers[FilterCount]; + struct Filters { + std::unique_ptr low; + std::unique_ptr mid; + std::unique_ptr high; + }; + + Filters m_filters; + + struct Buffers { + std::vector low; + std::vector mid; + std::vector high; + + SINT size; + + Buffers() + : low(), + mid(), + high(), + size(0) { + } + }; + + Buffers m_buffers; PerformanceTimer m_timer; diff --git a/src/main.cpp b/src/main.cpp index 9041dbf0f96..e11e038ab5c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,38 +57,16 @@ constexpr int kPixmapCacheLimitAt100PercentZoom = 32 * 1024; // 32 MByte int runMixxx(MixxxApplication* pApp, const CmdlineArgs& args) { CmdlineArgs::Instance().parseForUserFeedback(); - const auto pCoreServices = std::make_shared(args, pApp); - int exitCode; #ifdef MIXXX_USE_QML if (args.isQml()) { - auto pTick = std::make_unique(); - auto pVisuals = std::make_unique(); - WaveformWidgetFactory::createInstance(); // takes a long time - WaveformWidgetFactory::instance()->setConfig(pCoreServices->getSettings()); - WaveformWidgetFactory::instance()->startVSync(pTick.get(), pVisuals.get()); - { - mixxx::qml::QmlApplication qmlApplication(pApp, pCoreServices); - const QStringList visualGroups = - pCoreServices->getPlayerManager()->getVisualPlayerGroups(); - for (const QString& group : visualGroups) { - pVisuals->addDeck(group); - } - pCoreServices->getPlayerManager()->connect(pCoreServices->getPlayerManager().get(), - &PlayerManager::numberOfDecksChanged, - &qmlApplication, - [&pVisuals](int decks) { - for (int i = 0; i < decks; ++i) { - QString group = PlayerManager::groupForDeck(i); - pVisuals->addDeckIfNotExist(group); - } - }); - exitCode = pApp->exec(); - } - WaveformWidgetFactory::destroy(); + mixxx::qml::QmlApplication qmlApplication(pApp, args); + exitCode = pApp->exec(); } else #endif { + auto pCoreServices = std::make_shared(args, pApp); + // This scope ensures that `MixxxMainWindow` is destroyed *before* // CoreServices is shut down. Otherwise a debug assertion complaining about // leaked COs may be triggered. diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index 952679645c1..3d4c5af9011 100644 --- a/src/mixxxmainwindow.cpp +++ b/src/mixxxmainwindow.cpp @@ -274,7 +274,7 @@ void MixxxMainWindow::initialize() { WaveformWidgetFactory::createInstance(); // takes a long time WaveformWidgetFactory::instance()->setConfig(m_pCoreServices->getSettings()); - WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager); + WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager, false); connect(this, &MixxxMainWindow::skinLoaded, @@ -515,6 +515,8 @@ MixxxMainWindow::~MixxxMainWindow() { m_pCoreServices->getControlIndicatorTimer()->setLegacyVsyncEnabled(false); + qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting ControllerManager"; + WaveformWidgetFactory::destroy(); delete m_pGuiTick; diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp index 3e4c50ea460..3a1e426a93c 100644 --- a/src/preferences/dialog/dlgprefwaveform.cpp +++ b/src/preferences/dialog/dlgprefwaveform.cpp @@ -283,10 +283,10 @@ void DlgPrefWaveform::slotUpdate() { endOfTrackWarningTimeSpinBox->setValue(factory->getEndOfTrackWarningTime()); endOfTrackWarningTimeSlider->setValue(factory->getEndOfTrackWarningTime()); synchronizeZoomCheckBox->setChecked(factory->isZoomSync()); - allVisualGain->setValue(factory->getVisualGain(WaveformWidgetFactory::All)); - lowVisualGain->setValue(factory->getVisualGain(WaveformWidgetFactory::Low)); - midVisualGain->setValue(factory->getVisualGain(WaveformWidgetFactory::Mid)); - highVisualGain->setValue(factory->getVisualGain(WaveformWidgetFactory::High)); + allVisualGain->setValue(factory->getVisualGain(FilterIndex::AllChannel)); + lowVisualGain->setValue(factory->getVisualGain(FilterIndex::Low)); + midVisualGain->setValue(factory->getVisualGain(FilterIndex::Mid)); + highVisualGain->setValue(factory->getVisualGain(FilterIndex::High)); normalizeOverviewCheckBox->setChecked(factory->isOverviewNormalized()); // Round zoom to int to get a default zoom index. defaultZoomComboBox->setCurrentIndex(static_cast(factory->getDefaultZoom()) - 1); @@ -566,19 +566,19 @@ void DlgPrefWaveform::slotSetZoomSynchronization(bool checked) { } void DlgPrefWaveform::slotSetVisualGainAll(double gain) { - WaveformWidgetFactory::instance()->setVisualGain(WaveformWidgetFactory::All,gain); + WaveformWidgetFactory::instance()->setVisualGain(FilterIndex::AllChannel, gain); } void DlgPrefWaveform::slotSetVisualGainLow(double gain) { - WaveformWidgetFactory::instance()->setVisualGain(WaveformWidgetFactory::Low,gain); + WaveformWidgetFactory::instance()->setVisualGain(FilterIndex::Low, gain); } void DlgPrefWaveform::slotSetVisualGainMid(double gain) { - WaveformWidgetFactory::instance()->setVisualGain(WaveformWidgetFactory::Mid,gain); + WaveformWidgetFactory::instance()->setVisualGain(FilterIndex::Mid, gain); } void DlgPrefWaveform::slotSetVisualGainHigh(double gain) { - WaveformWidgetFactory::instance()->setVisualGain(WaveformWidgetFactory::High,gain); + WaveformWidgetFactory::instance()->setVisualGain(FilterIndex::High, gain); } void DlgPrefWaveform::slotSetNormalizeOverview(bool normalize) { diff --git a/src/qml/qmlapplication.cpp b/src/qml/qmlapplication.cpp index 5ead34eb30d..062164021d9 100644 --- a/src/qml/qmlapplication.cpp +++ b/src/qml/qmlapplication.cpp @@ -3,23 +3,14 @@ #include #include -#include "control/controlsortfiltermodel.h" #include "controllers/controllermanager.h" +#include "mixer/playermanager.h" #include "moc_qmlapplication.cpp" #include "qml/asyncimageprovider.h" -#include "qml/qmlconfigproxy.h" -#include "qml/qmlcontrolproxy.h" #include "qml/qmldlgpreferencesproxy.h" -#include "qml/qmleffectmanifestparametersmodel.h" -#include "qml/qmleffectslotproxy.h" -#include "qml/qmleffectsmanagerproxy.h" -#include "qml/qmllibraryproxy.h" -#include "qml/qmllibrarytracklistmodel.h" -#include "qml/qmlplayermanagerproxy.h" -#include "qml/qmlplayerproxy.h" -#include "qml/qmlvisibleeffectsmodel.h" -#include "qml/qmlwaveformoverview.h" #include "soundio/soundmanager.h" +#include "waveform/visualsmanager.h" +#include "waveform/waveformwidgetfactory.h" Q_IMPORT_QML_PLUGIN(MixxxPlugin) Q_IMPORT_QML_PLUGIN(Mixxx_ControlsPlugin) @@ -42,9 +33,10 @@ namespace qml { QmlApplication::QmlApplication( QApplication* app, - std::shared_ptr pCoreServices) - : m_pCoreServices(pCoreServices), - m_mainFilePath(pCoreServices->getSettings()->getResourcePath() + kMainQmlFileName), + const CmdlineArgs& args) + : m_pCoreServices(std::make_unique(args, app)), + m_visualsManager(std::make_unique()), + m_mainFilePath(m_pCoreServices->getSettings()->getResourcePath() + kMainQmlFileName), m_pAppEngine(nullptr), m_autoReload() { QQuickStyle::setStyle("Basic"); @@ -67,10 +59,11 @@ QmlApplication::QmlApplication( // Since DlgPreferences is only meant to be used in the main QML engine, it // follows a strict singleton pattern design - QmlDlgPreferencesProxy::s_pInstance = new QmlDlgPreferencesProxy(pDlgPreferences, this); + QmlDlgPreferencesProxy::s_pInstance = + std::make_unique(pDlgPreferences, this); loadQml(m_mainFilePath); - pCoreServices->getControllerManager()->setUpDevices(); + m_pCoreServices->getControllerManager()->setUpDevices(); connect(&m_autoReload, &QmlAutoReload::triggered, @@ -78,11 +71,31 @@ QmlApplication::QmlApplication( [this]() { loadQml(m_mainFilePath); }); + + const QStringList visualGroups = + m_pCoreServices->getPlayerManager()->getVisualPlayerGroups(); + for (const QString& group : visualGroups) { + m_visualsManager->addDeck(group); + } + + m_pCoreServices->getPlayerManager()->connect( + m_pCoreServices->getPlayerManager().get(), + &PlayerManager::numberOfDecksChanged, + this, + [this](int decks) { + for (int i = 0; i < decks; ++i) { + QString group = PlayerManager::groupForDeck(i); + m_visualsManager->addDeckIfNotExist(group); + } + }); } QmlApplication::~QmlApplication() { // Delete all the QML singletons in order to prevent leak detection in CoreService - QmlDlgPreferencesProxy::s_pInstance->deleteLater(); + QmlDlgPreferencesProxy::s_pInstance.reset(); + m_visualsManager.reset(); + m_pAppEngine.reset(); + m_pCoreServices.reset(); } void QmlApplication::loadQml(const QString& path) { diff --git a/src/qml/qmlapplication.h b/src/qml/qmlapplication.h index b13e8a0ed83..0e76be99de1 100644 --- a/src/qml/qmlapplication.h +++ b/src/qml/qmlapplication.h @@ -6,6 +6,9 @@ #include "coreservices.h" #include "qmlautoreload.h" +class GuiTick; +class VisualsManager; + namespace mixxx { namespace qml { @@ -14,14 +17,17 @@ class QmlApplication : public QObject { public: QmlApplication( QApplication* app, - std::shared_ptr pCoreServices); + const CmdlineArgs& args); ~QmlApplication() override; public slots: void loadQml(const QString& path); private: - std::shared_ptr m_pCoreServices; + std::unique_ptr m_pCoreServices; + + // std::unique_ptr<::GuiTick> m_guiTick; + std::unique_ptr<::VisualsManager> m_visualsManager; QString m_mainFilePath; diff --git a/src/qml/qmldlgpreferencesproxy.cpp b/src/qml/qmldlgpreferencesproxy.cpp index fbaf4a2eeae..7c67872866e 100644 --- a/src/qml/qmldlgpreferencesproxy.cpp +++ b/src/qml/qmldlgpreferencesproxy.cpp @@ -43,8 +43,8 @@ QmlDlgPreferencesProxy* QmlDlgPreferencesProxy::create( // Explicitly specify C++ ownership so that the engine doesn't delete // the instance. - QJSEngine::setObjectOwnership(s_pInstance, QJSEngine::CppOwnership); - return s_pInstance; + QJSEngine::setObjectOwnership(s_pInstance.get(), QJSEngine::CppOwnership); + return s_pInstance.get(); } } // namespace qml diff --git a/src/qml/qmldlgpreferencesproxy.h b/src/qml/qmldlgpreferencesproxy.h index 40e73ea300a..b41ed44ebec 100644 --- a/src/qml/qmldlgpreferencesproxy.h +++ b/src/qml/qmldlgpreferencesproxy.h @@ -20,7 +20,7 @@ class QmlDlgPreferencesProxy : public QObject { Q_INVOKABLE void show(); static QmlDlgPreferencesProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine); - static inline QmlDlgPreferencesProxy* s_pInstance = nullptr; + static inline std::unique_ptr s_pInstance; private: static inline QJSEngine* s_pJsEngine = nullptr; diff --git a/src/qml/qmlwaveformdisplay.cpp b/src/qml/qmlwaveformdisplay.cpp new file mode 100644 index 00000000000..2649e2bd3b5 --- /dev/null +++ b/src/qml/qmlwaveformdisplay.cpp @@ -0,0 +1,258 @@ +#include "qml/qmlwaveformdisplay.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mixer/basetrackplayer.h" +#include "moc_qmlwaveformdisplay.cpp" +#include "qml/qmlplayerproxy.h" +#include "rendergraph/context.h" +#include "rendergraph/node.h" +#include "waveform/renderers/allshader/waveformrendermark.h" +#include "waveform/renderers/allshader/waveformrendermarkrange.h" + +using namespace allshader; + +namespace mixxx { +namespace qml { + +QmlWaveformDisplay::QmlWaveformDisplay(QQuickItem* parent) + : QQuickItem(parent), + WaveformWidgetRenderer(), + m_pPlayer(nullptr) { + setFlag(QQuickItem::ItemHasContents, true); + + connect(this, + &QmlWaveformDisplay::windowChanged, + this, + &QmlWaveformDisplay::slotWindowChanged, + Qt::DirectConnection); + slotWindowChanged(window()); +} + +QmlWaveformDisplay::~QmlWaveformDisplay() { + // The stack contains references to Renderer that are owned and cleared by a BaseNode + m_rendererStack.clear(); +} + +void QmlWaveformDisplay::componentComplete() { + qDebug() << "QmlWaveformDisplay ready for group" << getGroup() << "with" + << m_waveformRenderers.count() << "renderer(s)"; + QQuickItem::componentComplete(); +} + +void QmlWaveformDisplay::slotWindowChanged(QQuickWindow* window) { + m_rendererStack.clear(); + + m_dirtyFlag |= DirtyFlag::Window; + if (window) { + connect(window, &QQuickWindow::afterFrameEnd, this, &QmlWaveformDisplay::slotFrameSwapped); + } + m_timer.restart(); +} + +int QmlWaveformDisplay::fromTimerToNextSyncMicros(const PerformanceTimer& timer) { + // TODO @m0dB probably better to use a singleton instead of deriving QmlWaveformDisplay from + // ISyncTimeProvider and have each keep track of this. + int difference = static_cast(m_timer.difference(timer).toIntegerMicros()); + // int math is fine here, because we do not expect times > 4.2 s + + return difference + m_syncIntervalTimeMicros; +} + +void QmlWaveformDisplay::slotFrameSwapped() { + m_timer.restart(); + + // continuous redraw + update(); +} + +void QmlWaveformDisplay::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { + m_dirtyFlag |= DirtyFlag::Geometry; + update(); + QQuickItem::geometryChange(newGeometry, oldGeometry); +} + +QSGNode* QmlWaveformDisplay::updatePaintNode(QSGNode* node, UpdatePaintNodeData*) { + auto* bgNode = dynamic_cast(node); + static rendergraph::GeometryNode* pPreRoll; + + if (m_dirtyFlag.testFlag(DirtyFlag::Window)) { + delete bgNode; + auto* pContext = getContext(); + if (pContext) { + delete pContext; + } + m_dirtyFlag.setFlag(DirtyFlag::Window, false); + } + if (!bgNode) { + bgNode = new QSGSimpleRectNode(); + bgNode->setRect(boundingRect()); + + if (getContext()) { + delete getContext(); + } + setContext(new rendergraph::Context(window())); + m_pTopNode = new rendergraph::Node; + + m_rendererStack.clear(); + for (auto* pQmlRenderer : m_waveformRenderers) { + auto renderer = pQmlRenderer->create(this); + if (!renderer.renderer) { + continue; + } + addRenderer(renderer.renderer); + m_pTopNode->appendChildNode(std::unique_ptr(renderer.node)); + auto* pWaveformRenderMark = + dynamic_cast( + renderer.renderer); + if (pWaveformRenderMark) { + m_waveformRenderMark = pWaveformRenderMark; + } + auto* pWaveformRenderMarkRange = + dynamic_cast( + renderer.renderer); + if (pWaveformRenderMarkRange) { + m_waveformRenderMarkRange = pWaveformRenderMarkRange; + } + } + + bgNode->appendChildNode(m_pTopNode); + init(); + } + + if (m_dirtyFlag.testFlag(DirtyFlag::Background)) { + m_dirtyFlag.setFlag(DirtyFlag::Background, false); + bgNode->setColor(m_backgroundColor); + } + + if (m_dirtyFlag.testFlag(DirtyFlag::Geometry)) { + m_dirtyFlag.setFlag(DirtyFlag::Geometry, false); + resizeRenderer(boundingRect().width(), + boundingRect().height(), + window()->devicePixelRatio()); + bgNode->setRect(boundingRect()); + + auto rect = QRectF(boundingRect().x() + + boundingRect().width() * m_playMarkerPosition - 1.0, + boundingRect().y(), + 2.0, + boundingRect().height()); + } + + if (m_waveformRenderMark != nullptr) { + m_waveformRenderMark->update(); + } + if (m_waveformRenderMarkRange != nullptr) { + m_waveformRenderMarkRange->update(); + } + + onPreRender(this); + bgNode->markDirty(QSGNode::DirtyForceUpdate); + + return bgNode; +} + +QmlPlayerProxy* QmlWaveformDisplay::getPlayer() const { + return m_pPlayer; +} + +void QmlWaveformDisplay::setPlayer(QmlPlayerProxy* pPlayer) { + if (m_pPlayer == pPlayer) { + return; + } + + if (m_pPlayer != nullptr) { + m_pPlayer->internalTrackPlayer()->disconnect(this); + } + + m_pPlayer = pPlayer; + + if (m_pPlayer != nullptr) { + setCurrentTrack(m_pPlayer->internalTrackPlayer()->getLoadedTrack()); + connect(m_pPlayer->internalTrackPlayer(), + &BaseTrackPlayer::newTrackLoaded, + this, + &QmlWaveformDisplay::slotTrackLoaded); + connect(m_pPlayer->internalTrackPlayer(), + &BaseTrackPlayer::loadingTrack, + this, + &QmlWaveformDisplay::slotTrackLoading); + connect(m_pPlayer->internalTrackPlayer(), + &BaseTrackPlayer::playerEmpty, + this, + &QmlWaveformDisplay::slotTrackUnloaded); + } + + emit playerChanged(); + update(); +} + +void QmlWaveformDisplay::setGroup(const QString& group) { + if (getGroup() == group) { + return; + } + + WaveformWidgetRenderer::setGroup(group); + emit groupChanged(group); +} + +void QmlWaveformDisplay::slotTrackLoaded(TrackPointer pTrack) { + // TODO: Investigate if it's a bug that this debug assertion fails when + // passing tracks on the command line + // DEBUG_ASSERT(m_pCurrentTrack == pTrack); + setCurrentTrack(pTrack); +} + +void QmlWaveformDisplay::slotTrackLoading(TrackPointer pNewTrack, TrackPointer pOldTrack) { + Q_UNUSED(pOldTrack); // only used in DEBUG_ASSERT + DEBUG_ASSERT(getTrackInfo() == pOldTrack); + setCurrentTrack(pNewTrack); +} + +void QmlWaveformDisplay::slotTrackUnloaded() { + setCurrentTrack(nullptr); +} + +void QmlWaveformDisplay::setCurrentTrack(TrackPointer pTrack) { + auto pCurrentTrack = getTrackInfo(); + // TODO: Check if this is actually possible + if (pCurrentTrack == pTrack) { + return; + } + + if (pCurrentTrack != nullptr) { + disconnect(pCurrentTrack.get(), nullptr, this, nullptr); + } + + setTrack(pTrack); + if (pTrack != nullptr) { + connect(pTrack.get(), + &Track::waveformSummaryUpdated, + this, + &QmlWaveformDisplay::slotWaveformUpdated); + } + slotWaveformUpdated(); +} + +void QmlWaveformDisplay::slotWaveformUpdated() { + update(); +} + +QQmlListProperty QmlWaveformDisplay::renderers() { + return {this, &m_waveformRenderers}; +} + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformdisplay.h b/src/qml/qmlwaveformdisplay.h new file mode 100644 index 00000000000..b9b19185b0c --- /dev/null +++ b/src/qml/qmlwaveformdisplay.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "qml/qmlplayerproxy.h" +#include "qml/qmlwaveformrenderer.h" +#include "track/track.h" +#include "util/performancetimer.h" +#include "waveform/isynctimeprovider.h" +#include "waveform/renderers/waveformwidgetrenderer.h" + +class WaveformRendererAbstract; + +namespace allshader { +class WaveformWidget; +class WaveformRenderMark; +class WaveformRenderMarkRange; +} // namespace allshader +namespace rendergraph { +class Node; +class OpacityNode; +class TreeNode; +} // namespace rendergraph + +namespace mixxx { +namespace qml { + +class QmlPlayerProxy; + +class QmlWaveformDisplay : public QQuickItem, ISyncTimeProvider, public WaveformWidgetRenderer { + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QmlPlayerProxy* player READ getPlayer WRITE setPlayer + NOTIFY playerChanged REQUIRED) + Q_PROPERTY(QString group READ getGroup WRITE setGroup NOTIFY groupChanged REQUIRED) + Q_PROPERTY(QQmlListProperty renderers READ renderers) + Q_PROPERTY(double zoom READ getZoom WRITE setZoom NOTIFY zoomChanged) + Q_PROPERTY(QColor backgroundColor READ getBackgroundColor WRITE + setBackgroundColor NOTIFY backgroundColorChanged) + Q_CLASSINFO("DefaultProperty", "renderers") + QML_NAMED_ELEMENT(WaveformDisplay) + + public: + QmlWaveformDisplay(QQuickItem* parent = nullptr); + ~QmlWaveformDisplay() override; + + void setPlayer(QmlPlayerProxy* player); + QmlPlayerProxy* getPlayer() const; + + QColor getBackgroundColor() const { + return m_backgroundColor; + } + void setBackgroundColor(QColor color) { + m_backgroundColor = color; + m_dirtyFlag.setFlag(DirtyFlag::Background, true); + emit backgroundColorChanged(); + } + + void setGroup(const QString& group) override; + void setZoom(double zoom) { + WaveformWidgetRenderer::setZoom(zoom); + emit zoomChanged(); + } + + int fromTimerToNextSyncMicros(const PerformanceTimer& timer) override; + int getSyncIntervalTimeMicros() const override { + return m_syncIntervalTimeMicros; + } + + virtual void componentComplete() override; + + QQmlListProperty renderers(); + + protected: + QSGNode* updatePaintNode(QSGNode* old, QQuickItem::UpdatePaintNodeData*) override; + void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; + private slots: + void slotTrackLoaded(TrackPointer pLoadedTrack); + void slotTrackLoading(TrackPointer pNewTrack, TrackPointer pOldTrack); + void slotTrackUnloaded(); + void slotWaveformUpdated(); + + void slotFrameSwapped(); + void slotWindowChanged(QQuickWindow* window); + signals: + void playerChanged(); + void zoomChanged(); + void groupChanged(const QString& group); + void backgroundColorChanged(); + + private: + void setCurrentTrack(TrackPointer pTrack); + + // Properties + QPointer m_pPlayer; + QColor m_backgroundColor{QColor(0, 0, 0, 255)}; + + PerformanceTimer m_timer; + + int m_syncIntervalTimeMicros{1000000 / 10}; // TODO don't hardcode + + enum class DirtyFlag : int { + None = 0x0, + Geometry = 0x1, + Window = 0x2, + Background = 0x4, + }; + Q_DECLARE_FLAGS(DirtyFlags, DirtyFlag) + + DirtyFlags m_dirtyFlag{DirtyFlag::None}; + QList m_waveformRenderers; + + // Owned by the QML scene? + rendergraph::Node* m_pTopNode; + allshader::WaveformRenderMark* m_waveformRenderMark; + allshader::WaveformRenderMarkRange* m_waveformRenderMarkRange; +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformrenderer.cpp b/src/qml/qmlwaveformrenderer.cpp new file mode 100644 index 00000000000..30952439452 --- /dev/null +++ b/src/qml/qmlwaveformrenderer.cpp @@ -0,0 +1,186 @@ +#include "qml/qmlwaveformrenderer.h" + +#include + +#include "moc_qmlwaveformrenderer.cpp" +#include "util/assert.h" +#include "waveform/renderers/allshader/waveformrenderbeat.h" +#include "waveform/renderers/allshader/waveformrendererendoftrack.h" +#include "waveform/renderers/allshader/waveformrendererpreroll.h" +#include "waveform/renderers/allshader/waveformrendererrgb.h" +#ifdef __STEM__ +#include "waveform/renderers/allshader/waveformrendererstem.h" +#endif +#include "waveform/renderers/allshader/waveformrendermark.h" +#include "waveform/renderers/allshader/waveformrendermarkrange.h" + +using namespace allshader; + +namespace mixxx { +namespace qml { + +QmlWaveformRendererEndOfTrack::QmlWaveformRendererEndOfTrack() { +} + +QmlWaveformRendererPreroll::QmlWaveformRendererPreroll() { +} + +QmlWaveformRendererRGB::QmlWaveformRendererRGB() { +} + +double QmlWaveformRendererRGB::getVisualGain(FilterIndex index) const { + switch (index) { + case AllChannel: + return m_gainAll; + case Low: + return m_gainLow; + case Mid: + return m_gainMid; + case High: + return m_gainHigh; + case FilterCount: + break; + } + return 1.0; +} + +QmlWaveformRendererBeat::QmlWaveformRendererBeat() { +} + +QmlWaveformRendererMarkRange::QmlWaveformRendererMarkRange() { +} + +QmlWaveformRendererStem::QmlWaveformRendererStem() { +} + +double QmlWaveformRendererStem::getVisualGain(FilterIndex index) const { + switch (index) { + case AllChannel: + return m_gainAll; + case Low: + case Mid: + case High: + case FilterCount: + break; + } + return 1.0; +} + +QmlWaveformRendererMark::QmlWaveformRendererMark() + : m_defaultMark(nullptr), + m_untilMark(std::make_unique()) { +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererEndOfTrack::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererEndOfTrack(waveformWidget); + renderer->setup(m_color, m_endOfTrackWarningTime); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererPreroll::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererPreroll( + waveformWidget, m_position); + renderer->setup(m_color); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererRGB::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererRGB(waveformWidget, m_position, m_options, this); + renderer->setup( + m_axesColor, + m_lowColor, + m_midColor, + m_highColor); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererBeat::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRenderBeat( + waveformWidget, m_position); + renderer->setup(m_color); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererMarkRange::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRenderMarkRange( + waveformWidget); + + for (auto* pMark : m_ranges) { + renderer->addRange(WaveformMarkRange( + waveformWidget->getGroup(), + pMark->color(), + pMark->disabledColor(), + pMark->opacity(), + pMark->disabledOpacity(), + pMark->durationTextColor(), + pMark->startControl(), + pMark->endControl(), + pMark->enabledControl(), + pMark->visibilityControl(), + pMark->durationTextLocation())); + } + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +#ifdef __STEM__ +QmlWaveformRendererFactory::Renderer QmlWaveformRendererStem::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererStem( + waveformWidget, m_position, this); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} +#endif + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererMark::create( + WaveformWidgetRenderer* waveformWidget) const { + VERIFY_OR_DEBUG_ASSERT(!!m_untilMark) { + return QmlWaveformRendererFactory::Renderer{}; + } + auto* renderer = new WaveformRenderMark(waveformWidget, + ::WaveformRendererAbstract::Play); + renderer->setup( + m_playMarkerColor, + m_playMarkerBackground, + m_untilMark->showTime(), + m_untilMark->showBeats(), + static_cast(m_untilMark->align()), + m_untilMark->textSize(), + m_untilMark->textHeightLimit()); + int priority = 0; + for (auto* pMark : m_marks) { + renderer->addMark(WaveformMarkPointer(new WaveformMark( + waveformWidget->getGroup(), + pMark->control(), + pMark->visibilityControl(), + pMark->textColor(), + pMark->align(), + pMark->text(), + pMark->pixmap(), + pMark->icon(), + pMark->color(), + --priority))); + } + auto* pMark = defaultMark(); + if (pMark != nullptr) { + renderer->setDefaultMark( + waveformWidget->getGroup(), + WaveformMarkSet::Seed{ + pMark->control(), + pMark->visibilityControl(), + pMark->textColor(), + pMark->align(), + pMark->text(), + pMark->pixmap(), + pMark->icon(), + pMark->color(), + }); + } + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformrenderer.h b/src/qml/qmlwaveformrenderer.h new file mode 100644 index 00000000000..e1d27b58dd1 --- /dev/null +++ b/src/qml/qmlwaveformrenderer.h @@ -0,0 +1,582 @@ +#pragma once + +#include +#include + +#include "rendergraph/node.h" +#include "waveform/renderers/allshader/waveformrenderersignalbase.h" +#include "waveform/renderers/waveformrendererabstract.h" +#include "waveform/renderers/waveformwidgetrenderer.h" + +class WaveformWidgetRenderer; + +namespace allshaders { +class WaveformRendererEndOfTrack; +class WaveformRendererPreroll; +class WaveformRendererRGB; +class WaveformRenderBeat; +} // namespace allshaders + +namespace mixxx { +namespace qml { + +class QmlWaveformRendererFactory : public QObject { + Q_OBJECT + QML_ANONYMOUS + public: + struct Renderer { + ::WaveformRendererAbstract* renderer{nullptr}; + rendergraph::BaseNode* node{nullptr}; + }; + + virtual Renderer create(WaveformWidgetRenderer* waveformWidget) const = 0; +}; + +class QmlWaveformRendererEndOfTrack + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + Q_PROPERTY(int endOfTrackWarningTime READ endOfTrackWarningTime WRITE + setEndOfTrackWarningTime NOTIFY endOfTrackWarningTimeChanged + REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererEndOfTrack) + + public: + QmlWaveformRendererEndOfTrack(); + + const QColor& color() const { + return m_color; + } + void setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + } + + int endOfTrackWarningTime() const { + return m_endOfTrackWarningTime; + } + + void setEndOfTrackWarningTime(int endOfTrackWarningTime) { + m_endOfTrackWarningTime = endOfTrackWarningTime; + emit endOfTrackWarningTimeChanged(m_endOfTrackWarningTime); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + signals: + void colorChanged(const QColor&); + void endOfTrackWarningTimeChanged(int m_endOfTrackWarningTime); + + private: + QColor m_color; + int m_endOfTrackWarningTime; +}; + +class QmlWaveformRendererPreroll + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererPreroll) + + public: + QmlWaveformRendererPreroll(); + + const QColor& color() const { + return m_color; + } + void setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + signals: + void colorChanged(const QColor&); + + private: + QColor m_color; + ::WaveformRendererAbstract::PositionSource m_position{::WaveformRendererAbstract::Play}; +}; + +class QmlWaveformRendererRGB + : public QmlWaveformRendererFactory, + public IVisualGainProvider { + Q_OBJECT + Q_PROPERTY(QColor axesColor READ axesColor WRITE setAxesColor NOTIFY axesColorChanged REQUIRED) + Q_PROPERTY(QColor lowColor READ lowColor WRITE setLowColor NOTIFY lowColorChanged REQUIRED) + Q_PROPERTY(QColor midColor READ midColor WRITE setMidColor NOTIFY midColorChanged REQUIRED) + Q_PROPERTY(QColor highColor READ highColor WRITE setHighColor NOTIFY highColorChanged REQUIRED) + Q_PROPERTY(double gainAll READ gainAll WRITE setGainAll NOTIFY gainAllChanged REQUIRED) + Q_PROPERTY(double gainLow READ gainLow WRITE setGainLow NOTIFY gainLowChanged REQUIRED) + Q_PROPERTY(double gainMid READ gainMid WRITE setGainMid NOTIFY gainMidChanged REQUIRED) + Q_PROPERTY(double gainHigh READ gainHigh WRITE setGainHigh NOTIFY gainHighChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererRGB) + + public: + QmlWaveformRendererRGB(); + + const QColor& axesColor() const { + return m_axesColor; + } + void setAxesColor(QColor color) { + m_axesColor = color; + emit axesColorChanged(m_axesColor); + } + + const QColor& lowColor() const { + return m_lowColor; + } + void setLowColor(QColor color) { + m_lowColor = color; + emit lowColorChanged(m_lowColor); + } + + const QColor& midColor() const { + return m_lowColor; + } + void setMidColor(QColor color) { + m_midColor = color; + emit midColorChanged(m_lowColor); + } + + const QColor& highColor() const { + return m_lowColor; + } + void setHighColor(QColor color) { + m_highColor = color; + emit highColorChanged(m_lowColor); + } + + double gainAll() const { + return m_gainAll; + } + void setGainAll(double gain) { + m_gainAll = gain; + emit gainAllChanged(gain); + } + + double gainLow() const { + return m_gainLow; + } + void setGainLow(double gain) { + m_gainLow = gain; + emit gainLowChanged(gain); + } + + double gainMid() const { + return m_gainMid; + } + void setGainMid(double gain) { + m_gainMid = gain; + emit gainMidChanged(gain); + } + + double gainHigh() const { + return m_gainHigh; + } + void setGainHigh(double gain) { + m_gainHigh = gain; + emit gainHighChanged(gain); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + double getVisualGain(FilterIndex index) const override; + + signals: + void axesColorChanged(const QColor&); + void lowColorChanged(const QColor&); + void midColorChanged(const QColor&); + void highColorChanged(const QColor&); + void gainAllChanged(double); + void gainLowChanged(double); + void gainMidChanged(double); + void gainHighChanged(double); + + private: + QColor m_axesColor; + QColor m_lowColor; + QColor m_midColor; + QColor m_highColor; + + double m_gainAll; + double m_gainLow; + double m_gainMid; + double m_gainHigh; + + ::WaveformRendererAbstract::PositionSource m_position{::WaveformRendererAbstract::Play}; + allshader::WaveformRendererSignalBase::Options m_options{ + allshader::WaveformRendererSignalBase::Option::None}; +}; + +class QmlWaveformRendererBeat + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererBeat) + + public: + QmlWaveformRendererBeat(); + + const QColor& color() const { + return m_color; + } + void setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + signals: + void colorChanged(const QColor&); + + private: + QColor m_color; + ::WaveformRendererAbstract::PositionSource m_position{::WaveformRendererAbstract::Play}; +}; + +class QmlWaveformMarkRange : public QObject { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(QColor disabledColor READ disabledColor WRITE setDisabledColor) + Q_PROPERTY(double opacity READ opacity WRITE setOpacity) + Q_PROPERTY(double disabledOpacity READ disabledOpacity WRITE setDisabledOpacity) + Q_PROPERTY(QColor durationTextColor READ durationTextColor WRITE setDurationTextColor) + Q_PROPERTY(QString startControl READ startControl WRITE setStartControl) + Q_PROPERTY(QString endControl READ endControl WRITE setEndControl) + Q_PROPERTY(QString enabledControl READ enabledControl WRITE setEnabledControl) + Q_PROPERTY(QString visibilityControl READ visibilityControl WRITE setVisibilityControl) + Q_PROPERTY(QString durationTextLocation READ durationTextLocation WRITE setDurationTextLocation) + QML_NAMED_ELEMENT(WaveformMarkRange) + + public: + QColor color() const { + return m_color; + } + + void setColor(const QColor& value) { + m_color = value; + } + + QColor disabledColor() const { + return m_disabledColor; + } + + void setDisabledColor(const QColor& value) { + m_disabledColor = value; + } + + double opacity() const { + return m_opacity; + } + + void setOpacity(double value) { + m_opacity = value; + } + + double disabledOpacity() const { + return m_disabledOpacity; + } + + void setDisabledOpacity(double value) { + m_disabledOpacity = value; + } + + QColor durationTextColor() const { + return m_durationTextColor; + } + + void setDurationTextColor(const QColor& value) { + m_durationTextColor = value; + } + + QString startControl() const { + return m_startControl; + } + + void setStartControl(const QString& value) { + m_startControl = value; + } + + QString endControl() const { + return m_endControl; + } + + void setEndControl(const QString& value) { + m_endControl = value; + } + + QString enabledControl() const { + return m_enabledControl; + } + + void setEnabledControl(const QString& value) { + m_enabledControl = value; + } + + QString visibilityControl() const { + return m_visibilityControl; + } + + void setVisibilityControl(const QString& value) { + m_visibilityControl = value; + } + + QString durationTextLocation() const { + return m_durationTextLocation; + } + + void setDurationTextLocation(const QString& value) { + m_durationTextLocation = value; + } + + private: + double m_opacity{0.5}; + double m_disabledOpacity{0.5}; + QColor m_color; + QColor m_disabledColor; + QColor m_durationTextColor; + QString m_startControl; + QString m_endControl; + QString m_enabledControl; + QString m_visibilityControl; + QString m_durationTextLocation; +}; + +class QmlWaveformMark : public QObject { + Q_OBJECT + Q_PROPERTY(QString control READ control WRITE setControl) + Q_PROPERTY(QString visibilityControl READ visibilityControl WRITE setVisibilityControl) + Q_PROPERTY(QString color READ color WRITE setColor) + Q_PROPERTY(QString textColor READ textColor WRITE setTextColor) + Q_PROPERTY(QString align READ align WRITE setAlign) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString pixmap READ pixmap WRITE setPixmap) + Q_PROPERTY(QString icon READ icon WRITE setIcon) + QML_NAMED_ELEMENT(WaveformMark) + public: + QString control() const { + return m_control; + } + void setControl(const QString& value) { + m_control = value; + } + QString visibilityControl() const { + return m_visibilityControl; + } + void setVisibilityControl(const QString& value) { + m_visibilityControl = value; + } + QString color() const { + return m_color; + } + void setColor(const QString& value) { + m_color = value; + } + QString textColor() const { + return m_textColor; + } + void setTextColor(const QString& value) { + m_textColor = value; + } + QString align() const { + return m_align; + } + void setAlign(const QString& value) { + m_align = value; + } + QString text() const { + return m_text; + } + void setText(const QString& value) { + m_text = value; + } + QString pixmap() const { + return m_pixmap; + } + void setPixmap(const QString& value) { + m_pixmap = value; + } + QString icon() const { + return m_icon; + } + void setIcon(const QString& value) { + m_icon = value; + } + + private: + QString m_control; + QString m_visibilityControl; + QString m_color; + QString m_textColor; + QString m_align; + QString m_text; + QString m_pixmap; + QString m_icon; +}; + +class QmlWaveformUntilMark : public QObject { + Q_OBJECT + Q_PROPERTY(bool showTime READ showTime WRITE setShowTime) + Q_PROPERTY(bool showBeats READ showBeats WRITE setShowBeats) + Q_PROPERTY(HAlignment align READ align WRITE setAlign) + Q_PROPERTY(int textSize READ textSize WRITE setTextSize) + + QML_NAMED_ELEMENT(WaveformUntilMark) + public: + enum HAlignment { AlignTop = Qt::AlignTop, + AlignCenter = Qt::AlignCenter, + AlignBottom = Qt::AlignBottom }; + Q_ENUM(HAlignment) + + bool showTime() const { + return m_showTime; + } + void setShowTime(bool showTime) { + m_showTime = showTime; + } + bool showBeats() const { + return m_showBeats; + } + void setShowBeats(bool showBeats) { + m_showBeats = showBeats; + } + HAlignment align() const { + return m_align; + } + void setAlign(HAlignment align) { + m_align = align; + } + int textSize() const { + return m_textSize; + } + void setTextSize(int textSize) { + m_textSize = textSize; + } + float textHeightLimit() const { + return m_textSize; + } + void setTextHeightLimit(float textHeightLimit) { + m_textHeightLimit = textHeightLimit; + } + + private: + bool m_showTime; + bool m_showBeats; + HAlignment m_align; + int m_textSize; + int m_textHeightLimit; +}; + +class QmlWaveformRendererMarkRange + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QQmlListProperty ranges READ ranges) + Q_CLASSINFO("DefaultProperty", "ranges") + QML_NAMED_ELEMENT(WaveformRendererMarkRange) + + public: + QmlWaveformRendererMarkRange(); + + QQmlListProperty ranges() { + return {this, &m_ranges}; + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + private: + QList m_ranges; +}; + +class QmlWaveformRendererStem + : public QmlWaveformRendererFactory, + public IVisualGainProvider { + Q_OBJECT + Q_PROPERTY(double gainAll READ gainAll WRITE setGainAll NOTIFY gainAllChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererStem) + + public: + QmlWaveformRendererStem(); + + double gainAll() const { + return m_gainAll; + } + void setGainAll(double gain) { + m_gainAll = gain; + emit gainAllChanged(gain); + } + +#ifdef __STEM__ + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; +#else + Renderer create(WaveformWidgetRenderer* waveformWidget) const override { + return Renderer{}; + } +#endif + + double getVisualGain(FilterIndex index) const override; + + signals: + void gainAllChanged(double); + + private: + double m_gainAll; + + ::WaveformRendererAbstract::PositionSource m_position{::WaveformRendererAbstract::Play}; +}; + +class QmlWaveformRendererMark + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QQmlListProperty marks READ marks) + Q_PROPERTY(QColor playMarkerColor READ playMarkerColor WRITE setPlayMarkerColor) + Q_PROPERTY(QColor playMarkerBackground READ playMarkerBackground WRITE setPlayMarkerBackground) + Q_PROPERTY(QmlWaveformMark* defaultMark READ defaultMark WRITE setDefaultMark) + Q_PROPERTY(QmlWaveformUntilMark* untilMark READ untilMark FINAL) + Q_CLASSINFO("DefaultProperty", "marks") + QML_NAMED_ELEMENT(WaveformRendererMark) + + public: + QmlWaveformRendererMark(); + + QmlWaveformMark* defaultMark() const { + return m_defaultMark; + } + + QmlWaveformUntilMark* untilMark() const { + return m_untilMark.get(); + } + void setDefaultMark(QmlWaveformMark* defaultMark) { + m_defaultMark = defaultMark; + } + + const QColor& playMarkerColor() const { + return m_playMarkerColor; + } + void setPlayMarkerColor(const QColor& playMarkerColor) { + m_playMarkerColor = playMarkerColor; + } + + const QColor& playMarkerBackground() const { + return m_playMarkerBackground; + } + void setPlayMarkerBackground(const QColor& playMarkerBackground) { + m_playMarkerBackground = playMarkerBackground; + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + QQmlListProperty marks() { + return {this, &m_marks}; + } + + private: + QColor m_playMarkerColor; + QColor m_playMarkerBackground; + QList m_marks; + QmlWaveformMark* m_defaultMark; + std::unique_ptr m_untilMark; +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/rendergraph/CMakeLists.txt b/src/rendergraph/CMakeLists.txt index 23569ef30ca..8f14cb2f313 100644 --- a/src/rendergraph/CMakeLists.txt +++ b/src/rendergraph/CMakeLists.txt @@ -39,8 +39,5 @@ set( ) add_subdirectory(opengl) -################################# -# TODO: uncomment in follow-up PR -# add_subdirectory(scenegraph) -################################# +add_subdirectory(scenegraph) add_subdirectory(shaders) diff --git a/src/rendergraph/opengl/texture.cpp b/src/rendergraph/opengl/texture.cpp index 45550222944..88921bde9df 100644 --- a/src/rendergraph/opengl/texture.cpp +++ b/src/rendergraph/opengl/texture.cpp @@ -1,7 +1,4 @@ #include "rendergraph/texture.h" -#include -#include -#include "rendergraph/assert.h" #include #include diff --git a/src/soundio/soundmanager.cpp b/src/soundio/soundmanager.cpp index 273211831ca..5e3bd6f639d 100644 --- a/src/soundio/soundmanager.cpp +++ b/src/soundio/soundmanager.cpp @@ -162,7 +162,7 @@ void SoundManager::closeDevices(bool sleepAfterClosing) { #ifdef __LINUX__ // Sleep for 5 sec to allow asynchronously sound APIs like "pulse" to free // its resources as well - QThread::sleep(kSleepSecondsAfterClosingDevice); + // QThread::sleep(kSleepSecondsAfterClosingDevice); #endif } diff --git a/src/waveform/ivisualgainprovider.h b/src/waveform/ivisualgainprovider.h new file mode 100644 index 00000000000..2cba2e20d2b --- /dev/null +++ b/src/waveform/ivisualgainprovider.h @@ -0,0 +1,8 @@ +#pragma once + +#include "waveform/waveform.h" + +class IVisualGainProvider { + public: + virtual double getVisualGain(FilterIndex index) const = 0; +}; diff --git a/src/waveform/renderers/allshader/waveformrenderbeat.h b/src/waveform/renderers/allshader/waveformrenderbeat.h index f14520d8d39..2d572ccbdf1 100644 --- a/src/waveform/renderers/allshader/waveformrenderbeat.h +++ b/src/waveform/renderers/allshader/waveformrenderbeat.h @@ -27,6 +27,10 @@ class allshader::WaveformRenderBeat final void setup(const QDomNode& node, const SkinContext& skinContext) override; + void setup(const QColor& color) { + m_color = color; + } + // Virtuals for rendergraph::Node void preprocess() override; diff --git a/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp b/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp index b0c15678701..5ed26e24412 100644 --- a/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp +++ b/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp @@ -1,9 +1,14 @@ #include "waveform/renderers/allshader/waveformrendererendoftrack.h" #include +#include #include #include "control/controlproxy.h" +#include "rendergraph/geometry.h" +#include "rendergraph/material/rgbamaterial.h" +#include "rendergraph/vertexupdaters/rgbavertexupdater.h" +#include "util/colorcomponents.h" #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/waveformwidgetfactory.h" #include "widget/wskincolor.h" @@ -11,19 +16,26 @@ namespace { constexpr int kBlinkingPeriodMillis = 1000; -constexpr float positionArray[] = {-1.f, -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f}; -constexpr float verticalGradientArray[] = {1.f, 1.f, -1.f, -1.f}; -constexpr float horizontalGradientArray[] = {-1.f, 1.f, -1.f, 1.f}; } // anonymous namespace +using namespace rendergraph; + namespace allshader { WaveformRendererEndOfTrack::WaveformRendererEndOfTrack( WaveformWidgetRenderer* waveformWidget) - : WaveformRenderer(waveformWidget), + : ::WaveformRendererAbstract(waveformWidget), m_pEndOfTrackControl(nullptr), m_pTimeRemainingControl(nullptr) { + initForRectangles(0); + setUsePreprocess(true); +} + +void WaveformRendererEndOfTrack::draw(QPainter* painter, QPaintEvent* event) { + Q_UNUSED(painter); + Q_UNUSED(event); + DEBUG_ASSERT(false); } bool WaveformRendererEndOfTrack::init() { @@ -37,73 +49,64 @@ bool WaveformRendererEndOfTrack::init() { return true; } -void WaveformRendererEndOfTrack::setup(const QDomNode& node, const SkinContext& context) { +void WaveformRendererEndOfTrack::setup(const QDomNode& node, const SkinContext& skinContext) { m_color = QColor(200, 25, 20); - const QString endOfTrackColorName = context.selectString(node, "EndOfTrackColor"); + const QString endOfTrackColorName = skinContext.selectString(node, "EndOfTrackColor"); if (!endOfTrackColorName.isNull()) { m_color = QColor(endOfTrackColorName); m_color = WSkinColor::getCorrectColor(m_color); } } -void WaveformRendererEndOfTrack::initializeGL() { - m_shader.init(); -} - -void WaveformRendererEndOfTrack::fillWithGradient(QColor color) { - const int colorLocation = m_shader.colorLocation(); - const int positionLocation = m_shader.positionLocation(); - const int gradientLocation = m_shader.gradientLocation(); - - m_shader.bind(); - m_shader.enableAttributeArray(positionLocation); - m_shader.enableAttributeArray(gradientLocation); - - m_shader.setUniformValue(colorLocation, color); - - m_shader.setAttributeArray( - positionLocation, GL_FLOAT, positionArray, 2); - m_shader.setAttributeArray(gradientLocation, - GL_FLOAT, - m_waveformRenderer->getOrientation() == Qt::Vertical - ? verticalGradientArray - : horizontalGradientArray, - 1); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - m_shader.disableAttributeArray(positionLocation); - m_shader.disableAttributeArray(gradientLocation); - m_shader.release(); +void WaveformRendererEndOfTrack::preprocess() { + if (!preprocessInner()) { + geometry().allocate(0); + markDirtyGeometry(); + } } -void WaveformRendererEndOfTrack::paintGL() { +bool WaveformRendererEndOfTrack::preprocessInner() { if (!m_pEndOfTrackControl->toBool()) { - return; + return false; } const int elapsed = m_timer.elapsed().toIntegerMillis() % kBlinkingPeriodMillis; - const double blinkIntensity = (double)(2 * abs(elapsed - kBlinkingPeriodMillis / 2)) / + const double blinkIntensity = + static_cast( + 2 * std::abs(elapsed - kBlinkingPeriodMillis / 2)) / kBlinkingPeriodMillis; const double remainingTime = m_pTimeRemainingControl->get(); - const double remainingTimeTriggerSeconds = - WaveformWidgetFactory::instance()->getEndOfTrackWarningTime(); - const double criticalIntensity = (remainingTimeTriggerSeconds - remainingTime) / - remainingTimeTriggerSeconds; + const double criticalIntensity = (m_remainingTimeTriggerSeconds - remainingTime) / + m_remainingTimeTriggerSeconds; - const double alpha = criticalIntensity * blinkIntensity; + const double alpha = std::clamp(criticalIntensity * blinkIntensity, 0.0, 1.0); - if (alpha != 0.0) { - QColor color = m_color; - color.setAlphaF(static_cast(alpha)); + QSizeF size(m_waveformRenderer->getWidth(), m_waveformRenderer->getHeight()); + float r, g, b, a; + getRgbF(m_color, &r, &g, &b, &a); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + const float posx0 = 0.f; + const float posx1 = static_cast(size.width()) / 2.f; + const float posx2 = static_cast(size.width()); + const float posy1 = 0.f; + const float posy2 = static_cast(size.height()); - fillWithGradient(color); - } + float minAlpha = 0.5f * static_cast(alpha); + float maxAlpha = 0.83f * static_cast(alpha); + + geometry().allocate(6 * 2); + RGBAVertexUpdater vertexUpdater{geometry().vertexDataAs()}; + vertexUpdater.addRectangleHGradient( + {posx0, posy1}, {posx1, posy2}, {r, g, b, minAlpha}, {r, g, b, minAlpha}); + vertexUpdater.addRectangleHGradient( + {posx1, posy1}, {posx2, posy2}, {r, g, b, minAlpha}, {r, g, b, maxAlpha}); + + markDirtyGeometry(); + markDirtyMaterial(); + + return true; } } // namespace allshader diff --git a/src/waveform/renderers/allshader/waveformrendererendoftrack.h b/src/waveform/renderers/allshader/waveformrendererendoftrack.h index 46b8a2a677b..5d89f088f9f 100644 --- a/src/waveform/renderers/allshader/waveformrendererendoftrack.h +++ b/src/waveform/renderers/allshader/waveformrendererendoftrack.h @@ -3,11 +3,11 @@ #include #include -#include "rendergraph/openglnode.h" -#include "shaders/endoftrackshader.h" +#include "rendergraph/geometrynode.h" +#include "rendergraph/opacitynode.h" #include "util/class.h" #include "util/performancetimer.h" -#include "waveform/renderers/allshader/waveformrenderer.h" +#include "waveform/renderers/waveformrendererabstract.h" class ControlProxy; class QDomNode; @@ -15,31 +15,39 @@ class SkinContext; namespace allshader { class WaveformRendererEndOfTrack; -} +} // namespace allshader class allshader::WaveformRendererEndOfTrack final - : public allshader::WaveformRenderer, - public rendergraph::OpenGLNode { + : public ::WaveformRendererAbstract, + public rendergraph::GeometryNode { public: explicit WaveformRendererEndOfTrack( WaveformWidgetRenderer* waveformWidget); + // Pure virtual from WaveformRendererAbstract, not used + void draw(QPainter* painter, QPaintEvent* event) override final; + void setup(const QDomNode& node, const SkinContext& skinContext) override; + void setup(const QColor& color, int endOfTrackWarningTime) { + m_color = color; + m_remainingTimeTriggerSeconds = endOfTrackWarningTime; + } + bool init() override; - void initializeGL() override; - void paintGL() override; + // Virtual for rendergraph::Node + void preprocess() override; private: - void fillWithGradient(QColor color); - - mixxx::EndOfTrackShader m_shader; std::unique_ptr m_pEndOfTrackControl; std::unique_ptr m_pTimeRemainingControl; QColor m_color; + int m_remainingTimeTriggerSeconds; PerformanceTimer m_timer; + bool preprocessInner(); + DISALLOW_COPY_AND_ASSIGN(WaveformRendererEndOfTrack); }; diff --git a/src/waveform/renderers/allshader/waveformrendererfiltered.cpp b/src/waveform/renderers/allshader/waveformrendererfiltered.cpp index 6a71f157db1..00e1b16fc77 100644 --- a/src/waveform/renderers/allshader/waveformrendererfiltered.cpp +++ b/src/waveform/renderers/allshader/waveformrendererfiltered.cpp @@ -12,8 +12,10 @@ using namespace rendergraph; namespace allshader { WaveformRendererFiltered::WaveformRendererFiltered( - WaveformWidgetRenderer* waveformWidget, bool bRgbStacked) - : WaveformRendererSignalBase(waveformWidget), + WaveformWidgetRenderer* waveformWidget, + bool bRgbStacked, + const IVisualGainProvider* visualGainProvider) + : WaveformRendererSignalBase(waveformWidget, visualGainProvider), m_bRgbStacked(bRgbStacked) { initForRectangles(0); setUsePreprocess(true); diff --git a/src/waveform/renderers/allshader/waveformrendererfiltered.h b/src/waveform/renderers/allshader/waveformrendererfiltered.h index 00ee18f7019..0f64a0a9a44 100644 --- a/src/waveform/renderers/allshader/waveformrendererfiltered.h +++ b/src/waveform/renderers/allshader/waveformrendererfiltered.h @@ -6,13 +6,15 @@ namespace allshader { class WaveformRendererFiltered; -} +} // namespace allshader class allshader::WaveformRendererFiltered final : public allshader::WaveformRendererSignalBase, public rendergraph::GeometryNode { public: - explicit WaveformRendererFiltered(WaveformWidgetRenderer* waveformWidget, bool rgbStacked); + explicit WaveformRendererFiltered(WaveformWidgetRenderer* waveformWidget, + bool rgbStacked, + const IVisualGainProvider* visualGainProvider = nullptr); // Pure virtual from WaveformRendererSignalBase, not used void onSetup(const QDomNode& node) override; diff --git a/src/waveform/renderers/allshader/waveformrendererhsv.cpp b/src/waveform/renderers/allshader/waveformrendererhsv.cpp index a626c548f32..db8af0c74f1 100644 --- a/src/waveform/renderers/allshader/waveformrendererhsv.cpp +++ b/src/waveform/renderers/allshader/waveformrendererhsv.cpp @@ -12,8 +12,9 @@ using namespace rendergraph; namespace allshader { -WaveformRendererHSV::WaveformRendererHSV(WaveformWidgetRenderer* waveformWidget) - : WaveformRendererSignalBase(waveformWidget) { +WaveformRendererHSV::WaveformRendererHSV(WaveformWidgetRenderer* waveformWidget, + const IVisualGainProvider* visualGainProvider) + : WaveformRendererSignalBase(waveformWidget, visualGainProvider) { initForRectangles(0); setUsePreprocess(true); } diff --git a/src/waveform/renderers/allshader/waveformrendererhsv.h b/src/waveform/renderers/allshader/waveformrendererhsv.h index 65acbcdaf09..16cba000c5e 100644 --- a/src/waveform/renderers/allshader/waveformrendererhsv.h +++ b/src/waveform/renderers/allshader/waveformrendererhsv.h @@ -6,13 +6,14 @@ namespace allshader { class WaveformRendererHSV; -} +} // namespace allshader class allshader::WaveformRendererHSV final : public allshader::WaveformRendererSignalBase, public rendergraph::GeometryNode { public: - explicit WaveformRendererHSV(WaveformWidgetRenderer* waveformWidget); + explicit WaveformRendererHSV(WaveformWidgetRenderer* waveformWidget, + const IVisualGainProvider* visualGainProvider = nullptr); // Pure virtual from WaveformRendererSignalBase, not used void onSetup(const QDomNode& node) override; diff --git a/src/waveform/renderers/allshader/waveformrendererpreroll.h b/src/waveform/renderers/allshader/waveformrendererpreroll.h index eb28459ef96..970f81c7262 100644 --- a/src/waveform/renderers/allshader/waveformrendererpreroll.h +++ b/src/waveform/renderers/allshader/waveformrendererpreroll.h @@ -29,6 +29,10 @@ class allshader::WaveformRendererPreroll final void setup(const QDomNode& node, const SkinContext& skinContext) override; + void setup(const QColor& color) { + m_color = color; + } + // Virtual for rendergraph::Node void preprocess() override; diff --git a/src/waveform/renderers/allshader/waveformrendererrgb.cpp b/src/waveform/renderers/allshader/waveformrendererrgb.cpp index 4d71108f10c..40b92d28e50 100644 --- a/src/waveform/renderers/allshader/waveformrendererrgb.cpp +++ b/src/waveform/renderers/allshader/waveformrendererrgb.cpp @@ -3,6 +3,7 @@ #include "rendergraph/material/rgbmaterial.h" #include "rendergraph/vertexupdaters/rgbvertexupdater.h" #include "track/track.h" +#include "util/colorcomponents.h" #include "util/math.h" #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/waveform.h" @@ -19,14 +20,26 @@ inline float math_pow2(float x) { WaveformRendererRGB::WaveformRendererRGB(WaveformWidgetRenderer* waveformWidget, ::WaveformRendererAbstract::PositionSource type, - WaveformRendererSignalBase::Options options) - : WaveformRendererSignalBase(waveformWidget), + WaveformRendererSignalBase::Options options, + const IVisualGainProvider* visualGainProvider) + : WaveformRendererSignalBase(waveformWidget, visualGainProvider), m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip), m_options(options) { initForRectangles(0); setUsePreprocess(true); } +void WaveformRendererRGB::setup(const QColor& axesColor, + const QColor& lowColor, + const QColor& midColor, + const QColor& highColor) { + getRgbF(axesColor, &m_axesColor_r, &m_axesColor_g, &m_axesColor_b, &m_axesColor_a); + + getRgbF(lowColor, &m_rgbLowColor_r, &m_rgbLowColor_g, &m_rgbLowColor_b); + getRgbF(midColor, &m_rgbMidColor_r, &m_rgbMidColor_g, &m_rgbMidColor_b); + getRgbF(highColor, &m_rgbHighColor_r, &m_rgbHighColor_g, &m_rgbHighColor_b); +} + void WaveformRendererRGB::onSetup(const QDomNode&) { } diff --git a/src/waveform/renderers/allshader/waveformrendererrgb.h b/src/waveform/renderers/allshader/waveformrendererrgb.h index a829cf3af2f..acd06ec50f6 100644 --- a/src/waveform/renderers/allshader/waveformrendererrgb.h +++ b/src/waveform/renderers/allshader/waveformrendererrgb.h @@ -6,7 +6,7 @@ namespace allshader { class WaveformRendererRGB; -} +} // namespace allshader class allshader::WaveformRendererRGB final : public allshader::WaveformRendererSignalBase, @@ -15,11 +15,17 @@ class allshader::WaveformRendererRGB final explicit WaveformRendererRGB(WaveformWidgetRenderer* waveformWidget, ::WaveformRendererAbstract::PositionSource type = ::WaveformRendererAbstract::Play, - WaveformRendererSignalBase::Options options = WaveformRendererSignalBase::Option::None); + WaveformRendererSignalBase::Options options = WaveformRendererSignalBase::Option::None, + const IVisualGainProvider* visualGainProvider = nullptr); // Pure virtual from WaveformRendererSignalBase, not used void onSetup(const QDomNode& node) override; + void setup(const QColor& axesColor, + const QColor& lowColor, + const QColor& midColor, + const QColor& highColor); + bool supportsSlip() const override { return true; } diff --git a/src/waveform/renderers/allshader/waveformrenderersignalbase.cpp b/src/waveform/renderers/allshader/waveformrenderersignalbase.cpp index ea906dba69e..e426bd8316d 100644 --- a/src/waveform/renderers/allshader/waveformrenderersignalbase.cpp +++ b/src/waveform/renderers/allshader/waveformrenderersignalbase.cpp @@ -3,8 +3,8 @@ namespace allshader { WaveformRendererSignalBase::WaveformRendererSignalBase( - WaveformWidgetRenderer* waveformWidget) - : ::WaveformRendererSignalBase(waveformWidget) { + WaveformWidgetRenderer* waveformWidget, const IVisualGainProvider* visualGainProvider) + : ::WaveformRendererSignalBase(waveformWidget, visualGainProvider) { } void WaveformRendererSignalBase::draw(QPainter*, QPaintEvent*) { diff --git a/src/waveform/renderers/allshader/waveformrenderersignalbase.h b/src/waveform/renderers/allshader/waveformrenderersignalbase.h index 91dbbe8046c..2cc56abaf80 100644 --- a/src/waveform/renderers/allshader/waveformrenderersignalbase.h +++ b/src/waveform/renderers/allshader/waveformrenderersignalbase.h @@ -27,7 +27,8 @@ class allshader::WaveformRendererSignalBase : public ::WaveformRendererSignalBas static constexpr float m_maxValue{static_cast(std::numeric_limits::max())}; - explicit WaveformRendererSignalBase(WaveformWidgetRenderer* waveformWidget); + explicit WaveformRendererSignalBase(WaveformWidgetRenderer* waveformWidget, + const IVisualGainProvider* visualGainProvider); virtual bool supportsSlip() const { return false; diff --git a/src/waveform/renderers/allshader/waveformrenderersimple.cpp b/src/waveform/renderers/allshader/waveformrenderersimple.cpp index 67972f164d2..ad47f18a6e2 100644 --- a/src/waveform/renderers/allshader/waveformrenderersimple.cpp +++ b/src/waveform/renderers/allshader/waveformrenderersimple.cpp @@ -11,8 +11,10 @@ using namespace rendergraph; namespace allshader { -WaveformRendererSimple::WaveformRendererSimple(WaveformWidgetRenderer* waveformWidget) - : WaveformRendererSignalBase(waveformWidget) { +WaveformRendererSimple::WaveformRendererSimple( + WaveformWidgetRenderer* waveformWidget, + const IVisualGainProvider* visualGainProvider) + : WaveformRendererSignalBase(waveformWidget, visualGainProvider) { initForRectangles(0); setUsePreprocess(true); } diff --git a/src/waveform/renderers/allshader/waveformrenderersimple.h b/src/waveform/renderers/allshader/waveformrenderersimple.h index fe3cfb3f431..01aa0fbfe05 100644 --- a/src/waveform/renderers/allshader/waveformrenderersimple.h +++ b/src/waveform/renderers/allshader/waveformrenderersimple.h @@ -6,13 +6,14 @@ namespace allshader { class WaveformRendererSimple; -} +} // namespace allshader class allshader::WaveformRendererSimple final : public allshader::WaveformRendererSignalBase, public rendergraph::GeometryNode { public: - explicit WaveformRendererSimple(WaveformWidgetRenderer* waveformWidget); + explicit WaveformRendererSimple(WaveformWidgetRenderer* waveformWidget, + const IVisualGainProvider* visualGainProvider = nullptr); // Pure virtual from WaveformRendererSignalBase, not used void onSetup(const QDomNode& node) override; diff --git a/src/waveform/renderers/allshader/waveformrendererstem.cpp b/src/waveform/renderers/allshader/waveformrendererstem.cpp index e9cd8ac21ff..5c9b6716427 100644 --- a/src/waveform/renderers/allshader/waveformrendererstem.cpp +++ b/src/waveform/renderers/allshader/waveformrendererstem.cpp @@ -20,8 +20,9 @@ namespace allshader { WaveformRendererStem::WaveformRendererStem( WaveformWidgetRenderer* waveformWidget, - ::WaveformRendererAbstract::PositionSource type) - : WaveformRendererSignalBase(waveformWidget), + ::WaveformRendererAbstract::PositionSource type, + const IVisualGainProvider* visualGainProvider) + : WaveformRendererSignalBase(waveformWidget, visualGainProvider), m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip) { initForRectangles(0); setUsePreprocess(true); diff --git a/src/waveform/renderers/allshader/waveformrendererstem.h b/src/waveform/renderers/allshader/waveformrendererstem.h index 1b8881d0cc6..e264876e8f2 100644 --- a/src/waveform/renderers/allshader/waveformrendererstem.h +++ b/src/waveform/renderers/allshader/waveformrendererstem.h @@ -19,7 +19,8 @@ class allshader::WaveformRendererStem final public: explicit WaveformRendererStem(WaveformWidgetRenderer* waveformWidget, ::WaveformRendererAbstract::PositionSource type = - ::WaveformRendererAbstract::Play); + ::WaveformRendererAbstract::Play, + const IVisualGainProvider* visualGainProvider = nullptr); // Pure virtual from WaveformRendererSignalBase, not used void onSetup(const QDomNode& node) override; diff --git a/src/waveform/renderers/allshader/waveformrenderertextured.cpp b/src/waveform/renderers/allshader/waveformrenderertextured.cpp index 97ac6a48059..e3d9c0fdd92 100644 --- a/src/waveform/renderers/allshader/waveformrenderertextured.cpp +++ b/src/waveform/renderers/allshader/waveformrenderertextured.cpp @@ -31,8 +31,9 @@ WaveformRendererTextured::WaveformRendererTextured( WaveformWidgetRenderer* waveformWidget, ::WaveformWidgetType::Type t, ::WaveformRendererAbstract::PositionSource type, - WaveformRendererSignalBase::Options options) - : WaveformRendererSignalBase(waveformWidget), + WaveformRendererSignalBase::Options options, + const IVisualGainProvider* visualGainProvider) + : WaveformRendererSignalBase(waveformWidget, visualGainProvider), m_unitQuadListId(-1), m_textureId(0), m_textureRenderedWaveformCompletion(0), diff --git a/src/waveform/renderers/allshader/waveformrenderertextured.h b/src/waveform/renderers/allshader/waveformrenderertextured.h index ccd82ae5a67..6b92c4eceeb 100644 --- a/src/waveform/renderers/allshader/waveformrenderertextured.h +++ b/src/waveform/renderers/allshader/waveformrenderertextured.h @@ -30,7 +30,8 @@ class allshader::WaveformRendererTextured final : public QObject, ::WaveformRendererAbstract::PositionSource type = ::WaveformRendererAbstract::Play, WaveformRendererSignalBase::Options options = - WaveformRendererSignalBase::Option::None); + WaveformRendererSignalBase::Option::None, + const IVisualGainProvider* visualGainProvider = nullptr); ~WaveformRendererTextured() override; // override ::WaveformRendererSignalBase diff --git a/src/waveform/renderers/allshader/waveformrendermark.cpp b/src/waveform/renderers/allshader/waveformrendermark.cpp index b79574578ac..5bb626b90ea 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.cpp +++ b/src/waveform/renderers/allshader/waveformrendermark.cpp @@ -172,7 +172,27 @@ allshader::WaveformRenderMark::WaveformRenderMark( } } -void allshader::WaveformRenderMark::draw(QPainter*, QPaintEvent*) { +#ifdef __RENDERGRAPH_IS_SCENEGRAPH +void allshader::WaveformRenderMark::setup(const QColor& fgPlayColor, + const QColor& bgPlayColor, + bool untilMarkShowBeats, + bool untilMarkShowTime, + Qt::Alignment untilMarkAlign, + int untilMarkTextSize, + float untilMarkTextHeightLimit) { + m_fgPlayColor = fgPlayColor; + m_bgPlayColor = bgPlayColor; + m_untilMarkShowBeats = untilMarkShowBeats; + m_untilMarkShowTime = untilMarkShowTime; + m_untilMarkAlign = untilMarkAlign; + m_untilMarkTextSize = untilMarkTextSize; + m_untilMarkTextHeightLimit = untilMarkTextHeightLimit; +} +#endif + +void allshader::WaveformRenderMark::draw(QPainter* painter, QPaintEvent* event) { + Q_UNUSED(painter); + Q_UNUSED(event); DEBUG_ASSERT(false); } @@ -225,12 +245,12 @@ void allshader::WaveformRenderMark::update() { // from m_pMarkNodesParent and store each with their mark // (transferring ownership). Later in this function we move the // visible nodes back to m_pMarkNodesParent children. - while (auto pChild = m_pMarkNodesParent->firstChild()) { + while (auto* pChild = m_pMarkNodesParent->firstChild()) { auto pNode = m_pMarkNodesParent->detachChildNode(pChild); WaveformMarkNode* pWaveformMarkNode = static_cast(pNode.get()); // Determine its WaveformMark - auto pMark = pWaveformMarkNode->m_pOwner; - auto pGraphics = static_cast(pMark->m_pGraphics.get()); + auto* pMark = pWaveformMarkNode->m_pOwner; + auto* pGraphics = static_cast(pMark->m_pGraphics.get()); // Store the node with the WaveformMark pGraphics->attachNode(std::move(pNode)); } @@ -272,10 +292,11 @@ void allshader::WaveformRenderMark::update() { continue; } - auto pMarkGraphics = pMark->m_pGraphics.get(); - auto pMarkNodeGraphics = static_cast(pMarkGraphics); - if (!pMarkGraphics) // is this even possible? + auto* pMarkGraphics = pMark->m_pGraphics.get(); + auto* pMarkNodeGraphics = static_cast(pMarkGraphics); + if (!pMarkGraphics) { // is this even possible? continue; + } const float currentMarkPos = static_cast( m_waveformRenderer->transformSamplePositionInRendererWorld( @@ -364,14 +385,26 @@ void allshader::WaveformRenderMark::update() { {1.f, 1.f}); } +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + if (m_untilMarkShowBeats || m_untilMarkShowTime) +#else if (WaveformWidgetFactory::instance()->getUntilMarkShowBeats() || - WaveformWidgetFactory::instance()->getUntilMarkShowTime()) { + WaveformWidgetFactory::instance()->getUntilMarkShowTime()) +#endif + { updateUntilMark(playPosition, nextMarkPosition); updateDigitsNodeForUntilMark(roundToPixel(playMarkerPos + 20.f)); } } void allshader::WaveformRenderMark::updateDigitsNodeForUntilMark(float x) { +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + const bool untilMarkShowBeats = m_untilMarkShowBeats; + const bool untilMarkShowTime = m_untilMarkShowTime; + const auto untilMarkAlign = m_untilMarkAlign; + const auto untilMarkTextPointSize = m_untilMarkTextSize; + const auto untilMarkTextHeightLimit = m_untilMarkTextHeightLimit; +#else const bool untilMarkShowBeats = WaveformWidgetFactory::instance()->getUntilMarkShowBeats(); const bool untilMarkShowTime = WaveformWidgetFactory::instance()->getUntilMarkShowTime(); const auto untilMarkAlign = WaveformWidgetFactory::instance()->getUntilMarkAlign(); @@ -382,6 +415,7 @@ void allshader::WaveformRenderMark::updateDigitsNodeForUntilMark(float x) { WaveformWidgetFactory::instance() ->getUntilMarkTextHeightLimit(); // proportion of waveform // height +#endif const auto untilMarkMaxHeightForText = getMaxHeightForText(untilMarkTextHeightLimit); m_pDigitsRenderNode->updateTexture(m_waveformRenderer->getContext(), @@ -465,8 +499,13 @@ void allshader::WaveformRenderMark::updatePlayPosMarkTexture(rendergraph::Contex painter.setWorldMatrixEnabled(false); +#ifdef __RENDERGRAPH_IS_OPENGL const QColor fgColor{m_waveformRenderer->getWaveformSignalColors()->getPlayPosColor()}; const QColor bgColor{m_waveformRenderer->getWaveformSignalColors()->getBgColor()}; +#else + const QColor& fgColor = m_fgPlayColor; + const QColor& bgColor = m_bgPlayColor; +#endif // draw dim outlines to increase playpos/waveform contrast painter.setPen(bgColor); @@ -526,7 +565,7 @@ void allshader::WaveformRenderMark::updateMarkImage(WaveformMarkPointer pMark) { pMark->generateImage( m_waveformRenderer->getDevicePixelRatio())); } else { - auto pGraphics = static_cast(pMark->m_pGraphics.get()); + auto* pGraphics = static_cast(pMark->m_pGraphics.get()); pGraphics->updateTexture(m_waveformRenderer->getContext(), pMark->generateImage( m_waveformRenderer->getDevicePixelRatio())); diff --git a/src/waveform/renderers/allshader/waveformrendermark.h b/src/waveform/renderers/allshader/waveformrendermark.h index 511dbaa8993..7a38517dc41 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.h +++ b/src/waveform/renderers/allshader/waveformrendermark.h @@ -28,6 +28,16 @@ class allshader::WaveformRenderMark : public ::WaveformRenderMarkBase, // Pure virtual from WaveformRendererAbstract, not used void draw(QPainter* painter, QPaintEvent* event) override final; +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + void setup(const QColor& fgPlayColor, + const QColor& bgPlayColor, + bool untilMarkShowBeats, + bool untilMarkShowTime, + Qt::Alignment untilMarkAlign, + int untilMarkTextSize, + float untilMarkTextHeightLimit); +#endif + bool init() override; void update(); @@ -69,5 +79,16 @@ class allshader::WaveformRenderMark : public ::WaveformRenderMarkBase, DigitsRenderNode* m_pDigitsRenderNode{}; +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + QColor m_fgPlayColor; + QColor m_bgPlayColor; + + bool m_untilMarkShowBeats; + bool m_untilMarkShowTime; + Qt::Alignment m_untilMarkAlign; + int m_untilMarkTextSize; + float m_untilMarkTextHeightLimit; +#endif + DISALLOW_COPY_AND_ASSIGN(WaveformRenderMark); }; diff --git a/src/waveform/renderers/waveformmark.cpp b/src/waveform/renderers/waveformmark.cpp index e90f2f4e02b..8200a6e6a95 100644 --- a/src/waveform/renderers/waveformmark.cpp +++ b/src/waveform/renderers/waveformmark.cpp @@ -93,6 +93,75 @@ bool isShowUntilNextPositionControl(const QString& positionControl) { } // anonymous namespace +WaveformMark::WaveformMark(const QString& group, + QString positionControl, + QString visibilityControl, + QString textColor, + QString markAlign, + QString text, + QString pixmapPath, + QString iconPath, + QColor color, + int priority, + int hotCue, + const WaveformSignalColors& signalColors) + : m_textColor(textColor), + m_pixmapPath(pixmapPath), + m_iconPath(iconPath), + m_linePosition{}, + m_breadth{}, + m_level{}, + m_iPriority(priority), + m_iHotCue(hotCue), + m_showUntilNext{} { + QString endPositionControl; + QString typeControl; + if (hotCue != Cue::kNoHotCue) { + positionControl = "hotcue_" + QString::number(hotCue + 1) + "_position"; + endPositionControl = "hotcue_" + QString::number(hotCue + 1) + "_endposition"; + typeControl = "hotcue_" + QString::number(hotCue + 1) + "_type"; + m_showUntilNext = true; + } else { + m_showUntilNext = isShowUntilNextPositionControl(positionControl); + } + + if (!positionControl.isEmpty()) { + m_pPositionCO = std::make_unique(group, positionControl); + } + if (!endPositionControl.isEmpty()) { + m_pEndPositionCO = std::make_unique(group, endPositionControl); + m_pTypeCO = std::make_unique(group, typeControl); + } + + if (!visibilityControl.isEmpty()) { + ConfigKey key = ConfigKey::parseCommaSeparated(visibilityControl); + m_pVisibleCO = std::make_unique(key); + } + + if (!color.isValid()) { + // As a fallback, grab the color from the parent's AxesColor + // color = signalColors.getAxesColor(); + qDebug() << "Didn't get mark :" << color; + } else { + color = WSkinColor::getCorrectColor(color); + } + int dimBrightThreshold = signalColors.getDimBrightThreshold(); + setBaseColor(color, dimBrightThreshold); + + if (!m_textColor.isValid()) { + // Read the text color, otherwise use the parent's BgColor. + m_textColor = signalColors.getBgColor(); + qDebug() << "Didn't get mark , using parent's :" << m_textColor; + } + + m_align = decodeAlignmentFlags(markAlign, Qt::AlignBottom | Qt::AlignHCenter); + + // Hotcue text is set by the cue's label in the database, not by the skin. + if (hotCue == Cue::kNoHotCue) { + m_text = text; + } +} + WaveformMark::WaveformMark(const QString& group, const QDomNode& node, const SkinContext& context, diff --git a/src/waveform/renderers/waveformmark.h b/src/waveform/renderers/waveformmark.h index c40af8927d9..c02e3a01404 100644 --- a/src/waveform/renderers/waveformmark.h +++ b/src/waveform/renderers/waveformmark.h @@ -31,6 +31,20 @@ class WaveformMark { int priority, const WaveformSignalColors& signalColors, int hotCue = Cue::kNoHotCue); + + WaveformMark( + const QString& group, + QString positionControl, + QString visibilityControl, + QString textColor, + QString markAlign, + QString text, + QString pixmapPath, + QString iconPath, + QColor color, + int priority, + int hotCue = Cue::kNoHotCue, + const WaveformSignalColors& signalColors = {}); ~WaveformMark(); // Disable copying diff --git a/src/waveform/renderers/waveformmarkrange.cpp b/src/waveform/renderers/waveformmarkrange.cpp index 1df2c6c7979..4cc3d07a4ec 100644 --- a/src/waveform/renderers/waveformmarkrange.cpp +++ b/src/waveform/renderers/waveformmarkrange.cpp @@ -75,6 +75,66 @@ WaveformMarkRange::WaveformMarkRange( } } +WaveformMarkRange::WaveformMarkRange( + const QString& group, + const QColor& activeColor, + const QColor& disabledColor, + double enabledOpacity, + double disabledOpacity, + const QColor& durationTextColor, + const QString& startControl, + const QString& endControl, + const QString& enabledControl, + const QString& visibilityControl, + const QString& durationTextLocation) + : m_activeColor(activeColor), + m_disabledColor(disabledColor), + m_enabledOpacity(enabledOpacity), + m_disabledOpacity(disabledOpacity), + m_durationTextColor(durationTextColor) { + if (!startControl.isEmpty()) { + DEBUG_ASSERT(!m_markStartPointControl); // has not been created yet + m_markStartPointControl = std::make_unique(group, startControl); + } + if (!endControl.isEmpty()) { + DEBUG_ASSERT(!m_markEndPointControl); // has not been created yet + m_markEndPointControl = std::make_unique(group, endControl); + } + + if (!enabledControl.isEmpty()) { + DEBUG_ASSERT(!m_markEnabledControl); // has not been created yet + m_markEnabledControl = std::make_unique(group, enabledControl); + } + if (!visibilityControl.isEmpty()) { + DEBUG_ASSERT(!m_markVisibleControl); // has not been created yet + ConfigKey key = ConfigKey::parseCommaSeparated(visibilityControl); + m_markVisibleControl = std::make_unique(key); + } + + if (durationTextLocation == "before") { + m_durationTextLocation = DurationTextLocation::Before; + } else { + m_durationTextLocation = DurationTextLocation::After; + } + + m_activeColor = WSkinColor::getCorrectColor(m_activeColor); + + if (!m_disabledColor.isValid()) { + if (enabledControl.isEmpty()) { + m_disabledColor = QColor(Qt::transparent); + } else { + // Show warning only when there's no EnabledControl, + // like for intro & outro ranges. + QString rangeSuffix = QStringLiteral("_start_position"); + QString rangeName = QString(startControl).remove(rangeSuffix); + int gray = qGray(m_activeColor.rgb()); + m_disabledColor = QColor(gray, gray, gray); + qDebug() << "Didn't get DisabledColor for mark range" << rangeName + << "- using desaturated Color:" << m_disabledColor; + } + } +} + bool WaveformMarkRange::active() const { const double startValue = start(); const double endValue = end(); diff --git a/src/waveform/renderers/waveformmarkrange.h b/src/waveform/renderers/waveformmarkrange.h index 5ac530e86af..63c0fe42f53 100644 --- a/src/waveform/renderers/waveformmarkrange.h +++ b/src/waveform/renderers/waveformmarkrange.h @@ -24,6 +24,18 @@ class WaveformMarkRange { const QDomNode& node, const SkinContext& context, const WaveformSignalColors& signalColors); + WaveformMarkRange( + const QString& group, + const QColor& activeColor, + const QColor& disabledColor, + double enabledOpacity, + double disabledOpacity, + const QColor& durationTextColor, + const QString& startControl, + const QString& endControl, + const QString& enabledControl, + const QString& visibilityControl, + const QString& durationTextLocation); // This class is only moveable, but not copiable! WaveformMarkRange(WaveformMarkRange&&) = default; WaveformMarkRange(const WaveformMarkRange&) = delete; diff --git a/src/waveform/renderers/waveformmarkset.cpp b/src/waveform/renderers/waveformmarkset.cpp index 8fe24730146..0fa78e5e1e4 100644 --- a/src/waveform/renderers/waveformmarkset.cpp +++ b/src/waveform/renderers/waveformmarkset.cpp @@ -22,14 +22,21 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, bool hasDefaultMark = false; QDomNode child = node.firstChild(); - QDomNode defaultChild; + Seed defaultModel; int priority = 0; while (!child.isNull()) { if (child.nodeName() == "DefaultMark") { - m_pDefaultMark = WaveformMarkPointer(new WaveformMark( - group, child, context, --priority, signalColors)); + defaultModel = Seed{ + context.selectString(node, "Control"), + context.selectString(node, "VisibilityControl"), + context.selectString(node, "TextColor"), + context.selectString(node, "Align"), + context.selectString(node, "Text"), + context.selectString(node, "Pixmap"), + context.selectString(node, "Icon"), + context.selectString(node, "Color"), + }; hasDefaultMark = true; - defaultChild = child; } else if (child.nodeName() == "Mark") { WaveformMarkPointer pMark(new WaveformMark( group, child, context, --priority, signalColors)); @@ -39,7 +46,7 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, if (!controlItemSet.insert(item).second) { qWarning() << "WaveformRenderMark::setup - redefinition of" << item; } else { - m_marks.push_back(pMark); + addMark(pMark); if (pMark->getHotCue() >= 0) { m_hotCueMarks.insert(pMark->getHotCue(), pMark); } @@ -52,14 +59,46 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, // check if there is a default mark and compare declared // and to create all missing hot_cues if (hasDefaultMark) { - for (int i = 0; i < NUM_HOT_CUES; ++i) { - if (m_hotCueMarks.value(i).isNull()) { - //qDebug() << "WaveformRenderMark::setup - Automatic mark" << hotCueControlItem; - WaveformMarkPointer pMark(new WaveformMark( - group, defaultChild, context, i, signalColors, i)); - m_marks.push_front(pMark); - m_hotCueMarks.insert(pMark->getHotCue(), pMark); - } + setDefault(group, defaultModel, signalColors); + } +} + +void WaveformMarkSet::setDefault(const QString& group, + const Seed& model, + const WaveformSignalColors& signalColors) { + m_pDefaultMark = WaveformMarkPointer(new WaveformMark( + + group, + model.positionControl, + model.visibilityControl, + model.textColor, + model.markAlign, + model.text, + model.pixmapPath, + model.iconPath, + model.color, + 0, + Cue::kNoHotCue, + signalColors)); + for (int i = 0; i < NUM_HOT_CUES; ++i) { + if (m_hotCueMarks.value(i).isNull()) { + // qDebug() << "WaveformRenderMark::setup - Automatic mark" << hotCueControlItem; + WaveformMarkPointer pMark(new WaveformMark( + + group, + model.positionControl, + model.visibilityControl, + model.textColor, + model.markAlign, + model.text, + model.pixmapPath, + model.iconPath, + model.color, + i, + i, + signalColors)); + m_marks.push_front(pMark); + m_hotCueMarks.insert(pMark->getHotCue(), pMark); } } } diff --git a/src/waveform/renderers/waveformmarkset.h b/src/waveform/renderers/waveformmarkset.h index 2ab37568f2f..d6e5b0d158b 100644 --- a/src/waveform/renderers/waveformmarkset.h +++ b/src/waveform/renderers/waveformmarkset.h @@ -11,6 +11,17 @@ // rendered. class WaveformMarkSet { public: + struct Seed { + QString positionControl; + QString visibilityControl; + QString textColor; + QString markAlign; + QString text; + QString pixmapPath; + QString iconPath; + QColor color; + }; + WaveformMarkSet(); virtual ~WaveformMarkSet(); @@ -67,11 +78,20 @@ class WaveformMarkSet { void setBreadth(float breadth); - private: void clear() { m_marks.clear(); m_marksToRender.clear(); } + + void addMark(WaveformMarkPointer pMark) { + m_marks.push_back(pMark); + } + + void setDefault(const QString& group, + const Seed& model, + const WaveformSignalColors& signalColors = {}); + + private: WaveformMarkPointer m_pDefaultMark; QList m_marks; // List of visible WaveformMarks sorted by the order they appear in the track diff --git a/src/waveform/renderers/waveformrendererabstract.h b/src/waveform/renderers/waveformrendererabstract.h index 7fc2b78529d..9d4f84a629e 100644 --- a/src/waveform/renderers/waveformrendererabstract.h +++ b/src/waveform/renderers/waveformrendererabstract.h @@ -6,6 +6,10 @@ QT_FORWARD_DECLARE_CLASS(QDomNode) QT_FORWARD_DECLARE_CLASS(QPaintEvent) QT_FORWARD_DECLARE_CLASS(QPainter) +namespace rendergraph { +class Node; +} + class SkinContext; class WaveformWidgetRenderer; diff --git a/src/waveform/renderers/waveformrenderersignalbase.cpp b/src/waveform/renderers/waveformrenderersignalbase.cpp index 2bed1ae6e29..b1584005e22 100644 --- a/src/waveform/renderers/waveformrenderersignalbase.cpp +++ b/src/waveform/renderers/waveformrenderersignalbase.cpp @@ -2,6 +2,8 @@ #include "control/controlproxy.h" #include "util/colorcomponents.h" +#include "waveform/ivisualgainprovider.h" +#include "waveform/waveform.h" #include "waveform/waveformwidgetfactory.h" #include "waveformwidgetrenderer.h" @@ -10,7 +12,8 @@ const QString kEffectGroupFormat = QStringLiteral("[EqualizerRack1_%1_Effect1]") } // namespace WaveformRendererSignalBase::WaveformRendererSignalBase( - WaveformWidgetRenderer* waveformWidgetRenderer) + WaveformWidgetRenderer* waveformWidgetRenderer, + const IVisualGainProvider* visualGainProvider) : WaveformRendererAbstract(waveformWidgetRenderer), m_pEQEnabled(nullptr), m_pLowFilterControlObject(nullptr), @@ -46,7 +49,10 @@ WaveformRendererSignalBase::WaveformRendererSignalBase( m_rgbMidColor_b(0), m_rgbHighColor_r(0), m_rgbHighColor_g(0), - m_rgbHighColor_b(0) { + m_rgbHighColor_b(0), + m_visualGainProvider(visualGainProvider != nullptr + ? visualGainProvider + : WaveformWidgetFactory::instance()) { } WaveformRendererSignalBase::~WaveformRendererSignalBase() { @@ -174,10 +180,11 @@ void WaveformRendererSignalBase::getGains(float* pAllGain, float* pLowGain, float* pMidGain, float* pHighGain) { - WaveformWidgetFactory* factory = WaveformWidgetFactory::instance(); if (pAllGain) { - *pAllGain = static_cast(m_waveformRenderer->getGain(applyCompensation)) * - static_cast(factory->getVisualGain(WaveformWidgetFactory::All)); + *pAllGain = static_cast( + m_waveformRenderer->getGain(applyCompensation)) * + static_cast(m_visualGainProvider->getVisualGain( + FilterIndex::AllChannel)); ; } @@ -196,11 +203,11 @@ void WaveformRendererSignalBase::getGains(float* pAllGain, } lowGain *= static_cast( - factory->getVisualGain(WaveformWidgetFactory::Low)); + m_visualGainProvider->getVisualGain(FilterIndex::Low)); midGain *= static_cast( - factory->getVisualGain(WaveformWidgetFactory::Mid)); + m_visualGainProvider->getVisualGain(FilterIndex::Mid)); highGain *= static_cast( - factory->getVisualGain(WaveformWidgetFactory::High)); + m_visualGainProvider->getVisualGain(FilterIndex::High)); if (m_pLowKillControlObject && m_pLowKillControlObject->get() > 0.0) { lowGain = 0; diff --git a/src/waveform/renderers/waveformrenderersignalbase.h b/src/waveform/renderers/waveformrenderersignalbase.h index 741f4ff4d61..898f6eedf07 100644 --- a/src/waveform/renderers/waveformrenderersignalbase.h +++ b/src/waveform/renderers/waveformrenderersignalbase.h @@ -2,22 +2,33 @@ #include "skin/legacy/skincontext.h" #include "util/span.h" +#include "waveform/ivisualgainprovider.h" +#include "waveform/waveform.h" #include "waveformrendererabstract.h" class ControlProxy; class WaveformSignalColors; +class IVisualGainProvider; class WaveformRendererSignalBase : public WaveformRendererAbstract { public: - explicit WaveformRendererSignalBase(WaveformWidgetRenderer* waveformWidgetRenderer); - virtual ~WaveformRendererSignalBase(); + explicit WaveformRendererSignalBase( + WaveformWidgetRenderer* waveformWidgetRenderer, + const IVisualGainProvider* visualGainProvider = nullptr); + virtual ~WaveformRendererSignalBase(); - virtual bool init(); - virtual void setup(const QDomNode& node, const SkinContext& context); + virtual bool init(); + virtual void setup(const QDomNode& node, const SkinContext& context); - virtual bool onInit() {return true;} + virtual bool onInit() { + return true; + } virtual void onSetup(const QDomNode &node) = 0; + void setVisualGainProvider(IVisualGainProvider* provider) { + m_visualGainProvider = provider; + } + protected: void deleteControls(); @@ -51,4 +62,6 @@ class WaveformRendererSignalBase : public WaveformRendererAbstract { float m_rgbLowFilteredColor_r, m_rgbLowFilteredColor_g, m_rgbLowFilteredColor_b; float m_rgbMidFilteredColor_r, m_rgbMidFilteredColor_g, m_rgbMidFilteredColor_b; float m_rgbHighFilteredColor_r, m_rgbHighFilteredColor_g, m_rgbHighFilteredColor_b; + + const IVisualGainProvider* m_visualGainProvider; }; diff --git a/src/waveform/renderers/waveformrendermarkbase.h b/src/waveform/renderers/waveformrendermarkbase.h index 8efc35aebd4..bc242b9aa2f 100644 --- a/src/waveform/renderers/waveformrendermarkbase.h +++ b/src/waveform/renderers/waveformrendermarkbase.h @@ -24,6 +24,18 @@ class WaveformRenderMarkBase : public QObject, public WaveformRendererAbstract { void onResize() override; + void clearMarks() { + m_marks.clear(); + } + + void setDefaultMark(const QString& group, const WaveformMarkSet::Seed& model) { + m_marks.setDefault(group, model); + } + + void addMark(WaveformMarkPointer pMark) { + m_marks.addMark(pMark); + } + public slots: // Called when the loaded track's cues are added, deleted or modified and // when a new track is loaded. diff --git a/src/waveform/renderers/waveformwidgetrenderer.cpp b/src/waveform/renderers/waveformwidgetrenderer.cpp index 7e50cf35741..a4ff34e4196 100644 --- a/src/waveform/renderers/waveformwidgetrenderer.cpp +++ b/src/waveform/renderers/waveformwidgetrenderer.cpp @@ -5,9 +5,12 @@ #include "control/controlproxy.h" #include "track/track.h" +#include "util/assert.h" #include "util/math.h" +#include "waveform/isynctimeprovider.h" #include "waveform/renderers/waveformrendererabstract.h" #include "waveform/visualplayposition.h" +#include "waveform/vsyncthread.h" #include "waveform/waveform.h" const double WaveformWidgetRenderer::s_waveformMinZoom = 1.0; @@ -83,7 +86,26 @@ WaveformWidgetRenderer::~WaveformWidgetRenderer() { } bool WaveformWidgetRenderer::init() { - //qDebug() << "WaveformWidgetRenderer::init, m_group=" << m_group; + m_trackPixelCount = 0.0; + m_visualSamplePerPixel = 1.0; + m_audioSamplePerPixel = 1.0; + m_totalVSamples = 0; + m_gain = 1.0; + m_trackSamples = 0.0; + + for (int type = ::WaveformRendererAbstract::Play; + type <= ::WaveformRendererAbstract::Slip; + type++) { + m_firstDisplayedPosition[type] = 0.0; + m_lastDisplayedPosition[type] = 0.0; + m_posVSample[type] = 0.0; + m_pos[type] = -1.0; // disable renderers + m_truePosSample[type] = -1.0; + } + + VERIFY_OR_DEBUG_ASSERT(!m_group.isEmpty()) { + return false; + } m_visualPlayPosition = VisualPlayPosition::getVisualPlayPosition(m_group); @@ -102,7 +124,7 @@ bool WaveformWidgetRenderer::init() { return true; } -void WaveformWidgetRenderer::onPreRender(VSyncThread* vsyncThread) { +void WaveformWidgetRenderer::onPreRender(ISyncTimeProvider* vsyncThread) { if (m_passthroughEnabled) { // disables renderers in draw() for (int type = ::WaveformRendererAbstract::Play; diff --git a/src/waveform/renderers/waveformwidgetrenderer.h b/src/waveform/renderers/waveformwidgetrenderer.h index 42d02f49e0c..c0c40cecd5e 100644 --- a/src/waveform/renderers/waveformwidgetrenderer.h +++ b/src/waveform/renderers/waveformwidgetrenderer.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "track/track_decl.h" #include "util/class.h" #include "waveform/renderers/waveformmark.h" @@ -11,7 +13,7 @@ class ControlProxy; class VisualPlayPosition; -class VSyncThread; +class ISyncTimeProvider; class QPainter; class WaveformRendererAbstract; @@ -32,20 +34,25 @@ class WaveformWidgetRenderer { }; public: - explicit WaveformWidgetRenderer(const QString& group); + explicit WaveformWidgetRenderer(const QString& group = {}); virtual ~WaveformWidgetRenderer(); bool init(); virtual bool onInit() {return true;} void setup(const QDomNode& node, const SkinContext& context); - void onPreRender(VSyncThread* vsyncThread); + void onPreRender(ISyncTimeProvider* vsyncThread); void draw(QPainter* painter, QPaintEvent* event); const QString& getGroup() const { return m_group; } + virtual void setGroup(const QString& group) { + m_group = group; + init(); + } + const TrackPointer& getTrackInfo() const { return m_pTrack; } @@ -119,7 +126,7 @@ class WaveformWidgetRenderer { int getTotalVSample() const { return m_totalVSamples; } - double getZoomFactor() const { + double getZoom() const { return m_zoomFactor; } double getGain(bool applyCompensation) const { @@ -183,6 +190,11 @@ class WaveformWidgetRenderer { #ifdef __STEM__ void selectStem(mixxx::StemChannelSelection stemMask); #endif + + void addRenderer(WaveformRendererAbstract* renderer) { + m_rendererStack.push_back(renderer); + } + void setTrack(TrackPointer track); void setMarkPositions(const QList& markPositions) { m_markPositions = markPositions; @@ -214,7 +226,7 @@ class WaveformWidgetRenderer { } protected: - const QString m_group; + QString m_group; TrackPointer m_pTrack; #ifdef __STEM__ uint m_selectedStems; diff --git a/src/waveform/waveform.h b/src/waveform/waveform.h index fc415e31db6..be78decc9ce 100644 --- a/src/waveform/waveform.h +++ b/src/waveform/waveform.h @@ -12,7 +12,11 @@ #include "util/class.h" #include "util/compatibility/qmutex.h" -enum FilterIndex { Low = 0, Mid = 1, High = 2, FilterCount = 3}; +enum FilterIndex { AllChannel = 0, + Low = 1, + Mid = 2, + High = 3, + FilterCount = 4 }; enum ChannelIndex { Left = 0, Right = 1, ChannelCount = 2}; struct WaveformFilteredData { diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp index 467060ebcce..a2fe18635c6 100644 --- a/src/waveform/waveformwidgetfactory.cpp +++ b/src/waveform/waveformwidgetfactory.cpp @@ -1,5 +1,7 @@ #include "waveform/waveformwidgetfactory.h" +#include "waveform/waveform.h" + #ifdef MIXXX_USE_QOPENGL #include #include @@ -113,7 +115,7 @@ WaveformWidgetFactory::WaveformWidgetFactory() m_frameCnt(0), m_actualFrameRate(0), m_playMarkerPosition(WaveformWidgetRenderer::s_defaultPlayMarkerPosition) { - m_visualGain[All] = 1.0; + m_visualGain[AllChannel] = 1.0; m_visualGain[Low] = 1.0; m_visualGain[Mid] = 1.0; m_visualGain[High] = 1.0; @@ -609,7 +611,7 @@ bool WaveformWidgetFactory::setWidgetTypeFromHandle(int handleIndex, bool force) WaveformWidgetAbstract* previousWidget = holder.m_waveformWidget; TrackPointer pTrack = previousWidget->getTrackInfo(); //previousWidget->hold(); - double previousZoom = previousWidget->getZoomFactor(); + double previousZoom = previousWidget->getZoom(); double previousPlayMarkerPosition = previousWidget->getPlayMarkerPosition(); int previousbeatgridAlpha = previousWidget->getBeatGridAlpha(); delete previousWidget; @@ -656,7 +658,7 @@ void WaveformWidgetFactory::setZoomSync(bool sync) { return; } - double refZoom = m_waveformWidgetHolders[0].m_waveformWidget->getZoomFactor(); + double refZoom = m_waveformWidgetHolders[0].m_waveformWidget->getZoom(); for (const auto& holder : std::as_const(m_waveformWidgetHolders)) { holder.m_waveformViewer->setZoom(refZoom); } @@ -678,7 +680,7 @@ void WaveformWidgetFactory::setVisualGain(FilterIndex index, double gain) { if (m_config) { m_config->set(ConfigKey("[Waveform]","VisualGain_" + QString::number(index)), QString::number(m_visualGain[index])); } - if (!m_overviewNormalized && index == FilterIndex::All) { + if (!m_overviewNormalized && index == FilterIndex::AllChannel) { emit overallVisualGainChanged(); } } @@ -711,7 +713,7 @@ void WaveformWidgetFactory::notifyZoomChange(WWaveformViewer* viewer) { if (pWaveformWidget == nullptr || !isZoomSync()) { return; } - double refZoom = pWaveformWidget->getZoomFactor(); + double refZoom = pWaveformWidget->getZoom(); for (const auto& holder : std::as_const(m_waveformWidgetHolders)) { if (holder.m_waveformViewer != viewer) { @@ -1172,9 +1174,12 @@ int WaveformWidgetFactory::findIndexOf(WWaveformViewer* viewer) const { return -1; } -void WaveformWidgetFactory::startVSync(GuiTick* pGuiTick, VisualsManager* pVisualsManager) { - const auto vSyncMode = static_cast( - m_config->getValue(ConfigKey("[Waveform]", "VSync"), 0)); +void WaveformWidgetFactory::startVSync( + GuiTick* pGuiTick, VisualsManager* pVisualsManager, bool useQML) { + const auto vSyncMode = useQML + ? VSyncThread::ST_TIMER + : static_cast( + m_config->getValue(ConfigKey("[Waveform]", "VSync"), 0)); m_pGuiTick = pGuiTick; m_pVisualsManager = pVisualsManager; @@ -1185,12 +1190,14 @@ void WaveformWidgetFactory::startVSync(GuiTick* pGuiTick, VisualsManager* pVisua #ifdef MIXXX_USE_QOPENGL if (m_vsyncThread->vsyncMode() == VSyncThread::ST_PLL) { WGLWidget* widget = SharedGLContext::getWidget(); - connect(widget->getOpenGLWindow(), - &QOpenGLWindow::frameSwapped, - this, - &WaveformWidgetFactory::slotFrameSwapped, - Qt::DirectConnection); - widget->show(); + if (widget) { + connect(widget->getOpenGLWindow(), + &QOpenGLWindow::frameSwapped, + this, + &WaveformWidgetFactory::slotFrameSwapped, + Qt::DirectConnection); + widget->show(); + } } #endif diff --git a/src/waveform/waveformwidgetfactory.h b/src/waveform/waveformwidgetfactory.h index c80585fd2e1..6e9ed4c0388 100644 --- a/src/waveform/waveformwidgetfactory.h +++ b/src/waveform/waveformwidgetfactory.h @@ -9,6 +9,7 @@ #include "skin/legacy/skincontext.h" #include "util/performancetimer.h" #include "util/singleton.h" +#include "waveform/ivisualgainprovider.h" #include "waveform/renderers/allshader/waveformrenderersignalbase.h" #include "waveform/widgets/waveformwidgettype.h" #include "waveform/widgets/waveformwidgetvars.h" @@ -104,12 +105,11 @@ class WaveformWidgetHolder { //######################################## -class WaveformWidgetFactory : public QObject, public Singleton { +class WaveformWidgetFactory : public QObject, + public IVisualGainProvider, + public Singleton { Q_OBJECT public: - //TODO merge this enum with the waveform analyzer one - enum FilterIndex { All = 0, Low = 1, Mid = 2, High = 3, FilterCount = 4}; - bool setConfig(UserSettingsPointer config); /// Creates the waveform widget using the type set with setWidgetType @@ -211,7 +211,7 @@ class WaveformWidgetFactory : public QObject, public Singleton #include "waveform/renderers/waveformwidgetrenderer.h" +#include "waveform/vsyncthread.h" WaveformWidgetAbstract::WaveformWidgetAbstract(const QString& group) : WaveformWidgetRenderer(group), diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 3b0ecf61ef4..168e428b102 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -738,7 +738,7 @@ void WOverview::drawWaveformPixmap(QPainter* pPainter) { diffGain = 255 - m_waveformPeak - 1; } else { const auto visualGain = static_cast( - widgetFactory->getVisualGain(WaveformWidgetFactory::All)); + widgetFactory->getVisualGain(FilterIndex::AllChannel)); diffGain = 255.0f - (255.0f / visualGain); } diff --git a/src/widget/wspinnybase.cpp b/src/widget/wspinnybase.cpp index a0baf58f70e..14414fecf32 100644 --- a/src/widget/wspinnybase.cpp +++ b/src/widget/wspinnybase.cpp @@ -15,6 +15,7 @@ #include "util/fpclassify.h" #include "vinylcontrol/vinylcontrolmanager.h" #include "waveform/visualplayposition.h" +#include "waveform/vsyncthread.h" #include "wimagestore.h" // The SampleBuffers format enables antialiasing. diff --git a/src/widget/wwaveformviewer.cpp b/src/widget/wwaveformviewer.cpp index f9e41713aca..9b390bcd936 100644 --- a/src/widget/wwaveformviewer.cpp +++ b/src/widget/wwaveformviewer.cpp @@ -195,9 +195,9 @@ void WWaveformViewer::mouseReleaseEvent(QMouseEvent* /*event*/) { void WWaveformViewer::wheelEvent(QWheelEvent* event) { if (m_waveformWidget) { if (event->angleDelta().y() > 0) { - onZoomChange(m_waveformWidget->getZoomFactor() / 1.05); + onZoomChange(m_waveformWidget->getZoom() / 1.05); } else if (event->angleDelta().y() < 0) { - onZoomChange(m_waveformWidget->getZoomFactor() * 1.05); + onZoomChange(m_waveformWidget->getZoom() * 1.05); } } }