From 61b365c18eaefe364214208fdfa0185416c1cd6a Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 1 Oct 2023 09:57:15 +0100 Subject: [PATCH] WIP - Partial drawing for S4 MK3 --- .../OnAirTrack.qml | 2 +- .../Traktor Kontrol S4 MK3.bulk.xml | 6 +- .../BPMIndicator.qml | 16 +- .../CustomCheckBox.qml | 43 - .../KeyIndicator.qml | 3 + .../Keyboard.qml | 4 + .../LoopSizeIndicator.qml | 8 +- .../OnAirTrack.qml | 6 +- .../Progression.qml | 8 +- .../TimeAndBeatloopIndicator.qml | 10 +- res/controllers/Traktor-Kontrol-S4-MK3.js | 2 +- .../TraktorKontrolS4MK3Screens.qml | 911 ++++++++++-------- src/controllers/controller.cpp | 6 +- src/controllers/dlgprefcontroller.cpp | 17 +- src/controllers/dlgprefcontroller.h | 3 + .../rendering/controllerrenderingengine.cpp | 23 +- .../legacy/controllerscriptenginelegacy.cpp | 2 - 17 files changed, 604 insertions(+), 466 deletions(-) delete mode 100755 res/controllers/Traktor-Kontrol-S4-MK3-Screens/CustomCheckBox.qml diff --git a/res/controllers/Dummy-Device-Default-Screen/OnAirTrack.qml b/res/controllers/Dummy-Device-Default-Screen/OnAirTrack.qml index c24e5905ef84..fea5b11c33bd 100644 --- a/res/controllers/Dummy-Device-Default-Screen/OnAirTrack.qml +++ b/res/controllers/Dummy-Device-Default-Screen/OnAirTrack.qml @@ -85,7 +85,7 @@ Item { onTriggered: { if (status == OnAirTrack.TimerStatus.Cooldown) { status += backward ? -1 : 1 - interval = 15 + interval = 10 } frame.x -= backward ? -1 : 1; if (-frame.x >= (text.text.length - 29) * 11) { diff --git a/res/controllers/Traktor Kontrol S4 MK3.bulk.xml b/res/controllers/Traktor Kontrol S4 MK3.bulk.xml index 9a8acc57c997..e1b0fac94089 100644 --- a/res/controllers/Traktor Kontrol S4 MK3.bulk.xml +++ b/res/controllers/Traktor Kontrol S4 MK3.bulk.xml @@ -3,15 +3,15 @@ S4 Mk3 screens A. Colombier - Dummy device screens + Test device screens - - + + diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/BPMIndicator.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/BPMIndicator.qml index 68164539af6b..aa40454f349a 100755 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/BPMIndicator.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/BPMIndicator.qml @@ -17,9 +17,11 @@ Rectangle { border.color: smallBoxBorder border.width: 2 + signal updated + Text { - id: inidcator - text: value.toFixed(2) + id: indicator + text: "-" font.pixelSize: 17 color: fontColor anchors.centerIn: parent @@ -28,7 +30,10 @@ Rectangle { group: root.group key: "bpm" onValueChanged: (value) => { - inidcator.text = value.toFixed(2); + const newValue = value.toFixed(2); + if (newValue === indicator.text) return; + indicator.text = newValue; + root.updated() } } } @@ -46,7 +51,10 @@ Rectangle { group: root.group key: "rateRange" onValueChanged: (value) => { - range.text = `-/+ \n${(value * 100).toFixed()}%`; + const newValue = `-/+ \n${(value * 100).toFixed()}%`; + if (range.text === newValue) return; + range.text = newValue; + root.updated(); } } } diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/CustomCheckBox.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/CustomCheckBox.qml deleted file mode 100755 index 14444a825274..000000000000 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/CustomCheckBox.qml +++ /dev/null @@ -1,43 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.14 -// import QtQuick.Controls.Styles 1.4 -import QtGraphicalEffects 1.0 - -CheckBox { - id: checkbox - checked: false - - property int box_width: 10 - property int box_height: box_width - property int box_radius: 3 - property int border_width: 1 - property color box_color: "white" - property color tick_color: "black" - property color border_color: "black" - property double tick_size_factor: 0.75 - property url tick_image: "qrc:/resources/accept.svg" - - width: box_width - - indicator: Rectangle { - anchors.verticalCenter: parent.verticalCenter - implicitWidth: box_width - implicitHeight: box_height - radius: box_radius - color: box_color - border.color: border_color - border.width: border_width - Image { - visible: checkbox.checked - anchors.centerIn: parent - width: parent.width * tick_size_factor - fillMode: Image.PreserveAspectFit - source: tick_image - ColorOverlay { - anchors.fill: parent - source: parent - color: tick_color - } - } - } -} diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/KeyIndicator.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/KeyIndicator.qml index 679c8c8b59bd..f5680d8ffac0 100755 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/KeyIndicator.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/KeyIndicator.qml @@ -102,12 +102,15 @@ Rectangle { border.width: 2 color: colorsMap[key] + signal updated Mixxx.ControlProxy { group: root.group key: "key" onValueChanged: (value) => { + if (value === root.key) return; root.key = value; + root.updated() } } diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Keyboard.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Keyboard.qml index 9e5bae8eee5f..d8da78c4794c 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Keyboard.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Keyboard.qml @@ -14,11 +14,15 @@ Item { property int key: -1 + signal updated + Mixxx.ControlProxy { group: root.group key: "key" onValueChanged: (value) => { + if (value === root.key) return; root.key = value; + root.updated() } } diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/LoopSizeIndicator.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/LoopSizeIndicator.qml index f78925af50f2..9c252df8d5b4 100755 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/LoopSizeIndicator.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/LoopSizeIndicator.qml @@ -15,6 +15,7 @@ Rectangle { property color loopOnFontColor: "black" property bool on: true + signal updated radius: 6 border.width: 2 @@ -31,7 +32,10 @@ Rectangle { group: root.group key: "beatloop_size" onValueChanged: (value) => { - indicator.text = (value < 1 ? `1/${1 / value}` : `${value}`); + const newValue = (value < 1 ? `1/${1 / value}` : `${value}`); + if (newValue === indicator.text) return; + indicator.text = newValue; + root.updated() } } } @@ -40,7 +44,9 @@ Rectangle { group: root.group key: "loop_enabled" onValueChanged: (value) => { + if (value === root.on) return; root.on = value; + root.updated() } } diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/OnAirTrack.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/OnAirTrack.qml index fea5b11c33bd..d25f51e8d827 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/OnAirTrack.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/OnAirTrack.qml @@ -16,6 +16,8 @@ Item { Backward } + signal updated + property string fullText property int index @@ -70,6 +72,7 @@ Item { if (text.text.trim() == "-") { text.text = trackLoadedControl.value ? `Unknown for ${root.group}` : qsTr("No Track Loaded") } + root.updated() } Timer { @@ -85,9 +88,10 @@ Item { onTriggered: { if (status == OnAirTrack.TimerStatus.Cooldown) { status += backward ? -1 : 1 - interval = 10 + interval = 15 } frame.x -= backward ? -1 : 1; + root.updated() if (-frame.x >= (text.text.length - 29) * 11) { backward = true status = OnAirTrack.TimerStatus.Cooldown diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Progression.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Progression.qml index be6faded44b2..772b5f50111a 100755 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Progression.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/Progression.qml @@ -12,12 +12,15 @@ Item { property real windowWidth: Window.width width: 0 + signal updated Mixxx.ControlProxy { group: root.group key: "track_loaded" onValueChanged: (value) => { + if (value === root.visible) return; root.visible = value + root.updated() } } @@ -25,7 +28,10 @@ Item { group: root.group key: "playposition" onValueChanged: (value) => { - root.width = value * (320 - 12); + const newValue = Math.round(value * (320 - 12)); + if (newValue === root.width) return; + root.width = newValue; + root.updated() } } diff --git a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/TimeAndBeatloopIndicator.qml b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/TimeAndBeatloopIndicator.qml index dcbe0e545722..2c60ed8e85b1 100755 --- a/res/controllers/Traktor-Kontrol-S4-MK3-Screens/TimeAndBeatloopIndicator.qml +++ b/res/controllers/Traktor-Kontrol-S4-MK3-Screens/TimeAndBeatloopIndicator.qml @@ -23,17 +23,22 @@ Rectangle { border.color: timeColor border.width: 2 color: timeColor + signal updated function update() { + let newValue = ""; if (root.mode === TimeAndBeatloopIndicator.Mode.RemainingTime) { var seconds = ((1.0 - progression.value) * duration.value); var mins = parseInt(seconds / 60).toString(); seconds = parseInt(seconds % 60).toString(); - indicator.text = `-${mins.padStart(2, '0')}:${seconds.padStart(2, '0')}`; + newValue = `-${mins.padStart(2, '0')}:${seconds.padStart(2, '0')}`; } else { - indicator.text = (beatjump.value < 1 ? `1/${1 / beatjump.value}` : `${beatjump.value}`); + newValue = (beatjump.value < 1 ? `1/${1 / beatjump.value}` : `${beatjump.value}`); } + if (newValue === indicator.text) return; + indicator.text = newValue; + root.updated() } Text { @@ -69,6 +74,7 @@ Rectangle { onValueChanged: (value) => { root.border.color = value ? 'red' : timeColor root.color = value ? 'red' : timeColor + root.updated() } } } diff --git a/res/controllers/Traktor-Kontrol-S4-MK3.js b/res/controllers/Traktor-Kontrol-S4-MK3.js index b8fbd1f1960f..c7dee6816b2a 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3.js +++ b/res/controllers/Traktor-Kontrol-S4-MK3.js @@ -2494,7 +2494,7 @@ class S4Mk3Deck extends Deck { const wheelOutput = new Uint8Array(40).fill(0); wheelOutput[0] = decks[0] - 1; - if (engine.getValue(this.group, "end_of_track") && WheelLedBlinkOnTrackEnd) { + if (engine.getValue(this.group, "end_of_track") && WheelLedBlinkOnTrackEnd && fractionOfTrack < 1) { wheelOutput[1] = wheelLEDmodes.ringFlash; } else { wheelOutput[1] = wheelLEDmodes.spot; diff --git a/res/controllers/TraktorKontrolS4MK3Screens.qml b/res/controllers/TraktorKontrolS4MK3Screens.qml index 9a24a9660f9f..2e07d64b5af2 100755 --- a/res/controllers/TraktorKontrolS4MK3Screens.qml +++ b/res/controllers/TraktorKontrolS4MK3Screens.qml @@ -15,7 +15,7 @@ import Mixxx.Controls 1.0 as MixxxControls // Import other project QML scripts import "Traktor-Kontrol-S4-MK3-Screens" as S4MK3 -Rectangle { +Item { id: root required property string screenId @@ -25,539 +25,662 @@ Rectangle { property string group: "[Channel1]" property var deckPlayer: Mixxx.PlayerManager.getPlayer(root.group) - color: "black" - antialiasing: true + property var _items_needing_redraw: new Set() + + Timer { + id: timer + } + function setTimeout(cb, delayTime) { + timer.interval = delayTime; + timer.repeat = false; + timer.triggered.connect(cb); + timer.start(); + } function init(controlerName, isDebug) { console.log(`Screen ${root.screenId} has started`) - // root.stat = DummyDeviceDefaultScreen.Stat.Running loader.sourceComponent = live - root.group = root.screenId === "rightdeck" ? "[Channel2]" : "[Channel1]" + group = screenId === "rightdeck" ? "[Channel2]" : "[Channel1]" onAir.update() } function shutdown() { console.log(`Screen ${root.screenId} is stopping`) loader.sourceComponent = splashoff - // root.stat = DummyDeviceDefaultScreen.Stat.Stopping } function transformFrame(input) { - const outputData = new ArrayBuffer(320*240*2 + 20); - const header = new Uint8Array(outputData, 0, 16); - const payload = new Uint8Array(outputData, 16, 320*240*2); - const footer = new Uint8Array(outputData, 320*240*2 + 16, 4); - header.fill(0) - footer.fill(0) - header[0] = 0x84; - header[2] = root.screenId === "leftdeck" ? 0 : 1; - header[3] = 0x21; - header[12] = 0x1; - header[13] = 0x40; - header[15] = 0xf0; - payload.set(new Uint8Array(input, 0, 320*240*2)) - footer[0] = 0x40; - // const debug = new Uint8Array(outputData, 0, 32); - // console.log(input.slice(0, 32)); + const areasToDraw = [] + let totalPixelToDraw = 0; + + if (!_items_needing_redraw.size) { // No redraw needed + return new ArrayBuffer(0); + } else if (_items_needing_redraw.has(root)) { // Full redraw needed + areasToDraw.push([0, 0, 320, 240]); + totalPixelToDraw += 320 * 240; + } else { // Partial redraw needed + _items_needing_redraw.forEach(function(item) { + const pos = item.mapToGlobal(0, 0) + console.log(`Redrawing item ${item}`) + let x = pos.x, y = pos.y, width = item.width, height = item.height; + areasToDraw.push([pos.x, pos.y, item.width, item.height]) + totalPixelToDraw += item.width * item.height; + }); + // Note: Some area could overlap and this could be optimised, but the cost of checking that every time vs. + // the optimisation for when it is happening is likely not worth it + } + _items_needing_redraw.clear() + + // console.log(`Redrawing ${totalPixelToDraw} the following region: ${areasToDraw}`) + + const screenIdx = screenId === "leftdeck" ? 0 : 1; + + const outputData = new ArrayBuffer(totalPixelToDraw*2 + 20*areasToDraw.length); + let offset = 0; + + for (const area of areasToDraw) { + const [x, y, width, height] = area; + const header = new Uint8Array(outputData, offset, 16); + const payload = new Uint8Array(outputData, offset + 16, width*height*2); + const footer = new Uint8Array(outputData, offset + width*height*2 + 16, 4); + + header.fill(0) + footer.fill(0) + header[0] = 0x84; + header[2] = screenIdx; + header[3] = 0x21; + + header[8] = x >> 8; + header[9] = x & 0xff; + header[10] = y >> 8; + header[11] = y & 0xff; + + header[12] = width >> 8; + header[13] = width & 0xff; + header[14] = height >> 8; + header[15] = height & 0xff; + + if (x === 0 && width === 320) { + payload.set(new Uint8Array(input, y * 320 * 2, width*height*2)); + } else { + for (let line = 0; line < height; line++) { + payload.set(new Uint8Array(input, ((y + line) * 320 + x) * 2, width*2), line*width*2); + } + } + footer[0] = 0x40; + footer[2] = screenIdx; + offset += width*height*2 + 20 + } + // console.log(`Generated ${offset} bytes to be sent`) return outputData; } - Item { - anchors.fill: parent + Mixxx.ControlProxy { + id: trackLoadedControl - Component { - id: splashoff - Rectangle { - anchors.fill: parent - color: "black" - - Image { - anchors.centerIn: parent - width: root.width*0.8 - height: root.height - fillMode: Image.PreserveAspectFit - source: "../images/templates/logo_mixxx.png" // loads cat.png - } + group: root.group + key: "track_loaded" + + onValueChanged: (value) => { + _items_needing_redraw.add(root) + } + } + + Component { + id: splashoff + Rectangle { + anchors.fill: parent + color: "black" + + Image { + anchors.centerIn: parent + width: root.width*0.8 + height: root.height + fillMode: Image.PreserveAspectFit + source: "../images/templates/logo_mixxx.png" // loads cat.png } } - Component { - id: live + } + Component { + id: live - Rectangle { - anchors.fill: parent - color: "black" + Rectangle { + anchors.fill: parent + color: "black" - function onRuntimeDataUpdate(data) { - console.log(`Received data on screen#${root.screenId} while currently bind to ${root.group}: ${JSON.stringify(data)}`); - if (typeof data === "object" && typeof data.group === "object" && data.group.length === 2 && typeof data.group[root.screenId] === "string" && root.group !== data.group[root.screenId]) { - root.group = data.group[root.screenId] - console.log(`Changed group for screen ${root.screenId} to ${root.group}`); - } - var shouldBeCompacted = false; - if (typeof data.zoomedWaveform === "object") { - shouldBeCompacted |= data.zoomedWaveform[root.group] - waveformZoomed.visible = data.zoomedWaveform[root.group] - } - if (typeof data.keyboardMode === "object") { - shouldBeCompacted |= data.keyboardMode[root.group] - keyboard.visible = !!data.keyboardMode[root.group] - } - deckInfo.state = shouldBeCompacted ? "compacted" : "" - if (typeof data.displayBeatloopSize === "object") { - timeIndicator.mode = data.displayBeatloopSize[root.screenId] ? S4MK3.TimeAndBeatloopIndicator.Mode.BeetjumpSize : S4MK3.TimeAndBeatloopIndicator.Mode.RemainingTime - timeIndicator.update() - } + function onRuntimeDataUpdate(data) { + console.log(`Received data on screen#${root.screenId} while currently bind to ${root.group}: ${JSON.stringify(data)}`); + if (typeof data === "object" && typeof data.group === "object" && data.group.length === 2 && typeof data.group[root.screenId] === "string" && root.group !== data.group[root.screenId]) { + root.group = data.group[root.screenId] + console.log(`Changed group for screen ${root.screenId} to ${root.group}`); } - - Component.onCompleted: { - // engine.onRuntimeDataUpdate(onRuntimeDataUpdate) - if (root.screenId === "rightdeck") { - root.group = "[Channel2]" - } - - onRuntimeDataUpdate({ - // zoomedWaveform: { - // "[Channel1]": true - // } - }) - - // root.deckPlayer.onWaveformLengthChanged((value) => { - // console.warn("Changed!!") - // }) - // root.deckPlayer.onWaveformTextureChanged((value) => { - // console.warn("Changed!!") - // }) - // root.deckPlayer.onWaveformTextureSizeChanged((value) => { - // console.warn("Changed!!") - // }) + var shouldBeCompacted = false; + if (typeof data.zoomedWaveform === "object") { + shouldBeCompacted |= data.zoomedWaveform[root.group] + waveformZoomed.visible = data.zoomedWaveform[root.group] + } + if (typeof data.keyboardMode === "object") { + shouldBeCompacted |= data.keyboardMode[root.group] + keyboard.visible = !!data.keyboardMode[root.group] + } + deckInfo.state = shouldBeCompacted ? "compacted" : "" + if (typeof data.displayBeatloopSize === "object") { + timeIndicator.mode = data.displayBeatloopSize[root.screenId] ? S4MK3.TimeAndBeatloopIndicator.Mode.BeetjumpSize : S4MK3.TimeAndBeatloopIndicator.Mode.RemainingTime + timeIndicator.update() } + } - ColumnLayout { - anchors.fill: parent - anchors.leftMargin: 6 - anchors.rightMargin: 6 - anchors.topMargin: 2 - anchors.bottomMargin: 6 - spacing: 6 + Component.onCompleted: { + // engine.onRuntimeDataUpdate(onRuntimeDataUpdate) + if (root.screenId === "rightdeck") { + root.group = "[Channel2]" + } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 36 - color: "transparent" + onRuntimeDataUpdate({ + // zoomedWaveform: { + // "[Channel1]": true + // } + }) + + // root.deckPlayer.onWaveformLengthChanged((value) => { + // console.warn("Changed!!") + // }) + // root.deckPlayer.onWaveformTextureChanged((value) => { + // console.warn("Changed!!") + // }) + // root.deckPlayer.onWaveformTextureSizeChanged((value) => { + // console.warn("Changed!!") + // }) + // setTimeout(() => {root._items_needing_redraw.add(this);}, 1000); + } - RowLayout { - anchors.fill: parent - spacing: 1 + ColumnLayout { + anchors.fill: parent + anchors.leftMargin: 6 + anchors.rightMargin: 6 + anchors.topMargin: 2 + anchors.bottomMargin: 6 + spacing: 6 + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 36 + color: "transparent" + + RowLayout { + anchors.fill: parent + spacing: 1 + + S4MK3.OnAirTrack { + id: onAir + group: root.group + Layout.fillWidth: true + Layout.fillHeight: true - S4MK3.OnAirTrack { - id: onAir - group: root.group - Layout.fillWidth: true - Layout.fillHeight: true + onUpdated: { + root._items_needing_redraw.add(this) } } } + } - // Indicator - Rectangle { - id: deckInfo + // Indicator + Rectangle { + id: deckInfo - Layout.fillWidth: true - Layout.preferredHeight: 105 - color: "transparent" + Layout.fillWidth: true + Layout.preferredHeight: 105 + color: "transparent" - GridLayout { - id: gridLayout - anchors.fill: parent - columnSpacing: 6 - rowSpacing: 6 - columns: 2 + GridLayout { + id: gridLayout + anchors.fill: parent + columnSpacing: 6 + rowSpacing: 6 + columns: 2 - // Section: Key - S4MK3.KeyIndicator { - id: keyIndicator - group: root.group - borderColor: smallBoxBorder + // Section: Key + S4MK3.KeyIndicator { + id: keyIndicator + group: root.group + borderColor: smallBoxBorder + + Layout.fillWidth: true + Layout.fillHeight: true - Layout.fillWidth: true - Layout.fillHeight: true + onUpdated: { + root._items_needing_redraw.add(this) } + } - // Section: Bpm - S4MK3.BPMIndicator { - id: bpmIndicator - group: root.group - borderColor: smallBoxBorder + // Section: Bpm + S4MK3.BPMIndicator { + id: bpmIndicator + group: root.group + borderColor: smallBoxBorder - Layout.fillWidth: true - Layout.fillHeight: true + Layout.fillWidth: true + Layout.fillHeight: true + + onUpdated: { + root._items_needing_redraw.add(this) } + } - // Section: Key - S4MK3.TimeAndBeatloopIndicator { - id: timeIndicator - group: root.group + // Section: Key + S4MK3.TimeAndBeatloopIndicator { + id: timeIndicator + group: root.group + + Layout.fillWidth: true + Layout.preferredHeight: 72 + timeColor: smallBoxBorder - Layout.fillWidth: true - Layout.preferredHeight: 72 - timeColor: smallBoxBorder + onUpdated: { + root._items_needing_redraw.add(this) } + } - // Section: Bpm - S4MK3.LoopSizeIndicator { - id: loopSizeIndicator - group: root.group + // Section: Bpm + S4MK3.LoopSizeIndicator { + id: loopSizeIndicator + group: root.group - Layout.fillWidth: true - Layout.preferredHeight: 72 + Layout.fillWidth: true + Layout.preferredHeight: 72 + + onUpdated: { + root._items_needing_redraw.add(this) } } - states: State { - name: "compacted" + } + states: State { + name: "compacted" - PropertyChanges { - target:deckInfo - Layout.preferredHeight: 28 - } - PropertyChanges { - target: gridLayout - columns: 4 - } - PropertyChanges { - target: bpmIndicator - state: "compacted" - } - PropertyChanges { - target: timeIndicator - Layout.preferredHeight: -1 - Layout.fillHeight: true - state: "compacted" - } - PropertyChanges { - target: loopSizeIndicator - Layout.preferredHeight: -1 - Layout.fillHeight: true - state: "compacted" - } + PropertyChanges { + target:deckInfo + Layout.preferredHeight: 28 + } + PropertyChanges { + target: gridLayout + columns: 4 + } + PropertyChanges { + target: bpmIndicator + state: "compacted" + } + PropertyChanges { + target: timeIndicator + Layout.preferredHeight: -1 + Layout.fillHeight: true + state: "compacted" + } + PropertyChanges { + target: loopSizeIndicator + Layout.preferredHeight: -1 + Layout.fillHeight: true + state: "compacted" } } - // Track progress - Item { - // Layout.preferredHeight: 40 - Layout.fillWidth: true - Layout.fillHeight: true - layer.enabled: true + onStateChanged: { + root._items_needing_redraw.add(root) + } + } - S4MK3.Progression { - id: progression - group: root.group + // Track progress + Item { + // Layout.preferredHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + layer.enabled: true - anchors.top: parent.top - anchors.left: parent.left - anchors.bottom: parent.bottom - } + S4MK3.Progression { + id: progression + group: root.group - MixxxControls.WaveformOverview { - id: waveform - group: root.group - anchors.fill: parent - channels: Mixxx.WaveformOverview.Channels.BothChannels - renderer: Mixxx.WaveformOverview.Renderer.RGB - colorHigh: 'white' - colorMid: 'blue' - colorLow: 'green' + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + + onUpdated: { + root._items_needing_redraw.add(waveform) } + } - // Hotcue - Repeater { - model: 8 + MixxxControls.WaveformOverview { + id: waveform + group: root.group + anchors.fill: parent + channels: Mixxx.WaveformOverview.Channels.BothChannels + renderer: Mixxx.WaveformOverview.Renderer.RGB + colorHigh: 'white' + colorMid: 'blue' + colorLow: 'green' + } - S4MK3.HotcuePoint { - required property int index + // Hotcue + Repeater { + model: 8 - Mixxx.ControlProxy { - id: samplesControl + S4MK3.HotcuePoint { + required property int index - group: root.group - key: "track_samples" - } + Mixxx.ControlProxy { + id: samplesControl - Mixxx.ControlProxy { - id: hotcueEnabled - group: root.group - key: `hotcue_${index}_status` - } + group: root.group + key: "track_samples" - Mixxx.ControlProxy { - id: hotcuePosition - group: root.group - key: `hotcue_${index}_position` + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) } - - anchors.top: parent.top - // anchors.left: parent.left - anchors.bottom: parent.bottom - visible: hotcueEnabled.value - - number: this.index - type: S4MK3.HotcuePoint.Type.OneShot - position: hotcuePosition.value / samplesControl.value } - } - - // Intro - S4MK3.HotcuePoint { Mixxx.ControlProxy { - id: introStartEnabled + id: hotcueEnabled group: root.group - key: `intro_start_enabled` + key: `hotcue_${index}_status` + onValueChanged: (value) => { - console.log(value); + _items_needing_redraw.add(waveform) } } Mixxx.ControlProxy { - id: introStartPosition + id: hotcuePosition group: root.group - key: `intro_start_position` + key: `hotcue_${index}_position` + onValueChanged: (value) => { - console.log(value); + _items_needing_redraw.add(waveform) } } anchors.top: parent.top + // anchors.left: parent.left anchors.bottom: parent.bottom - visible: introStartEnabled.value + visible: hotcueEnabled.value - type: S4MK3.HotcuePoint.Type.IntroIn - position: introStartPosition.value / samplesControl.value + number: this.index + type: S4MK3.HotcuePoint.Type.OneShot + position: hotcuePosition.value / samplesControl.value } + } - // Extro - S4MK3.HotcuePoint { + // Intro + S4MK3.HotcuePoint { - Mixxx.ControlProxy { - id: introEndEnabled - group: root.group - key: `intro_end_enabled` - } + Mixxx.ControlProxy { + id: introStartEnabled + group: root.group + key: `intro_start_enabled` - Mixxx.ControlProxy { - id: introEndPosition - group: root.group - key: `intro_end_position` + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) } + } - anchors.top: parent.top - anchors.bottom: parent.bottom - visible: introEndEnabled.value + Mixxx.ControlProxy { + id: introStartPosition + group: root.group + key: `intro_start_position` - type: S4MK3.HotcuePoint.Type.IntroOut - position: introEndPosition.value / samplesControl.value + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) + } } - // Loop in - S4MK3.HotcuePoint { - Mixxx.ControlProxy { - id: loopStartPosition - group: root.group - key: `loop_start_position` - } + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: introStartEnabled.value - anchors.top: parent.top - anchors.bottom: parent.bottom - visible: loopStartPosition.value > 0 + type: S4MK3.HotcuePoint.Type.IntroIn + position: introStartPosition.value / samplesControl.value + } + + // Extro + S4MK3.HotcuePoint { - type: S4MK3.HotcuePoint.Type.LoopIn - position: loopStartPosition.value / samplesControl.value + Mixxx.ControlProxy { + id: introEndEnabled + group: root.group + key: `intro_end_enabled` + + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) + } } - // Loop out - S4MK3.HotcuePoint { - Mixxx.ControlProxy { - id: loopEndPosition - group: root.group - key: `loop_end_position` + Mixxx.ControlProxy { + id: introEndPosition + group: root.group + key: `intro_end_position` + + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) } + } - anchors.top: parent.top - anchors.bottom: parent.bottom - visible: loopEndPosition.value > 0 + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: introEndEnabled.value + + type: S4MK3.HotcuePoint.Type.IntroOut + position: introEndPosition.value / samplesControl.value + } + + // Loop in + S4MK3.HotcuePoint { + Mixxx.ControlProxy { + id: loopStartPosition + group: root.group + key: `loop_start_position` - type: S4MK3.HotcuePoint.Type.LoopOut - position: loopEndPosition.value / samplesControl.value + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) + } } - Item { - id: waveformZoomed + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: loopStartPosition.value > 0 - property var group: root.group - property real scale: 0.2 + type: S4MK3.HotcuePoint.Type.LoopIn + position: loopStartPosition.value / samplesControl.value + } - visible: false - antialiasing: true - anchors.fill: parent + // Loop out + S4MK3.HotcuePoint { + Mixxx.ControlProxy { + id: loopEndPosition + group: root.group + key: `loop_end_position` - // MixxxControls.WaveformDisplay { - // anchors.fill: parent - // group: waveformZoomed.group - // scale: waveformZoomed.scale - // } - - Rectangle { - color: "white" - anchors.top: parent.top - anchors.bottom: parent.bottom - x: 153 - width: 2 + onValueChanged: (value) => { + _items_needing_redraw.add(waveform) } - Item { - id: waveformContainer + } - property real duration: samplesControl.value / sampleRateControl.value + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: loopEndPosition.value > 0 - anchors.fill: parent - clip: true + type: S4MK3.HotcuePoint.Type.LoopOut + position: loopEndPosition.value / samplesControl.value + } - Mixxx.ControlProxy { - id: samplesControl + Item { + id: waveformZoomed - group: root.group - key: "track_samples" - } + property var group: root.group + property real scale: 0.2 - Mixxx.ControlProxy { - id: sampleRateControl + visible: false + antialiasing: true + anchors.fill: parent - group: root.group - key: "track_samplerate" - } + // MixxxControls.WaveformDisplay { + // anchors.fill: parent + // group: waveformZoomed.group + // scale: waveformZoomed.scale + // } - Mixxx.ControlProxy { - id: playPositionControl + Rectangle { + color: "white" + anchors.top: parent.top + anchors.bottom: parent.bottom + x: 153 + width: 2 + } + Item { + id: waveformContainer - group: root.group - key: "playposition" - } + property real duration: samplesControl.value / sampleRateControl.value + + anchors.fill: parent + clip: true - Mixxx.ControlProxy { - id: rateRatioControl + Mixxx.ControlProxy { + id: samplesControl - group: root.group - key: "rate_ratio" - } + group: root.group + key: "track_samples" + } - Mixxx.ControlProxy { - id: zoomControl + Mixxx.ControlProxy { + id: sampleRateControl - group: root.group - key: "waveform_zoom" - } + group: root.group + key: "track_samplerate" + } + + Mixxx.ControlProxy { + id: playPositionControl + + group: root.group + key: "playposition" + } + + Mixxx.ControlProxy { + id: rateRatioControl + + group: root.group + key: "rate_ratio" + } + + Mixxx.ControlProxy { + id: zoomControl + + group: root.group + key: "waveform_zoom" + } - Item { - id: waveformBeat + Item { + id: waveformBeat - property real effectiveZoomFactor: (zoomControl.value * rateRatioControl.value / waveformZoomed.scale) * 6 + property real effectiveZoomFactor: (zoomControl.value * rateRatioControl.value / waveformZoomed.scale) * 6 - width: waveformContainer.duration * effectiveZoomFactor - height: parent.height - x: 0.5 * waveformContainer.width - playPositionControl.value * width - visible: true + width: waveformContainer.duration * effectiveZoomFactor + height: parent.height + x: 0.5 * waveformContainer.width - playPositionControl.value * width + visible: true - Shape { - id: preroll + Shape { + id: preroll - property real triangleHeight: waveformBeat.height - property real triangleWidth: 0.25 * waveformBeat.effectiveZoomFactor - property int numTriangles: Math.ceil(width / triangleWidth) + property real triangleHeight: waveformBeat.height + property real triangleWidth: 0.25 * waveformBeat.effectiveZoomFactor + property int numTriangles: Math.ceil(width / triangleWidth) - anchors.top: waveformBeat.top - anchors.right: waveformBeat.left - width: Math.max(0, waveformBeat.x) - height: waveformBeat.height + anchors.top: waveformBeat.top + anchors.right: waveformBeat.left + width: Math.max(0, waveformBeat.x) + height: waveformBeat.height - ShapePath { - strokeColor: 'red' - strokeWidth: 1 - fillColor: "transparent" + ShapePath { + strokeColor: 'red' + strokeWidth: 1 + fillColor: "transparent" - PathMultiline { - paths: { - let p = []; - for (let i = 0; i < preroll.numTriangles; i++) { - p.push([Qt.point(preroll.width - i * preroll.triangleWidth, preroll.triangleHeight / 2), Qt.point(preroll.width - (i + 1) * preroll.triangleWidth, 0), Qt.point(preroll.width - (i + 1) * preroll.triangleWidth, preroll.triangleHeight), Qt.point(preroll.width - i * preroll.triangleWidth, preroll.triangleHeight / 2)]); - } - return p; + PathMultiline { + paths: { + let p = []; + for (let i = 0; i < preroll.numTriangles; i++) { + p.push([Qt.point(preroll.width - i * preroll.triangleWidth, preroll.triangleHeight / 2), Qt.point(preroll.width - (i + 1) * preroll.triangleWidth, 0), Qt.point(preroll.width - (i + 1) * preroll.triangleWidth, preroll.triangleHeight), Qt.point(preroll.width - i * preroll.triangleWidth, preroll.triangleHeight / 2)]); } + return p; } } } + } - Shape { - id: postroll - - property real triangleHeight: waveformBeat.height - property real triangleWidth: 0.25 * waveformBeat.effectiveZoomFactor - property int numTriangles: Math.ceil(width / triangleWidth) - - anchors.top: waveformBeat.top - anchors.left: waveformBeat.right - width: waveformContainer.width / 2 - height: waveformBeat.height - - ShapePath { - strokeColor: 'red' - strokeWidth: 1 - fillColor: "transparent" - - PathMultiline { - paths: { - let p = []; - for (let i = 0; i < postroll.numTriangles; i++) { - p.push([Qt.point(i * postroll.triangleWidth, postroll.triangleHeight / 2), Qt.point((i + 1) * postroll.triangleWidth, 0), Qt.point((i + 1) * postroll.triangleWidth, postroll.triangleHeight), Qt.point(i * postroll.triangleWidth, postroll.triangleHeight / 2)]); - } - return p; + Shape { + id: postroll + + property real triangleHeight: waveformBeat.height + property real triangleWidth: 0.25 * waveformBeat.effectiveZoomFactor + property int numTriangles: Math.ceil(width / triangleWidth) + + anchors.top: waveformBeat.top + anchors.left: waveformBeat.right + width: waveformContainer.width / 2 + height: waveformBeat.height + + ShapePath { + strokeColor: 'red' + strokeWidth: 1 + fillColor: "transparent" + + PathMultiline { + paths: { + let p = []; + for (let i = 0; i < postroll.numTriangles; i++) { + p.push([Qt.point(i * postroll.triangleWidth, postroll.triangleHeight / 2), Qt.point((i + 1) * postroll.triangleWidth, 0), Qt.point((i + 1) * postroll.triangleWidth, postroll.triangleHeight), Qt.point(i * postroll.triangleWidth, postroll.triangleHeight / 2)]); } + return p; } } } } + } - MixxxControls.WaveformOverview { - property real duration: samplesControl.value / sampleRateControl.value + MixxxControls.WaveformOverview { + property real duration: samplesControl.value / sampleRateControl.value - // anchors.fill: parent - // clip: true + // anchors.fill: parent + // clip: true - group: root.group - anchors.fill: parent - channels: Mixxx.WaveformOverview.Channels.BothChannels - renderer: Mixxx.WaveformOverview.Renderer.RGB - colorHigh: 'white' - colorMid: 'blue' - colorLow: 'green' - } + group: root.group + anchors.fill: parent + channels: Mixxx.WaveformOverview.Channels.BothChannels + renderer: Mixxx.WaveformOverview.Renderer.RGB + colorHigh: 'white' + colorMid: 'blue' + colorLow: 'green' } } } + } - // spacer item - S4MK3.Keyboard { - id: keyboard - group: root.group - visible: false - Layout.fillWidth: true - Layout.fillHeight: true - } + // spacer item + S4MK3.Keyboard { + id: keyboard + group: root.group + visible: false + Layout.fillWidth: true + Layout.fillHeight: true } } } - Loader { - id: loader - anchors.fill: parent - sourceComponent: live + } + + Loader { + id: loader + anchors.fill: parent + sourceComponent: live + onLoaded: { + setTimeout(() => _items_needing_redraw.add(root), 100); } } } diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 5bb4e54183bd..9bb97c8b4a06 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -8,6 +8,7 @@ #include "controllers/controllermanager.h" #include "controllers/defs_controllers.h" #include "moc_controller.cpp" +#include "util/cmdlineargs.h" #include "util/screensaver.h" namespace { @@ -156,7 +157,10 @@ void Controller::receive(const QByteArray& data, mixxx::Duration timestamp) { triggerActivity(); int length = data.size(); - if (m_logInput().isDebugEnabled()) { + if (CmdlineArgs::Instance() + .getControllerDebug()) { // TODO shall we replicate this + // value local since it doesn + // change at runtime? // Formatted packet display QString message = QString("t:%2, %3 bytes:\n") .arg(timestamp.formatMillisWithUnit(), diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 6c4f1631604e..df236fda5026 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -836,7 +836,9 @@ ControllerScreenPreview::ControllerScreenPreview( m_screenInfo(screen), m_pFrame(make_parented(this)), m_pStat(make_parented("- FPS", this)), + m_frameDurationHistoryIdx(0), m_lastFrameTimespamp(mixxx::Time::elapsed()) { + memset(m_frameDurationHistory, 0, 5); m_pFrame->setFixedSize(screen.size); m_pStat->setAlignment(Qt::AlignRight); auto aLayout = make_parented(this); @@ -854,10 +856,19 @@ void ControllerScreenPreview::updateFrame( return; } auto currentTimestamp = mixxx::Time::elapsed(); - auto durationSinceLastFrame = (currentTimestamp - m_lastFrameTimespamp).toIntegerMillis(); - if (durationSinceLastFrame) { + m_frameDurationHistory[m_frameDurationHistoryIdx++] = + (currentTimestamp - m_lastFrameTimespamp).toIntegerMillis(); + m_frameDurationHistoryIdx %= 5; + + double durationSinceLastFrame = 0.0; + for (uint8_t i = 0; i < 5; i++) { + durationSinceLastFrame += (double)m_frameDurationHistory[i]; + } + durationSinceLastFrame /= 5.0; + + if (durationSinceLastFrame > 0.0) { m_pStat->setText(QString("%0 FPS (requested %1)") - .arg(1000 / durationSinceLastFrame) + .arg((int)(1000.0 / durationSinceLastFrame)) .arg(m_screenInfo.target_fps)); } m_pFrame->setPixmap(QPixmap::fromImage(frame)); diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index d7820ee8cb6b..bf6e3cec0d0c 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "controllers/controllerinputmappingtablemodel.h" @@ -32,6 +33,8 @@ class ControllerScreenPreview : public QWidget { parented_ptr m_pFrame; parented_ptr m_pStat; + uint8_t m_frameDurationHistoryIdx; + uint m_frameDurationHistory[5]; mixxx::Duration m_lastFrameTimespamp; }; diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 7961764af4f4..0da54ce23b03 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -227,13 +227,6 @@ void ControllerRenderingEngine::renderFrame() { emit frameRendered(m_screenInfo, fboImage); - // auto endOfRender = mixxx::Time::elapsed(); - // qDebug() << "Fame took " - // << (endOfRender - m_nextFrameStart).formatMillisWithUnit() - // << " and frame has" << fboImage.sizeInBytes() << "bytes"; - - m_nextFrameStart += mixxx::Duration::fromMillis(1000 / m_screenInfo.target_fps); - m_context->doneCurrent(); } @@ -243,7 +236,16 @@ bool ControllerRenderingEngine::stop() { } void ControllerRenderingEngine::send(Controller* controller, const QByteArray& frame) { - controller->sendBytes(frame); + if (!frame.isEmpty()) { + controller->sendBytes(frame); + } + + // auto endOfRender = mixxx::Time::elapsed(); + // qDebug() << "Fame took " + // << (endOfRender - m_nextFrameStart).formatMillisWithUnit() + // << " and frame has" << frame.size() << "bytes"; + + m_nextFrameStart += mixxx::Duration::fromSeconds(1.0 / (double)m_screenInfo.target_fps); auto durationToWaitBeforeFrame = (m_nextFrameStart - mixxx::Time::elapsed()); auto msecToWaitBeforeFrame = durationToWaitBeforeFrame.toIntegerMillis(); @@ -252,7 +254,10 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f // qDebug() << "Waiting for " // << durationToWaitBeforeFrame.formatMillisWithUnit() // << " before rendering next frame"; - QTimer::singleShot(msecToWaitBeforeFrame, this, &ControllerRenderingEngine::renderFrame); + QTimer::singleShot(msecToWaitBeforeFrame, + Qt::PreciseTimer, + this, + &ControllerRenderingEngine::renderFrame); } else { QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 7d0371396dd8..2bf250e5cf40 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -331,8 +331,6 @@ bool ControllerScriptEngineLegacy::initialize() { } } - // if () - // For testing, do not actually initialize the scripts, just check for // syntax errors above. if (m_bTesting) {