diff --git a/res/controllers/Traktor Kontrol S4 MK3.bulk.xml b/res/controllers/Traktor Kontrol S4 MK3.bulk.xml new file mode 100644 index 000000000000..9ff549336099 --- /dev/null +++ b/res/controllers/Traktor Kontrol S4 MK3.bulk.xml @@ -0,0 +1,636 @@ + + + + Traktor Kontrol S4 MK3 (Screens) + A. Colombier + Mapping for Traktor Kontrol S4 MK3 screens + native_instruments_traktor_kontrol_s4_mk3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/controllers/TraktorKontrolS4MK3Screens.qml b/res/controllers/TraktorKontrolS4MK3Screens.qml new file mode 100644 index 000000000000..10a2df52539c --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens.qml @@ -0,0 +1,217 @@ +import QtQuick 2.15 +import QtQuick.Window 2.3 + +import QtQuick.Controls 2.15 +import QtQuick.Shapes 1.11 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2.15 + +import Qt5Compat.GraphicalEffects + +import "." as Skin +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +import S4MK3 as S4MK3 + +Mixxx.ControllerScreen { + id: root + + required property string screenId + property color fontColor: Qt.rgba(242/255,242/255,242/255, 1) + property color smallBoxBorder: Qt.rgba(44/255,44/255,44/255, 1) + + property string group: screenId == "rightdeck" ? "[Channel2]" : "[Channel1]" + property string theme: engine.getSetting("theme") || "stock" + + readonly property bool isStockTheme: theme == "stock" + + property var lastFrame: null + + init: function(_controllerName, isDebug) { + console.log(`Screen ${root.screenId} has started with theme ${root.theme}`) + root.state = "Live" + } + + shutdown: function() { + console.log(`Screen ${root.screenId} is stopping`) + root.state = "Stop" + } + + transformFrame: function(input, timestamp) { + let updated = new Uint8Array(320*240); + updated.fill(0) + + let updatedPixelCount = 0; + let updated_zones = []; + + if (!root.lastFrame) { + root.lastFrame = new ArrayBuffer(input.byteLength); + updatedPixelCount = input.byteLength / 2; + updated_zones.push({ + x: 0, + y: 0, + width: 320, + height: 240, + }) + } else { + const view_input = new Uint8Array(input); + const view_last = new Uint8Array(root.lastFrame); + + for (let i = 0; i < 320 * 240; i++) { + } + + let current_rect = null; + + for (let y = 0; y < 240; y++) { + let line_changed = false; + for (let x = 0; x < 320; x++) { + let i = y * 320 + x; + if (view_input[2 * i] != view_last[2 * i] || view_input[2 * i + 1] != view_last[2 * i + 1]) { + line_changed = true; + updatedPixelCount++; + break; + } + } + if (current_rect !== null && line_changed) { + current_rect.height++; + } else if (current_rect !== null) { + updated_zones.push(current_rect); + current_rect = null; + } else if (current_rect === null && line_changed) { + current_rect = { + x: 0, + y, + width: 320, + height: 1, + }; + } + } + if (current_rect !== null) { + updated_zones.push(current_rect); + } + } + new Uint8Array(root.lastFrame).set(new Uint8Array(input)); + + if (!updatedPixelCount) { + return new ArrayBuffer(0); + } else if (root.renderDebug) { + console.log(`Pixel updated: ${updatedPixelCount}, ${updated_zones.length} areas`); + } + + // No redraw needed, stop right there + + let totalPixelToDraw = 0; + for (const area of updated_zones) { + area.x -= Math.min(2, area.x); + area.y -= Math.min(2, area.y); + area.width += Math.min(4, 320 - area.x - area.width); + area.height += Math.min(4, 240 - area.y - area.height); + totalPixelToDraw += area.width*area.height; + } + + if (totalPixelToDraw != 320*240 && (totalPixelToDraw > 320 * 180 || updated_zones.length > 20)) { + if (root.renderDebug) { + console.log(`Full redraw instead of ${totalPixelToDraw} pixels/${updated_zones.length} areas`) + } + totalPixelToDraw = 320*240 + updated_zones = [{ + x: 0, + y: 0, + width: 320, + height: 240, + }] + } else if (root.renderDebug) { + console.log(`Redrawing ${totalPixelToDraw} pixels`) + } + + const screenIdx = screenId === "leftdeck" ? 0 : 1; + + const outputData = new ArrayBuffer(totalPixelToDraw*2 + 20*updated_zones.length); // Number of pixel + 20 (header/footer size) x the number of region + let offset = 0; + + for (const area of updated_zones) { + const header = new Uint8Array(outputData, offset, 16); + const payload = new Uint8Array(outputData, offset + 16, area.width*area.height*2); + const footer = new Uint8Array(outputData, offset + area.width*area.height*2 + 16, 4); + + header.fill(0) + footer.fill(0) + header[0] = 0x84; + header[2] = screenIdx; + header[3] = 0x21; + + header[8] = area.x >> 8; + header[9] = area.x & 0xff; + header[10] = area.y >> 8; + header[11] = area.y & 0xff; + + header[12] = area.width >> 8; + header[13] = area.width & 0xff; + header[14] = area.height >> 8; + header[15] = area.height & 0xff; + + if (area.x === 0 && area.width === 320) { + payload.set(new Uint8Array(input, area.y * 320 * 2, area.width*area.height*2)); + } else { + for (let y = 0; y < area.height; y++) { + payload.set( + new Uint8Array(input, ((area.y + y) * 320 + area.x) * 2, area.width * 2), + y * area.width * 2); + } + } + footer[0] = 0x40; + footer[2] = screenIdx; + offset += area.width*area.height*2 + 20 + } + if (root.renderDebug) { + console.log(`Generated ${offset} bytes to be sent`) + } + // return new ArrayBuffer(0); + return outputData; + } + + Component { + id: splashOff + S4MK3.SplashOff { + anchors.fill: parent + } + } + Component { + id: stockLive + S4MK3.StockScreen { + group: root.group + screenId: root.screenId + anchors.fill: parent + } + } + Component { + id: advancedLive + S4MK3.AdvancedScreen { + isLeftScreen: root.screenId == "leftdeck" + } + } + + Loader { + id: loader + anchors.fill: parent + sourceComponent: splashOff + } + + states: [ + State { + name: "Live" + PropertyChanges { + target: loader + sourceComponent: isStockTheme ? stockLive : advancedLive + } + }, + State { + name: "Stop" + PropertyChanges { + target: loader + sourceComponent: splashOff + } + } + ] +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen.qml new file mode 100755 index 000000000000..77032739cda1 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen.qml @@ -0,0 +1,64 @@ +import QtQuick 2.15 + +import './AdvancedScreen/Defines' as Defines +import './AdvancedScreen/Views' as Views +import './AdvancedScreen' as S4MK3 + +//---------------------------------------------------------------------------------------------------------------------- +// S4MK3 Screen - manage top/bottom deck of one screen +//---------------------------------------------------------------------------------------------------------------------- + +Item { + id: screen + + required property bool isLeftScreen + + //-------------------------------------------------------------------------------------------------------------------- + + readonly property int topDeckId: isLeftScreen ? 1 : 2 + readonly property int bottomDeckId: isLeftScreen ? 3 : 4 + property bool propTopDeckFocus: true + + Defines.Font {id: fonts} + Defines.Utils {id: utils} + Defines.Settings {id: settings} + Defines.Durations {id: durations} + Defines.Colors {id: colors} + + Component.onCompleted: { + if (typeof engine.makeSharedDataConnection === "function") { + engine.makeSharedDataConnection(screen.onSharedDataUpdate) + screen.onSharedDataUpdate(engine.getSharedData()) + } + } + + function onSharedDataUpdate(data) { + if (typeof data === "object" && typeof data.group === "object") { + propTopDeckFocus = data.group[isLeftScreen ? 'leftdeck' : 'rightdeck'] === `[Channel${screen.topDeckId}]` + } + } + + width: 320 + height: 240 + clip: true + + /* + A screen is visible if - + The deck is in focus and the linked deck is not selecting a sample slot + OR + The deck is not in focus but a sample slot is selected + */ + S4MK3.DeckScreen { + id: topDeckScreen + deckId: topDeckId + visible: propTopDeckFocus + anchors.fill: parent + } + + S4MK3.DeckScreen { + id: bottomDeckScreen + deckId: bottomDeckId + visible: !propTopDeckFocus + anchors.fill: parent + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/BrowserFooter.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/BrowserFooter.qml new file mode 100755 index 000000000000..edd2dea43929 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/BrowserFooter.qml @@ -0,0 +1,340 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Defines' as Defines +import '../Defines' as Defines +import '../ViewModels' as ViewModels + +//------------------------------------------------------------------------------------------------------------------ +// LIST ITEM - DEFINES THE INFORMATION CONTAINED IN ONE LIST ITEM +//------------------------------------------------------------------------------------------------------------------ +Rectangle { + id: footer + + Defines.Colors { id: colors } + + required property var deckInfo + + property string propertiesPath: "" + property real sortingKnobValue: 0.0 + property bool isContentList: qmlBrowser.isContentList + property int maxCount: 0 + property int count: 0 + + // the given numbers are determined by the EContentListColumns in Traktor + readonly property variant sortIds: [0 ,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + readonly property variant sortNames: ["Sort By #", "Sort By #", "Title", "Artist", "Time", "BPM", "Track #", "Release", "Label", "Genre", "Key Text", "Comment", "Lyrics", "Comment 2", "Path", "Analysed", "Remixer", "Producer", "Mix", "CAT #", "Rel. Date", "Bitrate", "Rating", "Count", "Sort By #", "Cover Art", "Last Played", "Import Date", "Key", "Color", "File Name"] + readonly property int selectedFooterId: (selectedFooterItem.value === undefined) ? 0 : ( ( selectedFooterItem.value % 2 === 1 ) ? 1 : 4 ) // selectedFooterItem.value takes values from 1 to 4. + + property real preSortingKnobValue: 0.0 + + //-------------------------------------------------------------------------------------------------------------------- + + // AppProperty { id: previewIsLoaded; path : "app.traktor.browser.preview_player.is_loaded" } + QtObject { + id: previewIsLoaded + property string description: "Description" + property var value: 0 + } + // AppProperty { id: previewTrackLenght; path : "app.traktor.browser.preview_content.track_length" } + QtObject { + id: previewTrackLenght + property string description: "Description" + property var value: 0 + } + // AppProperty { id: previewTrackElapsed; path : "app.traktor.browser.preview_player.elapsed_time" } + QtObject { + id: previewTrackElapsed + property string description: "Description" + property var value: 0 + } + + // MappingProperty { id: overlayState; path: propertiesPath + ".overlay" } + QtObject { + id: overlayState + property string description: "Description" + property var value: 0 + } + // MappingProperty { id: isContentListProp; path: propertiesPath + ".browser.is_content_list" } + QtObject { + id: isContentListProp + property string description: "Description" + property var value: 0 + } + // MappingProperty { id: selectedFooterItem; path: propertiesPath + ".selected_footer_item" } + QtObject { + id: selectedFooterItem + property string description: "Description" + property var value: 0 + } + + //-------------------------------------------------------------------------------------------------------------------- + // Behavior on Sorting Changes (show/hide sorting widget, select next allowed sorting) + //-------------------------------------------------------------------------------------------------------------------- + + onIsContentListChanged: { + // We need this to be able do disable mappings (e.g. sorting ascend/descend) + isContentListProp.value = isContentList; + } + + onSortingKnobValueChanged: { + if (!footer.isContentList) + return; + + overlayState.value = Overlay.sorting; + sortingOverlayTimer.restart(); + + var val = clamp(footer.sortingKnobValue - footer.preSortingKnobValue, -1, 1); + val = parseInt(val); + if (val != 0) { + qmlBrowser.sortingId = getSortingIdWithDelta( val ); + footer.preSortingKnobValue = footer.sortingKnobValue; + } + } + + Timer { + id: sortingOverlayTimer + interval: 800 // duration of the scrollbar opacity + repeat: false + + onTriggered: overlayState.value = Overlay.none; + } + + //-------------------------------------------------------------------------------------------------------------------- + // View + //-------------------------------------------------------------------------------------------------------------------- + + clip: true + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 21 + (settings.raiseBrowserFooter ? 4 : 0) // set in state + color: "transparent" + + // background color + Rectangle { + id: browserFooterBg + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 15 + (settings.raiseBrowserFooter ? 4 : 0) + color: colors.colorBrowserHeader // footer background color + } + + Row { + id: sortingRow + anchors.left: browserFooterBg.left + anchors.leftMargin: 1 + anchors.top: browserFooterBg.top + + Item { + width: 100 + height: 15 + (settings.raiseBrowserFooter ? 4 : 0) + + Text { + font.pixelSize: fonts.scale(12) + anchors.left: parent.left + anchors.leftMargin: 3 + font.capitalization: Font.AllUppercase + color: selectedFooterId == 1 ? "white" : colors.colorFontBrowserHeader + text: getSortingNameForSortId(qmlBrowser.sortingId) + visible: qmlBrowser.isContentList + } + // Arrow (Sorting Direction Indicator) + Triangle { + id: sortDirArrow + width: 10 + height: 10 + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 2 + anchors.rightMargin: 6 + antialiasing: false + visible: qmlBrowser.sortingId > 0 + color: colors.colorGrey80 + rotation: ((qmlBrowser.sortingDirection == 1) ? 0 : 180) + } + Rectangle { + id: divider + height: 15 + width: 1 + color: colors.colorGrey40 // footer divider color + anchors.right: parent.right + } + } + + // Preview Player footer + Item { + width: 120 + height: 15 + + Text { + font.pixelSize: fonts.scale(12) + anchors.left: parent.left + anchors.leftMargin: 5 + font.capitalization: Font.AllUppercase + visible: !previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : "green" + text: deckInfo.masterDeckLetter + } + + Text { + font.pixelSize: fonts.scale(12) + anchors.left: parent.left + anchors.leftMargin: 20 + font.capitalization: Font.AllUppercase + visible: !previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : colors.colorFontBrowserHeader + text: deckInfo.masterBPMFooter + } + + Text { + font.pixelSize: fonts.scale(12) + anchors.right: parent.right + anchors.rightMargin: 5 + font.capitalization: Font.AllUppercase + visible: !previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : colors.musicalKeyColorsDark[deckInfo.masterKeyIndex] + text: settings.camelotKey ? utils.camelotConvert(deckInfo.masterKey) : deckInfo.masterKey + } + + Text { + font.pixelSize: fonts.scale(12) + anchors.left: parent.left + anchors.leftMargin: 5 + font.capitalization: Font.AllUppercase + visible: previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : colors.colorFontBrowserHeader + text: "Preview" + } + + // Image { + // anchors.top: parent.top + // anchors.right: parent.right + // anchors.topMargin: 2 + // anchors.rightMargin: 45 + // visible: previewIsLoaded.value + // antialiasing: false + // source: "../Images/PreviewIcon_Small.png" + // fillMode: Image.Pad + // clip: true + // cache: false + // sourceSize.width: width + // sourceSize.height: height + // } + Text { + width: 40 + clip: true + horizontalAlignment: Text.AlignRight + visible: previewIsLoaded.value + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 2 + anchors.rightMargin: 7 + font.pixelSize: fonts.scale(12) + font.capitalization: Font.AllUppercase + font.family: "Pragmatica" + color: colors.browser.prelisten + text: utils.convertToTimeString(previewTrackElapsed.value) + } + Rectangle { + id: divider2 + height: 15 + width: 1 + color: colors.colorGrey40 // footer divider color + anchors.right: parent.right + } + } + + Item { + width: 80 + height: 15 + + Text { + Text { + font.pixelSize: fonts.scale(12) + anchors.left: parent.left + anchors.leftMargin: 5 + font.capitalization: Font.AllUppercase + visible: true + color: colors.colorFontBrowserHeader + text: count+"/"+maxCount + } + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // black border & shadow + //-------------------------------------------------------------------------------------------------------------------- + + Rectangle { + id: browserHeaderBottomGradient + height: 3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: browserHeaderBlackBottomLine.top + gradient: Gradient { + GradientStop { position: 0.0; color: colors.colorBlack0 } + GradientStop { position: 1.0; color: colors.colorBlack38 } + } + } + + Rectangle { + id: browserHeaderBlackBottomLine + height: 2 + color: colors.colorBlack + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: browserFooterBg.top + } + + //------------------------------------------------------------------------------------------------------------------ + + state: "show" + states: [ + State { + name: "show" + PropertyChanges { target: footer; height: 21 + (settings.raiseBrowserFooter ? 4 : 0) } + }, + State { + name: "hide" + PropertyChanges { target: footer; height: 0 } + } + ] + + //-------------------------------------------------------------------------------------------------------------------- + // Necessary Functions + //-------------------------------------------------------------------------------------------------------------------- + + function getSortingIdWithDelta( delta ) { + var curPos = getPosForSortId( qmlBrowser.sortingId ); + var pos = curPos + delta; + var count = sortIds.length; + + pos = (pos < 0) ? count-1 : pos; + pos = (pos >= count) ? 0 : pos; + + return sortIds[pos]; + } + + function getPosForSortId(id) { + if (id == -1) return 0; // -1 is a special case which should be interpreted as "0" + for (var i=0; i= 0 && pos < sortNames.length) + return sortNames[pos]; + return "SORTED"; + } + + function clamp(val, min, max) { + return Math.max( Math.min(val, max) , min ); + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/BrowserHeader.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/BrowserHeader.qml new file mode 100755 index 000000000000..5e258cd4995b --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/BrowserHeader.qml @@ -0,0 +1,223 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Defines' as Defines +import '../ViewModels' as ViewModels + +//------------------------------------------------------------------------------------------------------------------ +// BROWSER HEADER - SHOWS THE CURRENT BROWSER PATH +//------------------------------------------------------------------------------------------------------------------ +Item { + id: header + + Defines.Colors { id: colors } + + property int currentDeck: 0 + property int nodeIconId: 0 + + readonly property color itemColor: colors.colorWhite19 + property int highlightIndex: 0 + + readonly property var letters: ["","A", "B", "C", "D"] + + property string pathStrings: "" // the complete path in one string given by QBrowser with separator " | " + property var stringList: [""] // list of separated path elements (calculated in "updateStringList") + property int stringListModelSize: 0 // nr of entries which can be displayed in the header ( calc in updateStringList) + readonly property int maxTextWidth: 150 // if a single text path block is bigger than this: ElideMiddle + readonly property int arrowContainerWidth: 18 // width of the graphical separator arrow. includes left / right spacing + readonly property int fontSize: 13 + + clip: true + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: 17 // set in state + + onPathStringsChanged: { updateStringList(textLengthDummy) } + + //-------------------------------------------------------------------------------------------------------------------- + // NOTE: text item used within the 'updateStringList' function to determine how many of the stringList items can be fit + // in the header! + // IMPORTANT EXTRA NOTE: all texts in the header should have the same Capitalization and font size settings as the "dummy" + // as the dummy is used to calculate the number of text blocks fitting into the header. + //-------------------------------------------------------------------------------------------------------------------- + Text { + id: textLengthDummy + visible: false + font.capitalization: Font.AllUppercase + font.pixelSize: header.fontSize + } + + // calculates the number of entries to be displayed in the header + function updateStringList(dummy) { + var sum = 0 + var count = 0 + + stringList = pathStrings.split(" | ") + + for (var i = 0; i < stringList.length; ++i) { + dummy.text = header.stringList[stringList.length - i - 1] + + sum += (dummy.width) > maxTextWidth ? header.maxTextWidth : dummy.width + sum += arrowContainerWidth + + if (sum > (textContainter.width - header.arrowContainerWidth)) { + header.stringListModelSize = count + return + } + count++ + } + header.stringListModelSize = stringList.length; + } + + //-------------------------------------------------------------------------------------------------------------------- + // background color + Rectangle { + id: browserHeaderBg + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: 17 + color: colors.colorBrowserHeader //colors.colorGrey24 + } + + //-------------------------------------------------------------------------------------------------------------------- + + Item { + id: textContainter + readonly property int spaceToDeckLetter: 20 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: deckLetter.left + anchors.leftMargin: 3 + anchors.rightMargin: spaceToDeckLetter + clip: true + + // dots appear at the left side of the browser in case the full path does not fit into the header anymore. + Item { + id: dots + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: (stringListModelSize < stringList.length) ? 0 : -width + visible: (stringListModelSize < stringList.length) + width: 30 + + Text { + anchors.left: parent.left + anchors.top: parent.top + text: "..." + font.capitalization: Font.AllUppercase + font.pixelSize: header.fontSize + color: colors.colorFontBrowserHeader + } + } + + // the text flow + Flow { + id: textFlow + layoutDirection: Qt.RightToLeft + + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: dots.right + + Repeater { + model: stringListModelSize + Item { + id: textContainer + property string displayTxt: (stringList[stringList.length - index - 1] == undefined) ? "" : stringList[stringList.length - index - 1] + + width: headerPath.width + arrowContainerWidth + height: 20 + + // arrows + // the graphical separator between texts anchors on the left side of each text block. The space of "arrowContainerWidth" is reserved for that + // Widgets.TextSeparatorArrow { + // color: colors.colorGrey80 + // visible: true + // anchors.top: parent.top + // anchors.right: headerPath.left + // anchors.topMargin: 4 + // anchors.rightMargin: 6 // left margin is set via "arrowContainerWidth" + // } + + Text { + id: dummy + // NOTE: dummyTextPath is only used to get the displayWidth of the strings. (otherwise dynamic text sizes are hard/impossible) + text: displayTxt + visible: false + font.capitalization: Font.AllUppercase + font.pixelSize: header.fontSize + } + + Text { + id: headerPath + // dummy.width is determined by the string contained in it and ceil to whole pixels (ceil instead of round to avoid unwanted elides) + width: (dummy.width > maxTextWidth) ? maxTextWidth : Math.ceil(dummy.width ) + elide: Text.ElideMiddle + text: displayTxt + visible: true + color: (index == 0) ? colors.colorDeckBlueBright : colors.colorGrey88 + font.capitalization: Font.AllUppercase + font.pixelSize: header.fontSize + } + } + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + Text { + id: deckLetter + anchors.right: parent.right + anchors.top: parent.top + height: parent.height + width: parent.height + + text: header.letters[header.currentDeck] + font.capitalization: Font.AllUppercase + font.pixelSize: header.fontSize + color: colors.colorDeckBlueBright + } + + //-------------------------------------------------------------------------------------------------------------------- + // black border & shadow + + Rectangle { + id: browserHeaderBlackBottomLine + anchors.left: parent.left + anchors.right: parent.right + anchors.top: browserHeaderBg.bottom + height: 2 + color: colors.colorBlack + } + + Rectangle { + id: browserHeaderBottomGradient + anchors.left: parent.left + anchors.right: parent.right + anchors.top: browserHeaderBlackBottomLine.bottom + height: 3 + gradient: Gradient { + GradientStop { position: 0.0; color: colors.colorBlack38 } + GradientStop { position: 1.0; color: colors.colorBlack0 } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + state: "show" + states: [ + State { + name: "show" + PropertyChanges {target: header; height: 15} + }, + State { + name: "hide" + PropertyChanges {target: header; height: 0} + } + ] +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/ListDelegate.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/ListDelegate.qml new file mode 100755 index 000000000000..54e8525fa5e6 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/ListDelegate.qml @@ -0,0 +1,262 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +//------------------------------------------------------------------------------------------------------------------ +// LIST ITEM - DEFINES THE INFORMATION CONTAINED IN ONE LIST ITEM +//------------------------------------------------------------------------------------------------------------------ + +// the model contains the following roles: +// dataType, nodeIconId, nodeName, nrOfSubnodes, coverUrl, artistName, trackName, bpm, key, keyIndex, rating, loadedInDeck, prevPlayed, prelisten + +Item { + id: contactDelegate + + Defines.Settings {id: settings} + + property string masterBPM: "" + property string masterKey: "" + property int keyIndex: 0 + property bool isPlaying: false + property bool adjacentKeys: false + property string newIndex: keyIndex || "" + property string masterKeyIndex: keyIndex + property color deckColor: qmlBrowser.focusColor + property color textColor: !ListView.isCurrentItem ? "White" : deckColor + property bool isCurrentItem: ListView.isCurrentItem + readonly property int textTopMargin: 5 // centers text vertically + // readonly property bool isLoaded: (dataType == "Track") ? model.loadedInDeck.length > 0 : false + readonly property bool isLoaded: false + // visible: !ListView.isCurrentItem + readonly property string dataType: "Track" + readonly property string artistName: model.artist + readonly property string trackName: model.title + readonly property string key: "" + readonly property string bpmText: "---" + readonly property real bpm: 0 + + readonly property string bpmMatch: tempoNeeded(masterBPM, bpm).toFixed(2).toString() + + property bool deck1: deckInfo.is1Playing + property bool deck2: deckInfo.is2Playing + property bool deck3: deckInfo.is3Playing + property bool deck4: deckInfo.is4Playing + + readonly property bool deckPlaying: deck1 || deck2 || deck3 || deck4 + + function tempoNeeded(master, current) { + if (master > current) { + return (1-(current/master))*100; + } + + return ((master/current)-1)*100; + } + + readonly property int key1m: 21 + readonly property int key2m: 16 + readonly property int key3m: 23 + readonly property int key4m: 18 + readonly property int key5m: 13 + readonly property int key6m: 20 + readonly property int key7m: 15 + readonly property int key8m: 22 + readonly property int key9m: 17 + readonly property int key10m: 12 + readonly property int key11m: 19 + readonly property int key12m: 14 + readonly property int key1d: 0 + readonly property int key2d: 7 + readonly property int key3d: 2 + readonly property int key4d: 9 + readonly property int key5d: 4 + readonly property int key6d: 11 + readonly property int key7d: 6 + readonly property int key8d: 1 + readonly property int key9d: 8 + readonly property int key10d: 3 + readonly property int key11d: 10 + readonly property int key12d: 5 + + function colorKey(newKey,masterKey) { + if (!contactDelegate.adjacentKeys) {return true} + else if ((newKey == masterKey)) {return true} + else if (masterKey == "") {return false} + else if (masterKey == "21" && (newKey == "14" || newKey == "16" || newKey == "0")) {return true} + else if (masterKey == "16" && (newKey == "21" || newKey == "23" || newKey == "7")) {return true} + else if (masterKey == "23" && (newKey == "16" || newKey == "18" || newKey == "2")) {return true} + else if (masterKey == "18" && (newKey == "23" || newKey == "13" || newKey == "9")) {return true} + else if (masterKey == "13" && (newKey == "18" || newKey == "20" || newKey == "4")) {return true} + else if (masterKey == "20" && (newKey == "13" || newKey == "15" || newKey == "11")) {return true} + else if (masterKey == "15" && (newKey == "20" || newKey == "22" || newKey == "6")) {return true} + else if (masterKey == "22" && (newKey == "15" || newKey == "17" || newKey == "1")) {return true} + else if (masterKey == "17" && (newKey == "22" || newKey == "12" || newKey == "8")) {return true} + else if (masterKey == "12" && (newKey == "17" || newKey == "19" || newKey == "3")) {return true} + else if (masterKey == "19" && (newKey == "12" || newKey == "14" || newKey == "10")) {return true} + else if (masterKey == "14" && (newKey == "19" || newKey == "21" || newKey == "5")) {return true} + else if (masterKey == "0" && (newKey == "5" || newKey == "7" || newKey == "21")) {return true} + else if (masterKey == "7" && (newKey == "0" || newKey == "2" || newKey == "16")) {return true} + else if (masterKey == "2" && (newKey == "7" || newKey == "9" || newKey == "23")) {return true} + else if (masterKey == "9" && (newKey == "2" || newKey == "4" || newKey == "18")) {return true} + else if (masterKey == "4" && (newKey == "9" || newKey == "11" || newKey == "13")) {return true} + else if (masterKey == "11" && (newKey == "4" || newKey == "6" || newKey == "20")) {return true} + else if (masterKey == "6" && (newKey == "11" || newKey == "1" || newKey == "15")) {return true} + else if (masterKey == "1" && (newKey == "6" || newKey == "8" || newKey == "22")) {return true} + else if (masterKey == "8" && (newKey == "1" || newKey == "3" || newKey == "17")) {return true} + else if (masterKey == "3" && (newKey == "8" || newKey == "10" || newKey == "12")) {return true} + else if (masterKey == "10" && (newKey == "3" || newKey == "5" || newKey == "19")) {return true} + else if (masterKey == "5" && (newKey == "10" || newKey == "0" || newKey == "14")) {return true} + else {return false}; + } + + // MappingProperty { id: propShift1; path: "mapping.state.left.shift" } + QtObject { + id: propShift1 + property string description: "Description" + property var value: 0 + } + // MappingProperty { id: propShift2; path: "mapping.state.right.shift" } + QtObject { + id: propShift2 + property string description: "Description" + property var value: 0 + } + readonly property bool isShift: propShift1.value || propShift2.value + readonly property bool isShiftleft: propShift1.value + readonly property bool isShiftRight: propShift2.value + + height: settings.browserFontSize*2 + anchors.left: parent.left + anchors.right: parent.right + + // container for zebra & track infos + Rectangle { + // when changing colors here please remember to change it in the GridView in Templates/Browser.qml + color: (index%2 == 0) ? colors.colorGrey32 : "Black" + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: settings.showTrackTitleColumn ? 3 : 0 + anchors.rightMargin: 3 + height: parent.height + + // folder name + Text { + id: firstFieldFolder + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: contactDelegate.textTopMargin + anchors.leftMargin: 37 + color: textColor + clip: true + text: (dataType == "Folder") ? model.nodeName : "" + font.pixelSize: settings.browserFontSize + elide: Text.ElideRight + visible: (dataType != "Track") + width: 190 + } + + // Image { + // id: prepListIcon + // visible: (dataType == "Track") ? model.prepared : false + // source: "../Images/PrepListIcon" + (!contactDelegate.isCurrentItem ? "White" : "Blue") + ".png" + // width: 10 + // height: 17 + // // anchors.left: firstFieldText.right + // anchors.top: parent.top + // anchors.topMargin: 6 + // anchors.leftMargin: 5 + // } + + // track name + Text { + id: firstFieldTrack + width: settings.swapArtistTitleColumns ? (settings.showArtistColumn ? (settings.hideBPM ? 110 : 77) + (settings.hideKey ? 30 : 0) : 0) + (!settings.showTrackTitleColumn ? 150 : 0) : !settings.showTrackTitleColumn ? 0 : (!settings.showArtistColumn && settings.hideBPM ? 280 : (settings.showArtistColumn ? 150 : 230)) + (!settings.showArtistColumn && settings.hideKey ? 30 : 0) + visible: (dataType == "Track") + anchors.top: parent.top + anchors.topMargin: contactDelegate.textTopMargin + anchors.left: parent.left + anchors.leftMargin: model.prepared ? 10 : 0 + elide: Text.ElideRight + text: settings.swapArtistTitleColumns ? ((dataType == "Track") ? artistName : "") : (settings.showArtistColumn && settings.showTrackTitleColumn ? ((dataType == "Track") ? trackName : "") : (!isShift && !settings.showArtistColumn && settings.showTrackTitleColumn ? ((dataType == "Track") ? trackName : "") : ((dataType == "Track") ? artistName : ""))) + font.pixelSize: settings.browserFontSize + color: isLoaded ? "lime" : ((model.prevPlayed && !model.prelisten) ? "yellow" : (!bpm ? "red" : textColor)) + } + + // artist name + Text { + id: trackTitleField + anchors.leftMargin: settings.showArtistColumn && settings.showTrackTitleColumn ? 4 : 0 + anchors.left: (dataType == "Track") ? firstFieldTrack.right : firstFieldFolder.right + anchors.top: parent.top + anchors.topMargin: contactDelegate.textTopMargin + width: settings.swapArtistTitleColumns ? !settings.showTrackTitleColumn ? 0 : (!settings.showArtistColumn && settings.hideBPM ? 280 : (settings.showArtistColumn ? 150 : 230)) + (!settings.showArtistColumn && settings.hideKey ? 30 : 0) : (settings.showArtistColumn ? (settings.hideBPM ? 110 : 77) + (settings.hideKey ? 30 : 0) : 0) + (!settings.showTrackTitleColumn ? 150 : 0) + color: isLoaded ? "lime" : ((model.prevPlayed && !model.prelisten) ? "yellow" : (!bpm ? "red" : textColor)) + clip: true + text: settings.swapArtistTitleColumns ? (dataType == "Track") ? trackName : "" : (settings.showArtistColumn && settings.showTrackTitleColumn ? ((dataType == "Track") ? artistName : "") : (!isShift && settings.showArtistColumn && !settings.showTrackTitleColumn ? ((dataType == "Track") ? artistName : "") : (dataType == "Track") ? trackName : "")) + font.pixelSize: settings.browserFontSize + elide: Text.ElideRight + } + + // bpm + Text { + id: bpmField + anchors.right: keyField.left + anchors.top: parent.top + anchors.topMargin: contactDelegate.textTopMargin + horizontalAlignment: Text.AlignLeft + width: settings.hideBPM ? 0 :53 + color: settings.bpmBrowserTextColor ? (bpm == "0.00") ? "red" : (bpmMatch <= settings.browserBpmGreen) && (bpmMatch >= -(settings.browserBpmGreen)) ? "lime" : (!((bpmMatch >= settings.browserBpmRed) || (bpmMatch <= -(settings.browserBpmRed)) && (masterBPM != "0.00")) ? textColor : settings.accentColor) : textColor + clip: true + text: (dataType == "Track") ? bpmText : "" + font.pixelSize: settings.browserFontSize + } + + function colorForKey(keyIndex) { + return colors.musicalKeyColors[keyIndex] + } + + // key + Text { + id: keyField + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: contactDelegate.textTopMargin + anchors.leftMargin: 5 + + color: (dataType == "Track") ? (((key == "none") || (key == "None")) ? "White" : ((colorKey(contactDelegate.newIndex, contactDelegate.masterKeyIndex) && contactDelegate.deckPlaying) ? parent.colorForKey(keyIndex) : "White")) : "White" + width: settings.hideKey ? 0 : 30 + clip: true + text: (dataType == "Track") ? (((key == "none") || (key == "None")) ? "n.a." : (settings.camelotKey ? utils.camelotConvert(key) : key)) : "" + font.pixelSize: settings.browserFontSize + } + + ListHighlight { + anchors.fill: parent + visible: contactDelegate.isCurrentItem + anchors.leftMargin: 0 + anchors.rightMargin: 0 + } + + // folder icon + Image { + id: folderIcon + source: (dataType == "Folder") ? ("image://icons/" + model.nodeIconId ) : "" + width: 33 + height: 33 + fillMode: Image.PreserveAspectFit + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 3 + clip: true + cache: false + visible: (dataType == "Folder") + } + + // ColorOverlay { + // id: folderIconColorOverlay + // color: isCurrentItem == false ? colors.colorFontsListBrowser : contactDelegate.deckColor // unselected vs. selected + // anchors.fill: folderIcon + // source: folderIcon + // } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/ListHighlight.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/ListHighlight.qml new file mode 100755 index 000000000000..28dadb00bdb5 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/ListHighlight.qml @@ -0,0 +1,19 @@ +import QtQuick 2.15 + +Item { + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "blue" + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "blue" + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/TrackFooter.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/TrackFooter.qml new file mode 100755 index 000000000000..f6c0213cb83b --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/TrackFooter.qml @@ -0,0 +1,353 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Defines' as Defines +import '../Defines' as Defines +import '../ViewModels' as ViewModels + +//------------------------------------------------------------------------------------------------------------------ +// LIST ITEM - DEFINES THE INFORMATION CONTAINED IN ONE LIST ITEM +//------------------------------------------------------------------------------------------------------------------ +Rectangle { + id: footer + + Defines.Colors { id: colors } + + required property var deckInfo + + property string propertiesPath: "" + property real sortingKnobValue: 0.0 + property bool isContentList: qmlBrowser.isContentList + property int maxCount: 0 + property int count: 0 + + // the given numbers are determined by the EContentListColumns in Traktor + readonly property variant sortIds: [0 ,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + readonly property variant sortNames: ["Sort By #", "Sort By #", "Title", "Artist", "Time", "BPM", "Track #", "Label", "Release", "Label", "Key Text", "Comment", "Lyrics", "Comment 2", "Path", "Analysed", "Remixer", "Producer", "Mix", "CAT #", "Rel. Date", "Bitrate", "Rating", "Count", "Sort By #", "Cover Art", "Last Played", "Import Date", "Key", "Color", "File Name"] + readonly property int selectedFooterId: (selectedFooterItem.value === undefined) ? 0 : ( ( selectedFooterItem.value % 2 === 1 ) ? 1 : 4 ) // selectedFooterItem.value takes values from 1 to 4. + + property real preSortingKnobValue: 0.0 + + //-------------------------------------------------------------------------------------------------------------------- + + // AppProperty { id: previewIsLoaded; path : "app.traktor.browser.preview_player.is_loaded" } + QtObject { + id: previewIsLoaded + property string description: "Description" + property var value: 0 + } + // AppProperty { id: previewTrackLenght; path : "app.traktor.browser.preview_content.track_length" } + QtObject { + id: previewTrackLenght + property string description: "Description" + property var value: 0 + } + // AppProperty { id: previewTrackElapsed; path : "app.traktor.browser.preview_player.elapsed_time" } + QtObject { + id: previewTrackElapsed + property string description: "Description" + property var value: 0 + } + + // MappingProperty { id: overlayState; path: propertiesPath + ".overlay" } + QtObject { + id: overlayState + property string description: "Description" + property var value: 0 + } + // MappingProperty { id: isContentListProp; path: propertiesPath + ".browser.is_content_list" } + QtObject { + id: isContentListProp + property string description: "Description" + property var value: 0 + } + // MappingProperty { id: selectedFooterItem; path: propertiesPath + ".selected_footer_item" } + QtObject { + id: selectedFooterItem + property string description: "Description" + property var value: 0 + } + + //-------------------------------------------------------------------------------------------------------------------- + // Behavior on Sorting Changes (show/hide sorting widget, select next allowed sorting) + //-------------------------------------------------------------------------------------------------------------------- + + onIsContentListChanged: { + // We need this to be able do disable mappings (e.g. sorting ascend/descend) + isContentListProp.value = isContentList; + } + + onSortingKnobValueChanged: { + if (!footer.isContentList) + return; + + overlayState.value = Overlay.sorting; + sortingOverlayTimer.restart(); + + var val = clamp(footer.sortingKnobValue - footer.preSortingKnobValue, -1, 1); + val = parseInt(val); + if (val != 0) { + qmlBrowser.sortingId = getSortingIdWithDelta( val ); + footer.preSortingKnobValue = footer.sortingKnobValue; + } + } + + Timer { + id: sortingOverlayTimer + interval: 800 // duration of the scrollbar opacity + repeat: false + + onTriggered: overlayState.value = Overlay.none; + } + + //-------------------------------------------------------------------------------------------------------------------- + // View + //-------------------------------------------------------------------------------------------------------------------- + + clip: false + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 36 + (settings.raiseBrowserFooter ? 4 : 0) // set in state + color: "transparent" + + // background color + Rectangle { + id: browserFooterBg + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 30 + (settings.raiseBrowserFooter ? 4 : 0) + color: colors.colorBrowserHeader // footer background color + } + + Row { + id: sortingRow + anchors.left: browserFooterBg.left + anchors.leftMargin: 1 + anchors.top: browserFooterBg.top + + Item { + width: 100 + height: 30 + (settings.raiseBrowserFooter ? 4 : 0) + + Text { + font.pixelSize: fonts.scale(15) + anchors.left: parent.left + anchors.leftMargin: 3 + anchors.top: parent.top + anchors.topMargin: 3 + font.capitalization: Font.AllUppercase + color: selectedFooterId == 1 ? "white" : colors.colorFontBrowserHeader + text: getSortingNameForSortId(qmlBrowser.sortingId) + visible: qmlBrowser.isContentList + } + // Arrow (Sorting Direction Indicator) + Triangle { + id: sortDirArrow + width: 15 + height: 15 + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 4 + anchors.rightMargin: 6 + antialiasing: false + visible: qmlBrowser.sortingId > 0 + color: colors.colorGrey80 + rotation: ((qmlBrowser.sortingDirection == 1) ? 0 : 180) + } + Rectangle { + id: divider + height: 30 + width: 1 + color: colors.colorGrey40 // footer divider color + anchors.right: parent.right + } + } + + // Preview Player footer + Item { + width: 150 + height: 30 + + Text { + font.pixelSize: fonts.scale(18) + anchors.left: parent.left + anchors.leftMargin: 1 + anchors.top: parent.top + anchors.topMargin: 3 + font.capitalization: Font.AllUppercase + visible: !previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : "green" + text: deckInfo.masterDeckLetter + } + + Text { + font.pixelSize: fonts.scale(18) + anchors.left: parent.left + anchors.leftMargin: 15 + anchors.top: parent.top + anchors.topMargin: 3 + font.capitalization: Font.AllUppercase + visible: !previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : colors.colorFontBrowserHeader + text: deckInfo.masterBPMFooter2 + } + + Text { + font.pixelSize: fonts.scale(18) + anchors.right: parent.right + anchors.rightMargin: 2 + anchors.top: parent.top + anchors.topMargin: 3 + font.capitalization: Font.AllUppercase + visible: !previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : colors.musicalKeyColorsDark[deckInfo.masterKeyIndex] + text: settings.camelotKey ? utils.camelotConvert(deckInfo.masterKey) : deckInfo.masterKey + } + + Text { + font.pixelSize: fonts.scale(16) + anchors.left: parent.left + anchors.leftMargin: 5 + anchors.top: parent.top + anchors.topMargin: 3 + font.capitalization: Font.AllUppercase + visible: previewIsLoaded.value + color: selectedFooterId == 4 ? "white" : colors.colorFontBrowserHeader + text: "Preview" + } + + // Image { + // anchors.top: parent.top + // anchors.right: parent.right + // anchors.topMargin: 3 + // anchors.rightMargin: 49 + // visible: previewIsLoaded.value + // antialiasing: false + // source: "../Images/PreviewIcon_Small.png" + // fillMode: Image.Pad + // clip: true + // cache: false + // width: 20 + // height: 20 + // } + Text { + width: 40 + clip: true + horizontalAlignment: Text.AlignRight + visible: previewIsLoaded.value + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 2 + anchors.rightMargin: 7 + font.pixelSize: fonts.scale(18) + font.capitalization: Font.AllUppercase + font.family: "Pragmatica" + color: colors.browser.prelisten + text: utils.convertToTimeString(previewTrackElapsed.value) + } + Rectangle { + id: divider2 + height: 30 + width: 1 + color: colors.colorGrey40 // footer divider color + anchors.right: parent.right + } + } + + Item { + + width: 150 + height: 30 + + Text { + Text { + font.pixelSize: fonts.scale(20) + anchors.left: parent.right + anchors.leftMargin: 3 + anchors.top: parent.top + anchors.topMargin: 3 + font.capitalization: Font.AllUppercase + visible: true + color: colors.colorFontBrowserHeader + text: count + } + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // black border & shadow + //-------------------------------------------------------------------------------------------------------------------- + + Rectangle { + id: browserHeaderBottomGradient + height: 3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: browserHeaderBlackBottomLine.top + gradient: Gradient { + GradientStop { position: 0.0; color: colors.colorBlack0 } + GradientStop { position: 1.0; color: colors.colorBlack38 } + } + } + + Rectangle { + id: browserHeaderBlackBottomLine + height: 2 + color: colors.colorBlack + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: browserFooterBg.top + } + + //------------------------------------------------------------------------------------------------------------------ + + state: "show" + states: [ + State { + name: "show" + PropertyChanges { target: footer; height: 36 + (settings.raiseBrowserFooter ? 4 : 0) } + }, + State { + name: "hide" + PropertyChanges { target: footer; height: 0 } + } + ] + + //-------------------------------------------------------------------------------------------------------------------- + // Necessary Functions + //-------------------------------------------------------------------------------------------------------------------- + + function getSortingIdWithDelta( delta ) { + var curPos = getPosForSortId( qmlBrowser.sortingId ); + var pos = curPos + delta; + var count = sortIds.length; + + pos = (pos < 0) ? count-1 : pos; + pos = (pos >= count) ? 0 : pos; + + return sortIds[pos]; + } + + function getPosForSortId(id) { + if (id == -1) return 0; // -1 is a special case which should be interpreted as "0" + for (var i=0; i= 0 && pos < sortNames.length) + return sortNames[pos]; + return "SORTED"; + } + + function clamp(val, min, max) { + return Math.max( Math.min(val, max) , min ); + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/TrackView.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/TrackView.qml new file mode 100755 index 000000000000..7ec1eb1a2f64 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/TrackView.qml @@ -0,0 +1,202 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +//------------------------------------------------------------------------------------------------------------------ +// LIST ITEM - DEFINES THE INFORMATION CONTAINED IN ONE LIST ITEM +//------------------------------------------------------------------------------------------------------------------ + +// the model contains the following roles: +// dataType, nodeIconId, nodeName, nrOfSubnodes, coverUrl, artistName, trackName, bpm, key, keyIndex, rating, loadedInDeck, prevPlayed, prelisten + +Item { + id: contactDelegate + + Defines.Settings {id: settings} + + property string masterBPM: "" + property color deckColor: qmlBrowser.focusColor + property color textColor: !ListView.isCurrentItem ? "White" : deckColor + property bool isCurrentItem: ListView.isCurrentItem + readonly property int textTopMargin: 5 // centers text vertically + readonly property bool isLoaded: (model.dataType == "Track") ? model.loadedInDeck.length > 0 : false + readonly property int rating: (model.dataType == "Track") ? ((model.rating == "") ? 0 : model.rating ) : 0 + // visible: !ListView.isCurrentItem + readonly property string bpm: (model.bpm || 0).toFixed(2).toString() + + readonly property string bpmMatch: tempoNeeded(masterBPM, bpm).toFixed(2).toString() + + function tempoNeeded(master, current) { + if (master > current) { + + return (1-(current/master))*100; + + } else if (master < current) { + + return ((master/current)-1)*100; + } + } + + // MappingProperty { id: propShift1; path: "mapping.state.left.shift" } + QtObject { + id: propShift1 + property string description: "Description" + property var value: 0 + } + // MappingProperty { id: propShift2; path: "mapping.state.right.shift" } + QtObject { + id: propShift2 + property string description: "Description" + property var value: 0 + } + readonly property bool isShift: propShift1.value || propShift2.value + readonly property bool isShiftleft: propShift1.value + readonly property bool isShiftRight: propShift2.value + + height: 240 + anchors.left: parent.left + anchors.right: parent.right + + // container for zebra & track infos + Rectangle { + // when changing colors here please remember to change it in the GridView in Templates/Browser.qml + color: (index%2 == 0) ? colors.colorGrey32 : "Black" + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: settings.showTrackTitleColumn ? 3 : 0 + anchors.rightMargin: 3 + height: parent.height + + // track name + Text { + id: firstFieldTrack + width: 300 + clip: true + anchors.top: trackImage.bottom + anchors.topMargin: contactDelegate.textTopMargin + anchors.left: parent.left + anchors.leftMargin: 5 + text: (model.dataType == "Track") ? model.trackName : "" + font.pixelSize: 20 + color: isLoaded ? "lime" : ((model.prevPlayed && !model.prelisten) ? "yellow" : (((model.bpm || 0).toFixed(4) == "0.0000" ) ? "red" : textColor)) + } + + // artist name + Text { + id: trackTitleField + anchors.top: firstFieldTrack.bottom + anchors.topMargin: contactDelegate.textTopMargin + anchors.left: parent.left + anchors.leftMargin: 5 + width: 300 + color: isLoaded ? "lime" : ((model.prevPlayed && !model.prelisten) ? "yellow" : (((model.bpm || 0).toFixed(4) == "0.0000" ) ? "red" : textColor)) + clip: true + text: (model.dataType == "Track") ? model.artistName : "" + font.pixelSize: 20 + } + + //bpm text + Text { + id: bpmFieldText + anchors.top: parent.top + anchors.left: trackImage.right + anchors.leftMargin: 5 + anchors.topMargin: 5 + horizontalAlignment: Text.AlignLeft + + color: "white" + text: "BPM:" + font.pixelSize: 28 + } + + //bpm + Text { + id: bpmField + anchors.top: parent.top + anchors.rightMargin: 0 + anchors.right: parent.right + anchors.topMargin: 5 + horizontalAlignment: Text.AlignRight + + color: settings.bpmBrowserTextColor ? (bpm == "0.00") ? "red" : (bpmMatch <= settings.browserBpmGreen) && (bpmMatch >= -(settings.browserBpmGreen)) ? "lime" : (!((bpmMatch >= settings.browserBpmRed) || (bpmMatch <= -(settings.browserBpmRed)) && (masterBPM != "0.00")) ? textColor : settings.accentColor) : textColor + clip: true + text: (model.dataType == "Track") ? bpm : "" + font.pixelSize: 30 + } + + function colorForKey(keyIndex) { + return colors.musicalKeyColors[keyIndex] + } + + // key text + Text { + id: keyFieldText + anchors.top: bpmField.bottom + anchors.left: trackImage.right + anchors.topMargin: 8 + anchors.leftMargin: 5 + + color: "white" + clip: true + text: "Key:" + font.pixelSize: 30 + } + + // key + Text { + id: keyField + anchors.top: bpmField.bottom + anchors.right: parent.right + anchors.topMargin: 8 + anchors.rightMargin: 0 + + color: (model.dataType == "Track") ? (((model.key == "none") || (model.key == "None")) ? textColor : parent.colorForKey(model.keyIndex)) : textColor + clip: true + text: (model.dataType == "Track") ? (((model.key == "none") || (model.key == "None")) ? "n.a." : (settings.camelotKey ? utils.camelotConvert(model.key) : model.key)) : "" + font.pixelSize: 30 + } + + Widgets.TrackRating { + id: trackRating + + anchors.top: keyFieldText.bottom + anchors.left: trackImage.right + anchors.topMargin: 8 + anchors.leftMargin: 5 + rating: (model.dataType == "Track") ? ((model.rating == "") ? 0 : model.rating ) : 0 + } + + ListHighlight { + anchors.fill: parent + visible: contactDelegate.isCurrentItem + anchors.leftMargin: 0 + anchors.rightMargin: 0 + } + + Rectangle { + id: trackImage + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 5 + anchors.topMargin: 10 + width: 125 + height: 125 + color: (model.coverUrl != "") ? "transparent" : ((contactDelegate.screenFocus < 2) ? colors.colorDeckBlueBright50Full : colors.colorGrey128 ) + visible: (model.dataType == "Track") && !settings.hideAlbumArt + + Image { + id: cover + anchors.fill: parent + source: (model.dataType == "Track") ? ("image://covers/" + model.coverUrl ) : "" + fillMode: Image.PreserveAspectFit + clip: true + cache: false + sourceSize.width: width + sourceSize.height: height + // the image either provides the cover of the track, or if not available the traktor logo on colored background ( opacity == 0.3) + opacity: (model.coverUrl != "") ? 1.0 : 0.3 + } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/Triangle.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/Triangle.qml new file mode 100755 index 000000000000..97cbe25ab4da --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Browser/Triangle.qml @@ -0,0 +1,29 @@ +import QtQuick 2.15 +import QtQuick.Shapes 1.7 + +Item { + id: root + + property int borderWidth: 0 + property color color: "black" + property color borderColor: "transparent" + + property alias antialiasing: triangle.antialiasing + + clip: false + + Shape { + id: triangle + anchors.centerIn: parent + + ShapePath { + strokeWidth: root.borderWidth + strokeColor: root.borderColor + fillColor: root.color + startX: 0; startY: 0 + PathLine { x: root.width; y: 0 } + PathLine { x: 0.5* root.width; y: root.height } + PathLine { x: 0; y: 0 } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/DeckScreen.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/DeckScreen.qml new file mode 100755 index 000000000000..bb4fccc836f8 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/DeckScreen.qml @@ -0,0 +1,185 @@ +import QtQuick 2.15 +import './Defines' as Dfeines +import './Views' as Views +import './ViewModels' as ViewModels +import './Overlays' as Overlays + +Item { + id: deckscreen + + property int deckId: 1 + + property bool active: true + + //-------------------------------------------------------------------------------------------------------------------- + // Deck Screen: show information for track, stem, remix decks + //-------------------------------------------------------------------------------------------------------------------- + QtObject { + id: deckType + property string description: deckInfoModel.isStemsActive ? "Stem Deck" : "Track Deck" + property var value: 1 + } + + QtObject { + id: propShift1 + property var value: false + } + QtObject { + id: propShift2 + property var value: false + } + readonly property bool isShift: propShift1.value || propShift2.value + + property bool browser: settings.showBrowserOnFavorites ? ((deckInfoModel.viewButton) || (deckInfoModel.favorites)) : (deckInfoModel.viewButton) + + Component.onCompleted: { + if (typeof engine.makeSharedDataConnection === "function") { + engine.makeSharedDataConnection(deckscreen.onSharedDataUpdate) + deckscreen.onSharedDataUpdate(engine.getSharedData()) + } + } + + function isLeftScreen(deckId) { + return deckId == 1 || deckId == 3; + } + + function onSharedDataUpdate(data) { + if (typeof data === "object" && typeof data.shift === "object") { + propShift1.value = !!data.shift["leftdeck"] + propShift2.value = !!data.shift["rightdeck"] + } + } + + ViewModels.DeckInfo { + id: deckInfoModel + deckId: deckscreen.deckId + } + + Component { + id: emptyDeckComponent; + + Views.EmptyDeck { + anchors.fill: parent + deckInfo: deckInfoModel + } + } + + Component { + id: trackDeckComponent; + Views.TrackDeck { + id: trackDeck + deckInfo: deckInfoModel + deckId: deckscreen.deckId + anchors.fill: parent + } + } + + Component { + id: browserComponent; + Views.BrowserView { + id: browserView + deckInfo: deckInfoModel + anchors.fill: parent + isActive: (loader.sourceComponent == browserComponent) && deckscreen.active + } + } + + Component { + id: stemDeckComponent; + + Views.StemDeck { + deckInfo: deckInfoModel + anchors.fill: parent + } + } + + Loader { + id: loader + active: true + visible: true + anchors.fill: parent + sourceComponent: trackDeckComponent + } + + Item { + id: content + state: "Empty Deck" + + Component.onCompleted: { + content.state = Qt.binding(function() { + return (browser && settings.enableBrowserMode) ? "Browser" : deckType.description }); + } + + states: [ + State { + name: "Empty Deck" + PropertyChanges { target: loader; sourceComponent: emptyDeckComponent } + }, + State { + name: "Track Deck" + PropertyChanges { target: loader; sourceComponent: trackDeckComponent } + }, + State { + name: "Browser" + PropertyChanges { target: loader; sourceComponent: browserComponent } + }, + State { + name: "Stem Deck" + PropertyChanges { target: loader; sourceComponent: stemDeckComponent } + } + ] + } + + Overlays.GridControls { + id: grid + deckId: deckInfoModel.deckId + showHideState: !settings.hideGridOverlay && deckInfoModel.adjustEnabled && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.BankInfo { + id: bank1; + bank: 1 + showHideState: deckInfoModel.padsModeBank1 && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.BankInfo { + id: bank2; + bank: 2 + showHideState: deckInfoModel.padsModeBank2 && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.CueInfo { + id: cue + hotcue: deckInfoModel.hotcueId + type: deckInfoModel.hotcueType + name: deckInfoModel.hotcueName + showHideState: !settings.hideHotcueOverlay && deckInfoModel.hotcueDisplay && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.JumpControls { + id: jump; + deckInfo: deckInfoModel + showHideState: !settings.hideJumpOverlay && deckInfoModel.padsModeJump && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.LoopControls { + id: loop; + deckInfo: deckInfoModel + deckId: deckInfoModel.deckId + showHideState: !settings.hideLoopOverlay && deckInfoModel.padsModeLoop && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.RollControls { + id: roll; + deckInfo: deckInfoModel + deckId: deckInfoModel.deckId + showHideState: !settings.hideRollOverlay && deckInfoModel.padsModeRoll && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } + + Overlays.ToneControls { + id: tone; + deckId: deckInfoModel.deckId + adjustVal: deckInfoModel.keyAdjustVal + showHideState: !settings.hideToneOverlay && deckInfoModel.padsModeTone && !(loader.sourceComponent == browserComponent) ? "show" : "hide" + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Colors.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Colors.qml new file mode 100755 index 000000000000..ab2c0ceb77da --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Colors.qml @@ -0,0 +1,549 @@ +import QtQuick 2.15 + +QtObject { + + function rgba(r,g,b,a) { return Qt.rgba( neutralizer(r)/255. , neutralizer(g)/255. , neutralizer(b)/255. , neutralizer(a)/255. ) } + + // this categorizes any rgb value to multiples of 8 for each channel to avoid unbalanced colors on the display (r5-g6-b5 bit) + // function neutralizer(value) { if(value%8 > 4) { return value - value%8 + 8} else { return value - value%8 }} + function neutralizer(value) { return value} + + property variant colorBlack: rgba (0, 0, 0, 255) + property variant colorBlack94: rgba (0, 0, 0, 240) + property variant colorBlack88: rgba (0, 0, 0, 224) + property variant colorBlack85: rgba (0, 0, 0, 217) + property variant colorBlack81: rgba (0, 0, 0, 207) + property variant colorBlack78: rgba (0, 0, 0, 199) + property variant colorBlack75: rgba (0, 0, 0, 191) + property variant colorBlack69: rgba (0, 0, 0, 176) + property variant colorBlack66: rgba (0, 0, 0, 168) + property variant colorBlack63: rgba (0, 0, 0, 161) + property variant colorBlack60: rgba (0, 0, 0, 153) // from 59 - 61% + property variant colorBlack56: rgba (0, 0, 0, 143) // + property variant colorBlack53: rgba (0, 0, 0, 135) // from 49 - 51% + property variant colorBlack50: rgba (0, 0, 0, 128) // from 49 - 51% + property variant colorBlack47: rgba (0, 0, 0, 120) // from 46 - 48% + property variant colorBlack44: rgba (0, 0, 0, 112) // from 43 - 45% + property variant colorBlack41: rgba (0, 0, 0, 105) // from 40 - 42% + property variant colorBlack38: rgba (0, 0, 0, 97) // from 37 - 39% + property variant colorBlack35: rgba (0, 0, 0, 89) // from 33 - 36% + property variant colorBlack31: rgba (0, 0, 0, 79) // from 30 - 32% + property variant colorBlack28: rgba (0, 0, 0, 71) // from 27 - 29% + property variant colorBlack25: rgba (0, 0, 0, 64) // from 24 - 26% + property variant colorBlack22: rgba (0, 0, 0, 56) // from 21 - 23% + property variant colorBlack19: rgba (0, 0, 0, 51) // from 18 - 20% + property variant colorBlack16: rgba (0, 0, 0, 41) // from 15 - 17% + property variant colorBlack12: rgba (0, 0, 0, 31) // from 11 - 13% + property variant colorBlack09: rgba (0, 0, 0, 23) // from 8 - 10% + property variant colorBlack0: rgba (0, 0, 0, 0) + + property variant colorWhite: rgba (255, 255, 255, 255) + + property variant colorWhite75: rgba (255, 255, 255, 191) + property variant colorWhite85: rgba (255, 255, 255, 217) + + // property variant colorWhite60: rgba (255, 255, 255, 153) // from 59 - 61% + property variant colorWhite50: rgba (255, 255, 255, 128) // from 49 - 51% + // property variant colorWhite47: rgba (255, 255, 255, 120) // from 46 - 48% + // property variant colorWhite44: rgba (255, 255, 255, 112) // from 43 - 45% + property variant colorWhite41: rgba (255, 255, 255, 105) // from 40 - 42% + // property variant colorWhite38: rgba (255, 255, 255, 97) // from 37 - 39% + property variant colorWhite35: rgba (255, 255, 255, 89) // from 33 - 36% + // property variant colorWhite31: rgba (255, 255, 255, 79) // from 30 - 32% + property variant colorWhite28: rgba (255, 255, 255, 71) // from 27 - 29% + property variant colorWhite25: rgba (255, 255, 255, 64) // from 24 - 26% + property variant colorWhite22: rgba (255, 255, 255, 56) // from 21 - 23% + property variant colorWhite19: rgba (255, 255, 255, 51) // from 18 - 20% + property variant colorWhite16: rgba (255, 255, 255, 41) // from 15 - 17% + property variant colorWhite12: rgba (255, 255, 255, 31) // from 11 - 13% + property variant colorWhite09: rgba (255, 255, 255, 23) // from 8 - 10% + // property variant colorWhite06: rgba (255, 255, 255, 15) // from 5 - 7% + // property variant colorWhite03: rgba (255, 255, 255, 8) // from 2 - 4% + + property variant colorGrey232: rgba (232, 232, 232, 255) + property variant colorGrey216: rgba (216, 216, 216, 255) + property variant colorGrey208: rgba (208, 208, 208, 255) + property variant colorGrey200: rgba (200, 200, 200, 255) + property variant colorGrey192: rgba (192, 192, 192, 255) + property variant colorGrey152: rgba (152, 152, 152, 255) + property variant colorGrey128: rgba (128, 128, 128, 255) + property variant colorGrey120: rgba (120, 120, 120, 255) + property variant colorGrey112: rgba (112, 112, 112, 255) + property variant colorGrey104: rgba (104, 104, 104, 255) + property variant colorGrey96: rgba (96, 96, 96, 255) + property variant colorGrey88: rgba (88, 88, 88, 255) + property variant colorGrey80: rgba (80, 80, 80, 255) + property variant colorGrey72: rgba (72, 72, 72, 255) + property variant colorGrey64: rgba (64, 64, 64, 255) + property variant colorGrey56: rgba (56, 56, 56, 255) + property variant colorGrey48: rgba (48, 48, 48, 255) + property variant colorGrey40: rgba (40, 40, 40, 255) + property variant colorGrey32: rgba (32, 32, 32, 255) + property variant colorGrey24: rgba (24, 24, 24, 255) + property variant colorGrey16: rgba (16, 16, 16, 255) + property variant colorGrey08: rgba (08, 08, 08, 255) + + property variant cueColors: [ + red, + darkOrange, + lightOrange, + colorWhite, + yellow, + lime, + green, + mint, + cyan, + turquoise, + blue, + plum, + violet, + purple, + magenta, + fuchsia, + warmYellow + ] + + property variant cueColorsDark: [ + Qt.darker(red, 0.15), + Qt.darker(darkOrange, 0.15), + Qt.darker(lightOrange, 0.15), + Qt.darker(colorWhite, 0.15), + Qt.darker(yellow, 0.15), + Qt.darker(lime, 0.15), + Qt.darker(green, 0.15), + Qt.darker(mint, 0.15), + Qt.darker(cyan, 0.15), + Qt.darker(turquoise, 0.15), + Qt.darker(blue, 0.15), + Qt.darker(plum, 0.15), + Qt.darker(violet, 0.15), + Qt.darker(purple, 0.15), + Qt.darker(magenta, 0.15), + Qt.darker(fuchsia, 0.15), + Qt.darker(warmYellow, 0.15) + ] + + property variant colorOrange: rgba(208, 104, 0, 255) // FX Selection; FX Faders etc + property variant colorOrangeDimmed: rgba(96, 48, 0, 255) + + property variant colorRed: rgba(255, 0, 0, 255) + property variant colorRed70: rgba(185, 6, 6, 255) + + // Playmarker + property variant colorRedPlaymarker: rgba(255, 0, 0, 255) + property variant colorRedPlaymarker75: rgba(255, 56, 26, 191) + property variant colorRedPlaymarker06: rgba(255, 56, 26, 31) + + // Playmarker + property variant colorBluePlaymarker: rgba(96, 184, 192, 255) //rgba(136, 224, 232, 255) + + property variant colorGreen: rgba(0, 255, 0, 255) + property variant colorGreen50: rgba(0, 255, 0, 128) + property variant colorGreen12: rgba(0, 255, 0, 31) // used for loop bg (in WaveformCues.qml) + property variant colorGreenLoopOverlay: rgba(96, 192, 128, 16) + property variant colorGreenMint: rgba(0, 219, 138, 255) + + property variant colorGreen08: rgba(0, 255, 0, 20) + property variant colorGreen50Full: rgba(0, 51, 0, 255) + + property variant colorGreenGreyMix: rgba(139, 240, 139, 82) + + // font colors + property variant colorFontsListBrowser: colorGrey72 + property variant colorFontsListFx: colorGrey56 + property variant colorFontBrowserHeader: colorGrey88 + property variant colorFontFxHeader: colorGrey80 // also for FX header, FX select buttons + + // headers & footers backgrounds + property variant colorBgEmpty: colorGrey16 // also for empty decks & Footer Small (used to be colorGrey08) + property variant colorBrowserHeader: colorGrey24 + property variant colorFxHeaderBg: colorGrey16 // also for large footer; fx overlay tabs + property variant colorFxHeaderLightBg: colorGrey24 + + property variant colorProgressBg: colorGrey32 + property variant colorProgressBgLight: colorGrey48 + property variant colorDivider: colorGrey40 + + property variant colorIndicatorBg: rgba(20, 20, 20, 255) + property variant colorIndicatorBg2: rgba(31, 31, 31, 255) + + property variant colorIndicatorLevelGrey: rgba(51, 51, 51, 255) + property variant colorIndicatorLevelOrange: rgba(247, 143, 30, 255) + + property variant colorCenterOverlayHeadline: colorGrey88 + +// blue + property variant colorDeckBlueBright: rgba(0, 136, 184, 255) + property variant colorDeckBlueDark: rgba(0, 64, 88, 255) + property variant colorDeckBlueBright20: rgba(0, 174, 239, 51) + property variant colorDeckBlueBright50Full: rgba(0, 87, 120, 255) + property variant colorDeckBlueBright12Full: rgba(0, 8, 10, 255) //rgba(0, 23, 31, 255) + property variant colorBrowserBlueBright: rgba(0, 187, 255, 255) + property variant colorBrowserBlueBright56Full:rgba(0, 114, 143, 255) + + property color footerBackgroundBlue: "#011f26" + + // fx Select overlay colors + property variant fxSelectHeaderTextRGB: rgba( 96, 96, 96, 255) + property variant fxSelectHeaderNormalRGB: rgba( 32, 32, 32, 255) + property variant fxSelectHeaderNormalBorderRGB: rgba( 32, 32, 32, 255) + property variant fxSelectHeaderHighlightRGB: rgba( 64, 64, 48, 255) + property variant fxSelectHeaderHighlightBorderRGB: rgba(128, 128, 48, 255) + + // 16 Colors Palette (Bright) + property variant color01Bright: rgba (255, 0, 0, 255) + property variant color02Bright: rgba (255, 16, 16, 255) + property variant color03Bright: rgba (255, 120, 0, 255) + property variant color04Bright: rgba (255, 184, 0, 255) + property variant color05Bright: rgba (255, 255, 0, 255) + property variant color06Bright: rgba (144, 255, 0, 255) + property variant color07Bright: rgba ( 40, 255, 40, 255) + property variant color08Bright: rgba ( 0, 208, 128, 255) + property variant color09Bright: rgba ( 0, 184, 232, 255) + property variant color10Bright: rgba ( 0, 120, 255, 255) + property variant color11Bright: rgba ( 0, 72, 255, 255) + property variant color12Bright: rgba (128, 0, 255, 255) + property variant color13Bright: rgba (160, 0, 200, 255) + property variant color14Bright: rgba (240, 0, 200, 255) + property variant color15Bright: rgba (255, 0, 120, 255) + property variant color16Bright: rgba (248, 8, 64, 255) + + // 16 Colors Palette (Mid) + property variant color01Mid: rgba (112, 8, 8, 255) + property variant color02Mid: rgba (112, 24, 8, 255) + property variant color03Mid: rgba (112, 56, 0, 255) + property variant color04Mid: rgba (112, 80, 0, 255) + property variant color05Mid: rgba (96, 96, 0, 255) + property variant color06Mid: rgba (56, 96, 0, 255) + property variant color07Mid: rgba (8, 96, 8, 255) + property variant color08Mid: rgba (0, 90, 60, 255) + property variant color09Mid: rgba (0, 77, 77, 255) + property variant color10Mid: rgba (0, 84, 108, 255) + property variant color11Mid: rgba (32, 56, 112, 255) + property variant color12Mid: rgba (72, 32, 120, 255) + property variant color13Mid: rgba (80, 24, 96, 255) + property variant color14Mid: rgba (111, 12, 149, 255) + property variant color15Mid: rgba (122, 0, 122, 255) + property variant color16Mid: rgba (130, 1, 43, 255) + + // 16 Colors Palette (Dark) + property variant color01Dark: rgba (16, 0, 0, 255) + property variant color02Dark: rgba (16, 8, 0, 255) + property variant color03Dark: rgba (16, 8, 0, 255) + property variant color04Dark: rgba (16, 16, 0, 255) + property variant color05Dark: rgba (16, 16, 0, 255) + property variant color06Dark: rgba (8, 16, 0, 255) + property variant color07Dark: rgba (8, 16, 8, 255) + property variant color08Dark: rgba (0, 16, 8, 255) + property variant color09Dark: rgba (0, 8, 16, 255) + property variant color10Dark: rgba (0, 8, 16, 255) + property variant color11Dark: rgba (0, 0, 16, 255) + property variant color12Dark: rgba (8, 0, 16, 255) + property variant color13Dark: rgba (8, 0, 16, 255) + property variant color14Dark: rgba (16, 0, 16, 255) + property variant color15Dark: rgba (16, 0, 8, 255) + property variant color16Dark: rgba (16, 0, 8, 255) + + //-------------------------------------------------------------------------------------------------------------------- + + // Browser + + //-------------------------------------------------------------------------------------------------------------------- + + property variant browser: + QtObject { + property color prelisten: rgba(223, 178, 30, 255) + property color prevPlayed: rgba(32, 32, 32, 255) + } + + //-------------------------------------------------------------------------------------------------------------------- + + // Hotcues + + //-------------------------------------------------------------------------------------------------------------------- + + property variant hotcue: + QtObject { + property color grid: colorWhite + property color hotcue: colorDeckBlueBright + property color fade: color03Bright + property color load: color05Bright + property color loop: color07Bright + property color temp: "grey" + } + + //-------------------------------------------------------------------------------------------------------------------- + + // Freeze & Slicer + + //-------------------------------------------------------------------------------------------------------------------- + + property variant freeze: + QtObject { + property color box_inactive: "#199be7ef" + property color box_active: "#ff9be7ef" + property color marker: "#4DFFFFFF" + property color slice_overlay: "white" // flashing rectangle + } + + property variant slicer: + QtObject { + property color box_active: rgba(20,195,13,255) + property color box_inrange: rgba(20,195,13,90) + property color box_inactive: rgba(20,195,13,25) + property color marker_default: rgba(20,195,13,77) + property color marker_beat: rgba(20,195,13,150) + property color marker_edge: box_active + } + + //-------------------------------------------------------------------------------------------------------------------- + + // Musical Key coloring for the browser + + //-------------------------------------------------------------------------------------------------------------------- + property variant color01MusicalKey: rgba (255, 0, 0, 255) // not yet in use + property variant color02MusicalKey: rgba (255, 64, 0, 255) + property variant color03MusicalKey: rgba (255, 120, 0, 255) // not yet in use + property variant color04MusicalKey: rgba (255, 200, 0, 255) + property variant color05MusicalKey: rgba (255, 255, 0, 255) + property variant color06MusicalKey: rgba (210, 255, 0, 255) // not yet in use + property variant color07MusicalKey: rgba ( 0, 255, 0, 255) + property variant color08MusicalKey: rgba ( 0, 255, 128, 255) + //property variant color09MusicalKey: rgba ( 0, 200, 232, 255) + property variant color09MusicalKey: colorDeckBlueBright // use the same color as for the browser selection + property variant color10MusicalKey: rgba ( 0, 100, 255, 255) + property variant color11MusicalKey: rgba ( 0, 40, 255, 255) + property variant color12MusicalKey: rgba (128, 0, 255, 255) + property variant color13MusicalKey: rgba (160, 0, 200, 255) // not yet in use + property variant color14MusicalKey: rgba (240, 0, 200, 255) + property variant color15MusicalKey: rgba (255, 0, 120, 255) // not yet in use + property variant color16MusicalKey: rgba (248, 8, 64, 255) + + property variant color01MusicalKey2: rgba (255, 0, 0, 120) // not yet in use + property variant color02MusicalKey2: rgba (255, 64, 0, 120) + property variant color03MusicalKey2: rgba (255, 120, 0, 120) // not yet in use + property variant color04MusicalKey2: rgba (255, 200, 0, 120) + property variant color05MusicalKey2: rgba (255, 255, 0, 120) + property variant color06MusicalKey2: rgba (210, 255, 0, 120) // not yet in use + property variant color07MusicalKey2: rgba ( 0, 255, 0, 120) + property variant color08MusicalKey2: rgba ( 0, 255, 128, 120) + //property variant color09MusicalKey2: rgba ( 0, 200, 232, 120) + property variant color09MusicalKey2: colorDeckBlueBright // use the same color as for the browser selection + property variant color10MusicalKey2: rgba ( 0, 100, 255, 120) + property variant color11MusicalKey2: rgba ( 0, 40, 255, 120) + property variant color12MusicalKey2: rgba (128, 0, 255, 120) + property variant color13MusicalKey2: rgba (160, 0, 200, 120) // not yet in use + property variant color14MusicalKey2: rgba (240, 0, 200, 120) + property variant color15MusicalKey2: rgba (255, 0, 120, 120) // not yet in use + property variant color16MusicalKey2: rgba (248, 8, 64, 120) + + // 16 Colors Palette (Bright) + property variant color01Bright2: rgba (255, 0, 0, 120) + property variant color02Bright2: rgba (255, 16, 16, 120) + property variant color03Bright2: rgba (255, 120, 0, 120) + property variant color04Bright2: rgba (255, 184, 0, 120) + property variant color05Bright2: rgba (255, 255, 0, 120) + property variant color06Bright2: rgba (144, 255, 0, 120) + property variant color07Bright2: rgba ( 40, 255, 40, 120) + property variant color08Bright2: rgba ( 0, 208, 128, 120) + property variant color09Bright2: rgba ( 0, 184, 232, 120) + property variant color10Bright2: rgba ( 0, 120, 255, 120) + property variant color11Bright2: rgba ( 0, 72, 255, 120) + property variant color12Bright2: rgba (128, 0, 255, 120) + property variant color13Bright2: rgba (160, 0, 200, 120) + property variant color14Bright2: rgba (240, 0, 200, 120) + property variant color15Bright2: rgba (255, 0, 120, 120) + property variant color16Bright2: rgba (248, 8, 64, 120) + + property variant musicalKeyColors: [ + 'grey', //0 No key + color15Bright, //1 -11 c + color06Bright, //2 -4 c#, db + color11MusicalKey, //3 -13 d + color03Bright, //4 -6 d#, eb + color09MusicalKey, //5 -16 e + color01Bright, //6 -9 f + color07MusicalKey, //7 -2 f#, gb + color13Bright, //8 -12 g + color04MusicalKey, //9 -5 g#, ab + color10MusicalKey, //10 -15 a + color02MusicalKey, //11 -7 a#, bb + color08MusicalKey, //12 -1 b + color03Bright, //13 -6 cm + color09MusicalKey, //14 -16 c#m, dbm + color01Bright, //15 -9 dm + color07MusicalKey, //16 -2 d#m, ebm + color13Bright, //17 -12 em + color04MusicalKey, //18 -5 fm + color10MusicalKey, //19 -15 f#m, gbm + color02MusicalKey, //20 -7 gm + color08MusicalKey, //21 -1 g#m, abm + color15Bright, //22 -11 am + color06Bright, //23 -4 a#m, bbm + color11MusicalKey //24 -13 bm + ] + + property variant musicalKeyColorsDark: [ + 'grey', //0 No key + Qt.darker(color15Bright, 5), //1 -11 c + Qt.darker(color06Bright, 5), //2 -4 c#, db + Qt.darker(color11MusicalKey, 5), //3 -13 d + Qt.darker(color03Bright, 5), //4 -6 d#, eb + Qt.darker(color09MusicalKey, 5), //5 -16 e + Qt.darker(color01Bright, 5), //6 -9 f + Qt.darker(color07MusicalKey, 5), //7 -2 f#, gb + Qt.darker(color13Bright, 5), //8 -12 g + Qt.darker(color04MusicalKey, 5), //9 -5 g#, ab + Qt.darker(color10MusicalKey, 5), //10 -15 a + Qt.darker(color02MusicalKey, 5), //11 -7 a#, bb + Qt.darker(color08MusicalKey, 5), //12 -1 b + Qt.darker(color03Bright, 5), //13 -6 cm + Qt.darker(color09MusicalKey, 5), //14 -16 c#m, dbm + Qt.darker(color01Bright, 5), //15 -9 dm + Qt.darker(color07MusicalKey, 5), //16 -2 d#m, ebm + Qt.darker(color13Bright, 5), //17 -12 em + Qt.darker(color04MusicalKey, 5), //18 -5 fm + Qt.darker(color10MusicalKey, 5), //19 -15 f#m, gbm + Qt.darker(color02MusicalKey, 5), //20 -7 gm + Qt.darker(color08MusicalKey, 5), //21 -1 g#m, abm + Qt.darker(color15Bright, 5), //22 -11 am + Qt.darker(color06Bright, 5), //23 -4 a#m, bbm + Qt.darker(color11MusicalKey, 5) //24 -13 bm + ] + + //-------------------------------------------------------------------------------------------------------------------- + + // Waveform coloring + + //-------------------------------------------------------------------------------------------------------------------- + + property color defaultBackground: "black" + property color defaultTextColor: "white" + property color loopActiveColor: rgba(0,255,70,255) + property color loopFlashColor: rgba ( 20, 235, 165, 120) + + property color loopActiveDimmedColor: rgba(0,255,70,190) + property color grayBackground: "#ff333333" + + property variant colorDeckBrightGrey: rgba (85, 85, 85, 255) + property variant colorDeckGrey: rgba (70, 70, 70, 255) + property variant colorDeckDarkGrey: rgba (40, 40, 40, 255) + + property variant colorDeckOrangeBright: rgba (253, 186, 16, 255) + + property variant colorQuantizeOn: rgba ( 20, 255, 255, 170) + property variant colorQuantizeOff: Qt.darker(colorQuantizeOn, 0.7) + + property color red: "#ff0000" + property color darkOrange: "#ff8c00" + property color lightOrange: "#fccf3e" + property color warmYellow: "#f9d71c" + property color yellow: "#ffff00" + property color lime: "#effd5f" + property color green: "#00FF00" + property color mint: "#98ff98" + property color cyan: "#00FFFF" + property color turquoise: "#40e0d0" + property color blue: "#0080FF" + property color plum: "#ff7eff" + property color violet: "#ee82ee" + property color purple: "#9f00c5" + property color magenta: "#ff6fff" + property color fuchsia: "#ff0080" + property color white: "#ff0080" + property color phaseColor: "#90550C" + + //-------------------------------------------------------------------------------------------------------------------- + + // Waveform coloring + + //-------------------------------------------------------------------------------------------------------------------- + + property variant low1: settings.low1 + property variant low2: settings.low2 + property variant mid1: settings.mid1 + property variant mid2: settings.mid2 + property variant high1: settings.high1 + property variant high2: settings.high2 + + function getWaveformColors(colorId) { + if (colorId <= 17) { + return waveformColorsMap[colorId]; + } + + return waveformColorsMap[0]; + } + + function palette(brightness, colorId) { + if ( brightness >= 0.666 && brightness <= 1.0 ) { // bright color + switch(colorId) { + case 0: return defaultBackground // default color for this palette! + case 1: return color01Bright + case 2: return color02Bright + case 3: return color03Bright + case 4: return color04Bright + case 5: return color05Bright + case 6: return color06Bright + case 7: return color07Bright + case 8: return color08Bright + case 9: return color09Bright + case 10: return color10Bright + case 11: return color11Bright + case 12: return color12Bright + case 13: return color13Bright + case 14: return color14Bright + case 15: return color15Bright + case 16: return color16Bright + case 17: return "grey" + case 18: return colorGrey232 + } + } else if ( brightness >= 0.333 && brightness < 0.666 ) { // mid color + switch(colorId) { + case 0: return defaultBackground // default color for this palette! + case 1: return color01Mid + case 2: return color02Mid + case 3: return color03Mid + case 4: return color04Mid + case 5: return color05Mid + case 6: return color06Mid + case 7: return color07Mid + case 8: return color08Mid + case 9: return color09Mid + case 10: return color10Mid + case 11: return color11Mid + case 12: return color12Mid + case 13: return color13Mid + case 14: return color14Mid + case 15: return color15Mid + case 16: return color16Mid + case 17: return "grey" + case 18: return colorGrey232 + } + } else if ( brightness >= 0 && brightness < 0.333 ) { // dimmed color + switch(colorId) { + case 0: return defaultBackground // default color for this palette! + case 1: return color01Dark + case 2: return color02Dark + case 3: return color03Dark + case 4: return color04Dark + case 5: return color05Dark + case 6: return color06Dark + case 7: return color07Dark + case 8: return color08Dark + case 9: return color09Dark + case 10: return color10Dark + case 11: return color11Dark + case 12: return color12Dark + case 13: return color13Dark + case 14: return color14Dark + case 15: return color15Dark + case 16: return color16Dark + case 17: return "grey" + case 18: return colorGrey232 + } + } else if ( brightness < 0) { // color Off + return defaultBackground; + } + return defaultBackground; // default color if no palette is set + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Durations.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Durations.qml new file mode 100755 index 000000000000..d91643a23e39 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Durations.qml @@ -0,0 +1,8 @@ +import QtQuick 2.15 + +QtObject { + readonly property int mainTransitionSpeed: 100 + readonly property int overlayTransition: 100 + readonly property int bottomInfoColor: 75 + readonly property int deckTransition: 90 +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Font.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Font.qml new file mode 100755 index 000000000000..833870d29e21 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Font.qml @@ -0,0 +1,17 @@ +import QtQuick 2.15 + +QtObject { + +// currently mapped to unity but you can use to bulk scale fonsize if needed + function scale(fontSize) { return fontSize; } + +// Font Size Variables + readonly property int miniFontSize: scale(10) + readonly property int smallFontSize: scale(12) + readonly property int middleFontSize: scale(15) + readonly property int largeFontSize: scale(18) + readonly property int largeValueFontSize: scale(21) + readonly property int moreLargeValueFontSize: scale(33) + readonly property int extraLargeValueFontSize: scale(45) + readonly property int superLargeValueFontSize: scale(55) +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Margins.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Margins.qml new file mode 100755 index 000000000000..6bedda675a5c --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Margins.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +QtObject { + +// Margin Variables + readonly property int topMarginCenterOverlayHeadline: 11 // 17 +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Settings.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Settings.qml new file mode 100755 index 000000000000..bd2dbc8e712f --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Settings.qml @@ -0,0 +1,296 @@ +import QtQuick 2.5 + +QtObject { + + // = comments + + ////////////////// + //EXTRA SETTINGS// + ////////////////// + + //show only decks A&B or C&D - SELECT ONLY ONE + readonly property color accentColor: engine.getSetting('accentColor') || 'green' + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /////////////////////////////// + //PAD TYPE SELECTION SETTINGS// + /////////////////////////////// + + //Please only use the values in the line below. Using other values could have unexpected effects. + //0 = disabled, 4 = freeze, 5 = loop, 7 = roll, 8 = jump/move, 9 = fx1, 10 = fx2, 11 = tone + readonly property int recordButton: 8 + readonly property int samplesButton: 4 + readonly property int muteButton: 7 + readonly property int stemsButton: 5 + readonly property int cueButton: 11 + readonly property int fxLeftButton: 9 + readonly property int fxRightButton: 10 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////// + //BPM/TEMPO DISPLAY SETTINGS// + ////////////////////////////// + + //Change to true to always show tempo/bpm info + readonly property bool alwaysShowTempoInfo: engine.getSetting('alwaysShowTempoInfo') || false + + //amount of time the bpm overlay will stay on the screen in ms. 1000 = 1 second. + readonly property int bpmOverlayTimer: (engine.getSetting('bpmOverlayTimer') || 5.0) * 1000 + + //0 = Hidden, 1 = Master BPM, 2 = BPM, 3 = Tempo, 4 = BPM Offset, 5 = Tempo Offset, 6 = Master Deck Letter, 7 = Tempo Range, 8 = Key, 9 = Original BPM + readonly property int tempoDisplayLeft: parseInt(engine.getSetting('tempoDisplayLeft')) || 2 + readonly property int tempoDisplayCenter: parseInt(engine.getSetting('tempoDisplayCenter')) || 1 + readonly property int tempoDisplayRight: parseInt(engine.getSetting('tempoDisplayRight')) || 3 + readonly property int tempoDisplayLeftShift: parseInt(engine.getSetting('tempoDisplayLeftShift')) || 4 + readonly property int tempoDisplayCenterShift: parseInt(engine.getSetting('tempoDisplayCenterShift')) || 6 + readonly property int tempoDisplayRightShift: parseInt(engine.getSetting('tempoDisplayRightShift')) || 5 + + //set to true to enable the text Color to aid with your mixing. + readonly property bool enableBpmTextColor: engine.getSetting('enableBpmTextColor') || false + readonly property bool enableMasterBpmTextColor: engine.getSetting('enableMasterBpmTextColor') || false + readonly property bool enableTempoTextColor: engine.getSetting('enableTempoTextColor') || false + readonly property bool enableBpmOffsetTextColor: engine.getSetting('enableBpmOffsetTextColor') || false + readonly property bool enableTempoOffsetTextColor: engine.getSetting('enableTempoOffsetTextColor') || false + readonly property bool enableMasterDeckTextColor: engine.getSetting('enableMasterDeckTextColor') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////// + //WAVEFORM SETTINGS// + ///////////////////// + + //change to true to disable the moving waveforms + readonly property bool hideWaveforms: engine.getSetting('hideWaveforms') || false + + //Change to false to hide loop size indicator (after 10 seconds of loop inactivity) + readonly property bool alwaysShowLoopSize: engine.getSetting('alwaysShowLoopSize') || false + + //amount of time the loop overlay will stay on the screen in ms. 1000 = 1 second. + readonly property int loopOverlayTimer: (engine.getSetting('loopOverlayTimer') || 10) * 1000 + + //set to true to hide the beatgrid + readonly property bool hideBeatgrid: engine.getSetting('hideBeatgrid') || false + + //this value is the visibility of the beatgrid lines in %. Values are 0 to 100 + readonly property real beatgridVisibility: engine.getSetting('beatgridVisibility') || 0.75 + + //set to true to show time to next cue on waveform + readonly property bool showTimeToCue: engine.getSetting('showTimeToCue') || false + + //set to true to show beats to next cue on waveform + readonly property bool showBeatToCue: engine.getSetting('showBeatToCue') || false + + //set to true to show beats to next cue on waveform + readonly property int distanceToCueFontSize: engine.getSetting('distanceToCueFontSize') || 12 + + //set to true to show beats to next cue on waveform + readonly property string distanceToCueAlignment: engine.getSetting('distanceToCueAlignment') || "bottom" + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////// + //BROWSER SETTINGS// + //////////////////// + + // NOTE the following setting are currently unused due to the lack of QML support for Mixxx library + + // //set to false to disable browser view and pads + // readonly property bool enableBrowserMode: engine.getSetting('enableBrowserMode') || true + + // //set to false to disable the adjacent key Coloring and return to all keys Colored + // readonly property bool adjacentKeys: engine.getSetting('adjacentKeys') || true + + // //change to true to enable camelot key + // readonly property bool camelotKey: engine.getSetting('camelotKey') || false + + // //set to true to disable the preview player toggle button and change it back to hold + // readonly property bool disablePreviewPlayerToggle: engine.getSetting('disablePreviewPlayerToggle') || false + + // //Set to false to disable browser on screen when pressing favorites button + // readonly property bool showBrowserOnFavourites: engine.getSetting('showBrowserOnFavourites') || true + + // //set to true to swap the functions of the view and add to prep buttons + // readonly property bool swapViewButtons: engine.getSetting('swapViewButtons') || false + + // //Set to false to disable browser on screen when open in full screen mode. + // //This will also revert the view and prep button functions back to default except opening the browser on the S4 instead of the laptop. + // readonly property bool showBrowserOnFullScreen: engine.getSetting('showBrowserOnFullScreen') || true + + // //set to true to disable the led output on the browser sort buttons + // readonly property bool disableSortButtonOutput: engine.getSetting('disableSortButtonOutput') || false + + // // 1 = "Sort By #", 2 = "Title", 3 = "Artist", 4 = "Time", 5 = "BPM", 6 = "Track #", 7 = "Release", 8 = "Label", 9 = "Genre", 10 = "Key Text", 11 = "Comment", 12 = "Lyrics", 13 = "Comment 2", 14 = "Path", 15 = "Analysed" + // // 16 = "Remixer", 17 = "Producer", 18 = "Mix", 19 = "CAT #", 20 = "Release Date", 21 = "Bitrate", 22 = "Rating", 23 = "Count", 24 = "Sort By #", 25 = "Cover Art", 26 = "Last Played", 27 = "Import Date", 28 = "Key", 29 = "Color" + // readonly property int hotcueButtonSort: parseInt(engine.getSetting('hotcueButtonSort')) || 2 + // readonly property int recordButtonSort: parseInt(engine.getSetting('recordButtonSort')) || 3 + // readonly property int samplesButtonSort: parseInt(engine.getSetting('samplesButtonSort')) || 5 + // readonly property int muteButtonSort: parseInt(engine.getSetting('muteButtonSort')) || 28 + // readonly property int stemsButtonSort: parseInt(engine.getSetting('stemsButtonSort')) || 22 + + // //Change this setting to true to change the browser encoder to a list scroll when holding shift. + // readonly property bool browserEncoderShiftScroll: engine.getSetting('browserEncoderShiftScroll') || false + + // //This is the size of the page scroll. + // readonly property int scrollPageSize: parseInt(engine.getSetting('scrollPageSize')) || 6 + + // //change to false to disable the browser view displaying artist data whilst holding shift + // readonly property bool browserShift: engine.getSetting('browserShift') || true + + // //only enable when both artist and title columns are shown + // readonly property bool swapArtistTitleColumns: engine.getSetting('swapArtistTitleColumns') || false + + // readonly property bool hideBPM: engine.getSetting('hideBPM') || false + // readonly property bool hideKey: engine.getSetting('hideKey') || false + // readonly property bool hideAlbumArt: engine.getSetting('hideAlbumArt') || false + // readonly property bool showArtistColumn: engine.getSetting('showArtistColumn') || false + // readonly property bool showTrackTitleColumn: engine.getSetting('showTrackTitleColumn') || true + // readonly property int browserFontSize: parseInt(engine.getSetting('browserFontSize')) || 15 + // readonly property bool raiseBrowserFooter: engine.getSetting('raiseBrowserFooter') || false + + // //change the values below to determine the bpm text Color in the browser + // //the number values represent the percentage difference of the master tempo and the selected song + // readonly property bool bpmBrowserTextColor: engine.getSetting('bpmBrowserTextColor') || true + // readonly property int browserBpmGreen: 3 + // readonly property int browserBpmRed: 12 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////// + //WAVEFORM OVERVIEW SETTINGS// + ////////////////////////////// + + //change to true to hide stripe + readonly property bool hideWaveformOverview: engine.getSetting('hideWaveformOverview') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /////////////////////////// + //TIME/BEATS BOX SETTINGS// + /////////////////////////// + + // 0 = Remaining Time, 1 = Elapsed Time, 2 = Time To Cue, 3 = Beats (0.0.0), 4 = Beats Alt (0.0), 5 = Beats To Cue (0.0.0), 6 = Beats To Cue Alt (0.0) + readonly property int timeBox: parseInt(engine.getSetting('timeBox')) || 0 + readonly property int timeBoxShift: parseInt(engine.getSetting('timeBoxShift')) || 1 + + //set to true to have the time text change to black when the box is red. + readonly property bool timeTextColorChange: engine.getSetting('timeTextColorChange') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////////////////// + //PHASE & PHRASE METER SETTINGS// + ///////////////////////////////// + + //0 = Red, 1 = Dark Orange, 2 = Light Orange, 3 = Default, 4 = Yellow, 5 = Lime, 6 = Green, 7 = Mint, 8 = Cyan, 9 = Turquoise, 10 = Blue, 11 = Plum, 12 = Violet, 13 = Purple, 14 = Magenta, 15 = Fuchsia, 16 = White, 17 = Warm Yellow + readonly property int phaseAColor: parseInt(engine.getSetting('phaseAColor')) || 3 + readonly property int phaseBColor: parseInt(engine.getSetting('phaseBColor')) || 3 + readonly property int phaseCColor: parseInt(engine.getSetting('phaseCColor')) || 3 + readonly property int phaseDColor: parseInt(engine.getSetting('phaseDColor')) || 3 + + //change to true to hide the phase meter + readonly property bool hidePhase: engine.getSetting('hidePhase') || false + + //change to true to hide the phrase meter + readonly property bool hidePhrase: engine.getSetting('hidePhrase') || true + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////// + //GRID EDIT SETTINGS// + ////////////////////// + + //change to false to hide the bpm overlay when in grid adjust mode. + readonly property bool showBPMGridAdjust: engine.getSetting('showBPMGridAdjust') || true + readonly property int rateAdjustTimer: (engine.getSetting('rateAdjustTimer') || 2) * 1000 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /////////////////// + //HOTCUE SETTINGS// + /////////////////// + + //set to true to disable the hotcue overlay appearing + readonly property bool hideHotcueOverlay: engine.getSetting('hideHotcueOverlay') || false + + //0 = Red, 1 = Dark Orange, 2 = Light Orange, 3 = White, 4 = Yellow, 5 = Lime, 6 = Green, 7 = Mint, 8 = Cyan, 9 = Turquoise, 10 = Blue, 11 = Plum, 12 = Violet, 13 = Purple, 14 = Magenta, 15 = Fuchsia, 16 = Warm Yellow + //change these values to change default cue type Colors. + //This will change the cue markers and also the loop indicator. + readonly property int cueCueColor: parseInt(engine.getSetting('cueCueColor')) || 10 + readonly property int cueLoopColor: parseInt(engine.getSetting('cueLoopColor')) || 6 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////// + //EFFECTS SETTINGS// + //////////////////// + + //change to false to disable FX overlays + readonly property bool fxOverlays: (engine.getSetting('fxOverlays') || 'both') !== 'off' + + //amount of time the fx overlay will stay on the screen in ms. 1000 = 1 second. + readonly property int fxOverlayTimer: (engine.getSetting('fxOverlayTimer') || 2) * 1000 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////// + //EFFECTS PAD 1 SETTINGS// + ////////////////////////// + + //set to true to disable the effects pads 1 overlay appearing + readonly property bool hideEffectsOverlay1: (engine.getSetting('fxOverlays') || 'both') === 'right' + + //The fx unit used by fx pads 1 + readonly property int fx1unit: 1 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////// + //EFFECTS PAD 2 SETTINGS// + ////////////////////////// + + //set to true to disable the effects pads 2 overlay appearing + readonly property bool hideEffectsOverlay2: (engine.getSetting('fxOverlays') || 'both') === 'left' + + //The fx unit used by fx pads 2 + readonly property int fx2unit: 2 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////// + //TONE PAD SETTINGS// + ///////////////////// + + //set to true to disable the tone pads overlay appearing + readonly property bool hideToneOverlay: engine.getSetting('hideToneOverlay') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////// + //JUMP PAD SETTINGS// + ///////////////////// + + //set to true to disable the tone pads overlay appearing + readonly property bool hideJumpOverlay: engine.getSetting('hideJumpOverlay') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////// + //LOOP PAD SETTINGS// + ///////////////////// + + //set to true to disable the loop pads overlay appearing + readonly property bool hideLoopOverlay: engine.getSetting('hideLoopOverlay') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////// + //ROLL PAD SETTINGS// + ///////////////////// + + //set to true to disable the tone pads overlay appearing + readonly property bool hideRollOverlay: engine.getSetting('hideRollOverlay') || false + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Utils.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Utils.qml new file mode 100755 index 000000000000..b5f59ac7649a --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Utils.qml @@ -0,0 +1,126 @@ +import QtQuick 2.15 + +QtObject { + + function convertToTimeString(inSeconds) { + var neg = (inSeconds < 0); + var roundedSec = Math.floor(inSeconds); + + if (neg) { + roundedSec = -roundedSec; + } + + var sec = roundedSec % 60; + var min = (roundedSec - sec) / 60; + + var secStr = sec.toString(); + if (sec < 10) secStr = "0" + secStr; + + var minStr = min.toString(); + if (min < 10) minStr = "0" + minStr; + + return (neg ? "-" : "") + minStr + ":" + secStr; + } + + function computeRemainingTimeString(length, elapsed) { + return ((elapsed > length) ? convertToTimeString(0) : convertToTimeString( Math.floor(elapsed) - Math.floor(length))); + } + + function camelotConvert(keyToConvert) { + if (keyToConvert == "") return "-"; + + switch(keyToConvert) { + case "1d": return "8B"; + case "2d": return "9B"; + case "3d": return "10B"; + case "4d": return "11B"; + case "5d": return "12B"; + case "6d": return "1B"; + case "7d": return "2B"; + case "8d": return "3B"; + case "9d": return "4B"; + case "10d": return "5B"; + case "11d": return "6B"; + case "12d": return "7B"; + + case "1m": return "8A"; + case "2m": return "9A"; + case "3m": return "10A"; + case "4m": return "11A"; + case "5m": return "12A"; + case "6m": return "1A"; + case "7m": return "2A"; + case "8m": return "3A"; + case "9m": return "4A"; + case "10m": return "5A"; + case "11m": return "6A"; + case "12m": return "7A"; + + case "1D": return "8B"; + case "2D": return "9B"; + case "3D": return "10B"; + case "4D": return "11B"; + case "5D": return "12B"; + case "6D": return "1B"; + case "7D": return "2B"; + case "8D": return "3B"; + case "9D": return "4B"; + case "10D": return "5B"; + case "11D": return "6B"; + case "12D": return "7B"; + + case "1M": return "8A"; + case "2M": return "9A"; + case "3M": return "10A"; + case "4M": return "11A"; + case "5M": return "12A"; + case "6M": return "1A"; + case "7M": return "2A"; + case "8M": return "3A"; + case "9M": return "4A"; + case "10M": return "5A"; + case "11M": return "6A"; + case "12M": return "7A"; + + case "B": return "1B"; + case "F#": return "2B"; + case "C#": return "3B"; + case "G#": return "4B"; + case "D#": return "5B"; + case "A#": return "6B"; + case "F": return "7B"; + case "C": return "8B"; + case "G": return "9B"; + case "D": return "10B"; + case "A": return "11B"; + case "E": return "12B"; + + case "G#m": return "1A"; + case "D#m": return "2A"; + case "A#m": return "3A"; + case "Fm": return "4A"; + case "Cm": return "5A"; + case "Gm": return "6A"; + case "Dm": return "7A"; + case "Am": return "8A"; + case "Em": return "9A"; + case "Bm": return "10A"; + case "F#m": return "11A"; + case "C#m": return "12A"; + + case "G#M": return "1A"; + case "D#M": return "2A"; + case "A#M": return "3A"; + case "FM": return "4A"; + case "CM": return "5A"; + case "GM": return "6A"; + case "DM": return "7A"; + case "AM": return "8A"; + case "EM": return "9A"; + case "BM": return "10A"; + case "F#M": return "11A"; + case "C#M": return "12A"; + } + return "ERR"; + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/BankInfo.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/BankInfo.qml new file mode 100755 index 000000000000..0104aca56629 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/BankInfo.qml @@ -0,0 +1,193 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: bottomLabels + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (240 - height) + property int deckId: 1 + property int hotcue: 0 + property int type: 0 + property string name: "" + readonly property color barBgColor: "black" + property int bank: 1 + + // AppProperty { id: type; path: "app.traktor.fx." + bank + ".type"} + QtObject { + id: type2 + property string description: "Description" + property var value: 0 + } + + // AppProperty { id: routing; path: "app.traktor.fx." + bank + ".routing"} + QtObject { + id: routing + property string description: "Description" + property var value: 0 + } + property string routingText: routing.value == 0 ? "Send" : routing.value == 1 ? "Insert" : routing.value == 2 ? "Post" : "ERROR" + + // AppProperty { id: fxSelect1; path: "app.traktor.fx." + bank + ".select.1"} + QtObject { + id: fxSelect1 + property string description: "Description" + property var value: 0 + } + // AppProperty { id: fxSelect2; path: "app.traktor.fx." + bank + ".select.2"} + QtObject { + id: fxSelect2 + property string description: "Description" + property var value: 0 + } + // AppProperty { id: fxSelect3; path: "app.traktor.fx." + bank + ".select.3"} + QtObject { + id: fxSelect3 + property string description: "Description" + property var value: 0 + } + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + + height: type2.value == 2 ? 25 : 50 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: bottomLabels.height + color: colors.colorFxHeaderBg + } + + // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:63; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 25 + anchors.left: parent.left + anchors.leftMargin: 320/3 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 25 + anchors.left: parent.left + anchors.leftMargin: (320/3) * 2 + height: 63 + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Row { + BankInfoDetails { + id: bottomInfoDetails1 + finalLabel: (type2.value == 2 ? "Pattern Player " : "FX Bank ") + bank + " - " + routingText + hideValue: true + hideTitle: false + width: 240 + } + } + + Row { + BankInfoDetails { + id: bottomInfoDetails2 + finalValue: fxSelect1.description + hideValue: (type2.value == 2 ? true : false) + hideTitle: true + width: 320/3 + } + + BankInfoDetails { + id: bottomInfoDetails3 + finalValue: fxSelect2.description + hideValue: (type2.value != 0) ? true : false + hideTitle: true + width: 320/3 + } + + BankInfoDetails { + id: bottomInfoDetails4 + finalValue: fxSelect3.description + hideValue: (type2.value != 0) ? true : false + hideTitle: true + width: 320/3 + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: bottomLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: bottomLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: bottomLabels; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/BankInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/BankInfoDetails.qml new file mode 100755 index 000000000000..026bca41f4f3 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/BankInfoDetails.qml @@ -0,0 +1,77 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + Defines.Settings {id: settings} + + property var parameter: ({description:"Description",value: 0, valueRange: {isDiscrete: true, steps: 1}}) // set from outside + property bool isOn: false + property string label: "DRUMLOOP" + property string buttonLabel: "HP ON" + + property bool hideValue: false + property bool hideTitle: false + property bool fxEnabled: false + + property bool indicatorEnabled: fxEnabled && label.length > 0 + property string finalValue: "" + property string finalLabel: "" + + function toInt_round(val) { return parseInt(val+0.5); } + + property alias textColor: colors.colorFontFxHeader + + readonly property int macroEffectChar: 0x00B6 + readonly property bool isMacroFx: (finalLabel.charCodeAt(0) == macroEffectChar) + + width: 0 + height: 25 + + Defines.Colors { id: colors } + + // Level indicator for knobs + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 25 + width: parent.width + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: finalLabel + visible: !hideTitle + color: settings.accentColor + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + + // value + Text { + id: fxInfoValueLarge + width: 320/3 + text: finalValue + font.family: "Pragmatica" // is monospaced + color: colors.colorWhite + visible: !hideValue + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 4 + font.pixelSize: 15 + anchors.topMargin: 25 + elide: Text.ElideRight + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/CueInfo.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/CueInfo.qml new file mode 100755 index 000000000000..dbc47d841456 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/CueInfo.qml @@ -0,0 +1,152 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: bottomLabels + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (195 - bottomMargin) + property int hotcue: 0 + property int type: 0 + property string name: "" + readonly property color barBgColor: "black" + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + + height: 40 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: bottomLabels.height + color: colors.colorFxHeaderBg + // light grey background + Rectangle { + id:bottomInfoDetailsPanelLightBg + anchors { + top: parent.top + left: parent.left + } + height: bottomLabels.height + width: 18 + color: colors.colorFxHeaderLightBg + } + } + +// // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:63; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 18 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 240 + height: 63 + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Row { + CueInfoDetails { + id: bottomInfoDetails1 + finalValue: hotcue + finalLabel: "#" + width: 18 + } + + CueInfoDetails { + id: bottomInfoDetails2 + finalValue: name + finalLabel: "NAME" + width: 222 + } + + CueInfoDetails { + id: bottomInfoDetails3 + finalValue: (type == 0 ? "Cue" : type == 1 ? "Fade-In" : type == 2 ? "Fade-Out" : type == 3 ? "Load" : type == 4 ? "Grid" : type == 5 ? "Loop" : "-") + finalLabel: "TYPE" + width: 50 + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: bottomLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: bottomLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: bottomLabels; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/CueInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/CueInfoDetails.qml new file mode 100755 index 000000000000..5f0d2b102835 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/CueInfoDetails.qml @@ -0,0 +1,73 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + Defines.Settings {id: settings} + + property var parameter: ({description:"Description",value: 0, valueRange: {isDiscrete: true, steps: 1}}) // set from outside + property bool isOn: false + property string label: "DRUMLOOP" + property string buttonLabel: "HP ON" + + property bool hideValue: true + property bool fxEnabled: false + + property bool indicatorEnabled: fxEnabled && label.length > 0 + property string finalValue: "" + property string finalLabel: "" + + function toInt_round(val) { return parseInt(val+0.5); } + + property alias textColor: colors.colorFontFxHeader + + readonly property int macroEffectChar: 0x00B6 + readonly property bool isMacroFx: (finalLabel.charCodeAt(0) == macroEffectChar) + + width: 0 + height: 45 + + Defines.Colors { id: colors } + + // Level indicator for knobs + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 45 + width: parent.width + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: finalLabel + color: settings.accentColor + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + + // value + Text { + id: fxInfoValueLarge + text: finalValue + font.family: "Pragmatica" // is monospaced + color: colors.colorWhite + visible: true + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 4 + font.pixelSize: 15 + anchors.topMargin: 22 + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/FXInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/FXInfoDetails.qml new file mode 100755 index 000000000000..70b52426b30d --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/FXInfoDetails.qml @@ -0,0 +1,66 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + property var parameter: ({description:"Description",value: 0, valueRange: {isDiscrete: true, steps: 1}}) // set from outside + property string label: "DRUMLOOP" + + property alias textColor: colors.colorFontFxHeader + property bool header: false + property int effectID: 0 + property int fxUnit: 1 + + // AppProperty {id: slot1; path: "app.traktor.fx." + fxUnit + ".select.1"} + QtObject { + id: slot1 + property string description: "Description" + property var value: 0 + } + // AppProperty {id: slot2; path: "app.traktor.fx." + fxUnit + ".select.2"} + QtObject { + id: slot2 + property string description: "Description" + property var value: 0 + } + // AppProperty {id: slot3; path: "app.traktor.fx." + fxUnit + ".select.3"} + QtObject { + id: slot3 + property string description: "Description" + property var value: 0 + } + + width: 0 + height: 20 + + Defines.Colors { id: colors } + Defines.Settings {id: settings} + + // Level indicator for knobs + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 20 + width: parent.width + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: label + color: header ? settings.accentColor : (slot1.value == effectID || slot2.value == effectID || slot3.value == effectID ? "lime" : "white") + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/GridControls.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/GridControls.qml new file mode 100755 index 000000000000..0da2209b2edf --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/GridControls.qml @@ -0,0 +1,209 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +import Mixxx 1.0 as Mixxx + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: bottomLabels + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (180 - bottomMargin) + property int deckId: 1 + + // AppProperty { id: waveZoomProp; path: "app.traktor.decks." + deckId + ".track.waveform_zoom" } + Mixxx.ControlProxy { + group: `[Channel${deckId}]` + key: "waveform_zoom" + id: waveZoomProp + } + // AppProperty { id: tick; path: "app.traktor.decks." + deckId + ".track.grid.enable_tick" } + QtObject { + id: tick + property string description: "Description" + property var value: 0 + } + Mixxx.ControlProxy { + group: `[Channel${deckId}]` + id: range + key: "rateRange" + property string description: "Description" + property var valueRange: ({isDiscrete: false, steps: 1}) + } + + readonly property color barBgColor: "black" + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + + height: 60 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: bottomLabels.height + color: colors.colorFxHeaderBg + // light grey background + Rectangle { + id:bottomInfoDetailsPanelLightBg + anchors { + top: parent.top + left: parent.left + } + height: bottomLabels.height + width: 80 + color: colors.colorFxHeaderLightBg + } + } + +// // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:63; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 160 + height: 63 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 240 + height: 63 + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Row { + GridInfoDetails { + id: bottomInfoDetails1 + parameter: waveZoomProp + label: "ZOOM" + fxEnabled: true + barBgColor: bottomLabels.barBgColor + hideButton: true + zoom: true + hideValue: false + } + + GridInfoDetails { + id: bottomInfoDetails2 + parameter: tick + label: "TICK" + fxEnabled: false + isOn: tick.value + barBgColor: bottomLabels.barBgColor + hideButton: true + zoom: true + hideValue: true + } + + GridInfoDetails { + id: bottomInfoDetails3 + parameter: tick + label: "TICK" + fxEnabled: false + isOn: tick.value + barBgColor: bottomLabels.barBgColor + hideButton: true + zoom: true + hideValue: true + } + + GridInfoDetails { + id: bottomInfoDetails4 + parameter: range + label: "RANGE" + fxEnabled: true + barBgColor: bottomLabels.barBgColor + hideButton: true + zoom: false + hideValue: false + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: bottomLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: bottomLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: bottomLabels; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/GridInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/GridInfoDetails.qml new file mode 100755 index 000000000000..4d54ee03c285 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/GridInfoDetails.qml @@ -0,0 +1,160 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + Defines.Settings {id: settings} + + property var parameter: ({description:"Description",value: 0}) // set from outside + property bool isOn: false + property string label: "DRUMLOOP" + property string sizeState: "small" + property string buttonLabel: "HP ON" + property bool fxEnabled: false + property bool zoom: true + property bool hideButton: true + property bool hideValue: true + + property bool indicatorEnabled: fxEnabled && label.length > 0 + property string finalValue: zoom ? (((10 - parameter.value) / 9)*100).toFixed(2)+"%" : toInt_round(parameter.value*100).toString() + "%" + property string finalLabel: fxEnabled ? label : "" + property string finalButtonLabel: "ON" + property color barBgColor // set from outside + + function toInt_round(val) { return parseInt(val+0.5); } + + property alias textColor: colors.colorFontFxHeader + + readonly property int macroEffectChar: 0x00B6 + readonly property bool isMacroFx: (finalLabel.charCodeAt(0) == macroEffectChar) + + readonly property var valueRange: parameter.valueRange || {} + + width: 80 + height: 45 + + Defines.Colors { id: colors } + + // Level indicator for knobs + Widgets.ProgressBar { + id: slider + progressBarHeight: (sizeState == "small") ? 6 : 9 + progressBarWidth: 76 + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 3 + anchors.leftMargin: 2 + + value: label == "ZOOM" ? (10 - parameter.value) / 9 : parameter.value + visible: !(valueRange.isDiscrete && fxEnabled) + + drawAsEnabled: indicatorEnabled + + progressBarBackgroundColor: parent.barBgColor + } + + // stepped progress bar + Widgets.StateBar { + id: slider2 + height: (sizeState == "small") ? 6 : 9 + width: 76 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 2 + anchors.topMargin: 3 + + stateCount: valueRange.steps || 0 + currentState: (valueRange.steps - 1.0 + 0.2) * parameter.value // +.2 to make sure we round in the right direction + visible: !slider.visible + barBgColor: parent.barBgColor + } + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 100 + width: parent.width + + Rectangle { + id: macroIconDetails + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.topMargin: 50 + + width: 12 + height: 11 + radius: 1 + visible: isMacroFx + color: colors.colorGrey216 + + Text { + anchors.fill: parent + anchors.topMargin: -1 + anchors.leftMargin: 1 + text: "M" + font.pixelSize: fonts.miniFontSize + color: colors.colorBlack + } + } + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: finalLabel + color: settings.accentColor + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 40 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: isMacroFx ? 26 : 4 + anchors.rightMargin: 12 + elide: Text.ElideRight + } + + // value + Text { + id: fxInfoValueLarge + text: finalValue + font.family: "Pragmatica" // is monospaced + color: colors.colorWhite + visible: (label.length > 0) && !hideValue + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 4 + font.pixelSize: 15 + anchors.topMargin: 22 + } + + // button + Rectangle { + id: fxInfoFilterButton + width: 30 + + color: ( fxEnabled ? (isOn ? colors.colorIndicatorLevelOrange : colors.colorBlack) : "transparent" ) + visible: (buttonLabel.length > 0) && !hideButton + radius: 1 + anchors.right: parent.right + anchors.rightMargin: 2 + anchors.top: parent.top + height: 15 + anchors.topMargin: 24 + + Text { + id: fxInfoFilterButtonText + font.capitalization: Font.AllUppercase + text: finalButtonLabel + color: ( fxEnabled ? (isOn ? colors.colorBlack : colors.colorGrey128) : colors.colorGrey128 ) + font.pixelSize: fonts.miniFontSize + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/JumpControls.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/JumpControls.qml new file mode 100755 index 000000000000..57c4e97d8adb --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/JumpControls.qml @@ -0,0 +1,239 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Widgets' as Widgets +import '../Defines' as Defines + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: fxLabels + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (240 - height) + property string name: "" + readonly property color barBgColor: "black" + + required property var deckInfo + readonly property bool shift: deckInfo.shift + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + Defines.Settings { id: settings } + + height: 65 + anchors.left: parent.left + anchors.right: parent.right + + Mixxx.ControlProxy { + id: beatjump + group: deckInfo.group + key: "beatjump_size" + } + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: fxLabels.height + color: colors.colorFxHeaderBg + } + + // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:80; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 160 + height: 80 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 240 + height: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider3 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + } + + // dividers + Rectangle { + id: fxInfoDivider4 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 40 + anchors.left: parent.left + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Column { + Row { + JumpInfoDetails { + id: header + label: "MOVE/BEATJUMP" + width: 200 + header: true + } + } + + Row { + JumpInfoDetails { + id: bottomInfoDetails1 + label: deckInfo.jumpSizePad1 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad1, shift) + back: shift + width: 80 + } + JumpInfoDetails { + id: bottomInfoDetails2 + label: deckInfo.jumpSizePad2 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad2, shift) + back: shift + width: 80 + } + JumpInfoDetails { + id: bottomInfoDetails3 + label: deckInfo.jumpSizePad3 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad3, shift) + back: shift + width: 80 + } + JumpInfoDetails { + id: bottomInfoDetails4 + label: deckInfo.jumpSizePad4 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad4, shift) + back: shift + width: 80 + } + } + + Row { + JumpInfoDetails { + id: bottomInfoDetails5 + label: deckInfo.jumpSizePad5 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad5, shift) + back: shift + width: 80 + } + JumpInfoDetails { + id: bottomInfoDetails6 + label: deckInfo.jumpSizePad6 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad6, shift) + back: shift + width: 80 + } + JumpInfoDetails { + id: bottomInfoDetails7 + label: deckInfo.jumpSizePad7 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad7, shift) + back: shift + width: 80 + } + JumpInfoDetails { + id: bottomInfoDetails8 + label: deckInfo.jumpSizePad8 === "??" ? (shift ? "- " : " ") + (beatjump.value < 1 ? `1 / ${1/beatjump.value}` : `${beatjump.value}`) : getValue(deckInfo.jumpSizePad8, shift) + back: shift + width: 80 + } + } + } + } + + function getValue(size, shift) { + if (parseFloat(size)) { + return (shift ? "- " : " ") + (size < 1 ? `1 / ${1/size}` : `${size}`) + } else if (size === "??") { + return null; + } else { + return size + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: fxLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: fxLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: fxLabels; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/JumpInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/JumpInfoDetails.qml new file mode 100755 index 000000000000..6b220097ade5 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/JumpInfoDetails.qml @@ -0,0 +1,45 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + Defines.Settings {id: settings} + + property string label: "DRUMLOOP" + property bool back: false + property bool header: false + + width: 0 + height: 20 + + Defines.Colors { id: colors } + + // Level indicator for knobs + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 20 + width: parent.width + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: label + color: header ? settings.accentColor : (label == "n/a" || label == "" ? "white" : back == true ? "red" : "lime") + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 0 + font.pixelSize: fonts.scale(18) + anchors.leftMargin: 4 + elide: Text.ElideRight + horizontalAlignment: header ? Text.AlignLeft : Text.AlignHCenter + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/LoopControls.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/LoopControls.qml new file mode 100755 index 000000000000..ea9f5c29fe72 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/LoopControls.qml @@ -0,0 +1,230 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines +import '../ViewModels' as ViewModels + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: view + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (240 - height) + property string name: "" + readonly property color barBgColor: "black" + property int deckId: 1 + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + Defines.Settings { id: settings } + + required property var deckInfo + + height: 65 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: view.height + color: colors.colorFxHeaderBg + } + + // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:80; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 160 + height: 80 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 240 + height: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider3 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + } + + // dividers + Rectangle { + id: fxInfoDivider4 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 40 + anchors.left: parent.left + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Column { + Row { + LoopInfoDetails { + id: header + label: "LOOP" + width: 200 + header: true + } + } + + Row { + LoopInfoDetails { + id: bottomInfoDetails1 + label: deckInfo.loopSizePad1 + label2: deckInfo.loopSizePad1 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails2 + label: deckInfo.loopSizePad2 + label2: deckInfo.loopSizePad2 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails3 + label: deckInfo.loopSizePad3 + label2: deckInfo.loopSizePad3 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails4 + label: deckInfo.loopSizePad4 + label2: deckInfo.loopSizePad4 + deckId: deckId + width: 80 + } + } + + Row { + LoopInfoDetails { + id: bottomInfoDetails5 + label: deckInfo.loopSizePad5 + label2: deckInfo.loopSizePad5 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails6 + label: deckInfo.loopSizePad6 + label2: deckInfo.loopSizePad6 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails7 + label: deckInfo.loopSizePad7 + label2: deckInfo.loopSizePad7 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails8 + label: deckInfo.loopSizePad8 + label2: deckInfo.loopSizePad8 + deckId: deckId + width: 80 + } + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: view.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: view; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: view; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/LoopInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/LoopInfoDetails.qml new file mode 100755 index 000000000000..12ab2ff7c036 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/LoopInfoDetails.qml @@ -0,0 +1,57 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + property string label: "" + property string label2: "" + property bool header: false + property int deckId: 1 + + // AppProperty { id: enabled; path: "app.traktor.decks." + deckId + ".loop.is_in_active_loop" } + QtObject { + id: enabled + property string description: "Description" + property var value: 0 + } + // AppProperty { id: size; path: "app.traktor.decks." + deckId + ".loop.size" } + QtObject { + id: size + property string description: "Description" + property var value: 0 + } + + width: 0 + height: 20 + + Defines.Colors { id: colors } + + // Level indicator for knobs + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 20 + width: parent.width + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: header ? label : label2 + color: header ? settings.accentColor : (enabled.value && (size.value == label) ? "lime" : "white") + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 0 + font.pixelSize: fonts.scale(18) + anchors.leftMargin: 4 + elide: Text.ElideRight + horizontalAlignment: header ? Text.AlignLeft : Text.AlignHCenter + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/QuickFXSelector.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/QuickFXSelector.qml new file mode 100755 index 000000000000..930ce608e7ce --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/QuickFXSelector.qml @@ -0,0 +1,151 @@ +import QtQuick 2.15 + +import '../Defines' as Defines +import '../Widgets' as Widgets + +import Mixxx 1.0 as Mixxx + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: topLabels + + required property var deckInfo + + property int topMargin: 0 + + property int yPositionWhenHidden: -25 + property int yPositionWhenShown: topMargin + + readonly property color barBgColor: "black" + + property var fxModel: Mixxx.EffectsManager.quickChainPresetModel + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + Defines.Settings {id: settings} + + height: 25 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: topInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: topLabels.height + color: colors.colorFxHeaderBg + // light grey background + // Rectangle { + // id:topInfoDetailsPanelLightBg + // anchors { + // top: parent.top + // left: parent.left + // } + // height: topLabels.height + // width: 240 + // color: colors.colorFxHeaderLightBg + // } + } + + // Info Details + Rectangle { + id: topInfoDetailsPanel + + height: parent.height + // clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + // Row { + // id: controlRow + + // Item { + // id: quickFxDetailsPanel + + // height: display.height + // width: 260 + + // name + Text { + id: stemInfoName + font.capitalization: Font.AllUppercase + text: "SELECTED QUICK FX" + color: settings.accentColor + + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + + font.pixelSize: fonts.scale(13.5) + elide: Text.ElideRight + } + + // value + Text { + id: nameValue + font.capitalization: Font.AllUppercase + text: fxModel.get(deckInfo.quickFXSelected).display || "---" + color: colors.colorWhite + + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 10 + + font.pixelSize: fonts.scale(13.5) + elide: Text.ElideRight + } + // } + // } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: topLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + state: deckInfo.quickFXSelected != null ? "show" : "hide" + states: [ + State { + name: "show"; + PropertyChanges { target: topLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: topLabels; y: -height} + } + ] +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/RollControls.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/RollControls.qml new file mode 100755 index 000000000000..0b85f5b4e983 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/RollControls.qml @@ -0,0 +1,230 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines +import '../ViewModels' as ViewModels + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: view + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (240 - height) + property string name: "" + readonly property color barBgColor: "black" + property int deckId: 1 + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + Defines.Settings { id: settings } + + required property var deckInfo + + height: 65 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: view.height + color: colors.colorFxHeaderBg + } + + // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:80; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 160 + height: 80 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 240 + height: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider3 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + } + + // dividers + Rectangle { + id: fxInfoDivider4 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 40 + anchors.left: parent.left + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Column { + Row { + LoopInfoDetails { + id: header + label: "ROLL" + width: 200 + header: true + } + } + + Row { + LoopInfoDetails { + id: bottomInfoDetails1 + label: deckInfo.rollSizePad1 + label2: deckInfo.rollSizePad1 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails2 + label: deckInfo.rollSizePad2 + label2: deckInfo.rollSizePad2 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails3 + label: deckInfo.rollSizePad3 + label2: deckInfo.rollSizePad3 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails4 + label: deckInfo.rollSizePad4 + label2: deckInfo.rollSizePad4 + deckId: deckId + width: 80 + } + } + + Row { + LoopInfoDetails { + id: bottomInfoDetails5 + label: deckInfo.rollSizePad5 + label2: deckInfo.rollSizePad5 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails6 + label: deckInfo.rollSizePad6 + label2: deckInfo.rollSizePad6 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails7 + label: deckInfo.rollSizePad7 + label2: deckInfo.rollSizePad7 + deckId: deckId + width: 80 + } + LoopInfoDetails { + id: bottomInfoDetails8 + label: deckInfo.rollSizePad8 + label2: deckInfo.rollSizePad8 + deckId: deckId + width: 80 + } + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: view.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: view; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: view; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/ToneControls.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/ToneControls.qml new file mode 100755 index 000000000000..0ecebfade340 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/ToneControls.qml @@ -0,0 +1,247 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: fxLabels + + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (240 - height) + property string name: "" + readonly property color barBgColor: "black" + property int deckId: 1 + property real adjustVal: 0.00 + property string adjust: adjustVal.toFixed(0) + + Timer { + id: toneTimer + property bool blink: false + + interval: 250 + repeat: true + running: adjust != 0 + + onTriggered: { + blink = !blink; + } + + onRunningChanged: { + blink = running; + } + } + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + Defines.Settings { id: settings } + + // MappingProperty { id: forward; path: "mapping.state." + deckId + ".forward"} + QtObject { + id: forward + property string description: "Description" + property var value: 0 + } + + property bool forwardVal: forward.value + + height: 65 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: fxLabels.height + color: colors.colorFxHeaderBg + } + + // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:80; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 160 + height: 80 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 240 + height: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider3 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + } + + // dividers + Rectangle { + id: fxInfoDivider4 + width:360; + height:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.topMargin: 40 + anchors.left: parent.left + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Column { + Row { + ToneInfoDetails { + id: header + label: "TONE" + width: 200 + header: true + } + } + + Row { + ToneInfoDetails { + id: bottomInfoDetails1 + label: "0" + color: adjust != 0 ? "grey" : "white" + width: 80 + } + ToneInfoDetails { + id: bottomInfoDetails2 + label: forwardVal ? "+1" : "-1" + color: forwardVal ? ((adjust == 1) && toneTimer.blink ? "white" : "lime") : ((adjust == -1) && toneTimer.blink ? "white" : "red") + width: 80 + } + ToneInfoDetails { + id: bottomInfoDetails3 + label: forwardVal ? "+2" : "-2" + color: forwardVal ? ((adjust == 2) && toneTimer.blink ? "white" : "lime") : ((adjust == -2) && toneTimer.blink ? "white" : "red") + width: 80 + } + ToneInfoDetails { + id: bottomInfoDetails4 + label: forwardVal ? "+3" : "-3" + color: forwardVal ? ((adjust == 3) && toneTimer.blink ? "white" : "lime") : ((adjust == -3) && toneTimer.blink ? "white" : "red") + width: 80 + } + } + + Row { + ToneInfoDetails { + id: bottomInfoDetails5 + label: forwardVal ? "+4" : "-4" + color: forwardVal ? ((adjust == 4) && toneTimer.blink ? "white" : "lime") : ((adjust == -4) && toneTimer.blink ? "white" : "red") + width: 80 + } + ToneInfoDetails { + id: bottomInfoDetails6 + label: forwardVal ? "+5" : "-5" + color: forwardVal ? ((adjust == 5) && toneTimer.blink ? "white" : "lime") : ((adjust == -5) && toneTimer.blink ? "white" : "red") + width: 80 + } + ToneInfoDetails { + id: bottomInfoDetails7 + label: forwardVal ? "+6" : "-6" + color: forwardVal ? ((adjust == 6) && toneTimer.blink ? "white" : "lime") : ((adjust == -6) && toneTimer.blink ? "white" : "red") + width: 80 + } + ToneInfoDetails { + id: bottomInfoDetails8 + label: forwardVal ? "+7" : "-7" + color: forwardVal ? ((adjust == 7) && toneTimer.blink ? "white" : "lime") : ((adjust == -7) && toneTimer.blink ? "white" : "red") + width: 80 + } + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: fxLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: fxLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: fxLabels; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/ToneInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/ToneInfoDetails.qml new file mode 100755 index 000000000000..0e39735a4f92 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/ToneInfoDetails.qml @@ -0,0 +1,44 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + property string label: "" + property bool header: false + property string color: "white" + + width: 0 + height: 20 + + Defines.Colors { id: colors } + Defines.Settings {id: settings} + + // Level indicator for knobs + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 20 + width: parent.width + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: label + color: header ? settings.accentColor : fxInfoDetails.color + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 0 + font.pixelSize: fonts.scale(18) + anchors.leftMargin: 4 + elide: Text.ElideRight + horizontalAlignment: header ? Text.AlignLeft : Text.AlignHCenter + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/TopControls.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/TopControls.qml new file mode 100755 index 000000000000..3fb7fa1defa3 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/TopControls.qml @@ -0,0 +1,348 @@ +import QtQuick 2.15 + +import '../Defines' as Defines +import '../Widgets' as Widgets + +import Mixxx 1.0 as Mixxx + +//-------------------------------------------------------------------------------------------------------------------- +// FX CONTROLS +//-------------------------------------------------------------------------------------------------------------------- + +// The FxControls are located on the top of the screen and blend in if one of the top knobs is touched/changed + +Item { + id: topLabels + + property int topMargin: 0 + + property string showHideState: "hide" + property int fxUnit: 0 + property int yPositionWhenHidden: 0 - topLabels.height - headerBlackLine.height - headerShadow.height // also hides black border & shadow + property int yPositionWhenShown: topMargin + + readonly property color barBgColor: "black" + + property var fxModel: Mixxx.EffectsManager.visibleEffectsModel + + Defines.Colors { id: colors } + Defines.Durations { id: durations } + + height: 40 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: topInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: topLabels.height + color: colors.colorFxHeaderBg + // light grey background + Rectangle { + id:topInfoDetailsPanelLightBg + anchors { + top: parent.top + left: parent.left + } + height: topLabels.height + width: 80 + color: colors.colorFxHeaderLightBg + } + } + +// // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:40; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 80 + } + + // dividers + Rectangle { + id: fxInfoDivider1 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 160 + height: 40 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 240 + height: 40 + } + + // Info Details + Rectangle { + id: topInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + // AppProperty { id: fxDryWet; path: "app.traktor.fx." + (fxUnit + 1) + ".dry_wet" } + + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}]` + key: `mix` + id: fxDryWet + property string description: "" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + + // AppProperty { id: fxParam1; path: "app.traktor.fx." + (fxUnit + 1) + ".parameters.1" } + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect1]` + key: `meta` + id: fxParam1 + property string description: "" + property var valueRange: ({isDiscrete: false, steps: 0}) + } + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect1]` + key: `enabled` + id: fxEnabled1 + } + QtObject { + id: fxKnob1name + + property Mixxx.EffectSlotProxy slot: Mixxx.EffectsManager.getEffectSlot(1, 1) + property string description: "Description" + property var value: "---" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + Mixxx.ControlProxy { + id: fxSelect1 + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect1]` + key: `loaded_effect` + onValueChanged: { + fxKnob1name.value = topLabels.fxModel.get(value).display + } + } + + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect2]` + key: `meta` + id: fxParam2 + property string description: "" + property var valueRange: ({isDiscrete: false, steps: 0}) + } + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect2]` + key: `enabled` + id: fxEnabled2 + } + QtObject { + id: fxKnob2name + + property Mixxx.EffectSlotProxy slot: Mixxx.EffectsManager.getEffectSlot(1, 2) + property string description: "Description" + property var value: "---" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + Mixxx.ControlProxy { + id: fxSelect2 + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect2]` + key: `loaded_effect` + onValueChanged: { + fxKnob2name.value = topLabels.fxModel.get(value).display + } + } + + // AppProperty { id: fxParam3; path: "app.traktor.fx." + (fxUnit + 1) + ".parameters.3" } + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect3]` + key: `meta` + id: fxParam3 + property string description: "" + property var valueRange: ({isDiscrete: false, steps: 0}) + } + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect3]` + key: `enabled` + id: fxEnabled3 + } + QtObject { + id: fxKnob3name + + property Mixxx.EffectSlotProxy slot: Mixxx.EffectsManager.getEffectSlot(1, 3) + property string description: "Description" + property var value: "---" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + Mixxx.ControlProxy { + id: fxSelect3 + group: `[EffectRack1_EffectUnit${fxUnit + 1}_Effect3]` + key: `loaded_effect` + onValueChanged: { + fxKnob3name.value = topLabels.fxModel.get(value).display + } + } + + Mixxx.ControlProxy { + group: `[EffectRack1_EffectUnit${fxUnit + 1}]` + key: "enabled" + id: fxOn + property string description: "Description" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: fxButton1; path: "app.traktor.fx." + (fxUnit + 1) + ".buttons.1" } + QtObject { + id: fxButton1 + property string description: "Description" + property var value: fxEnabled1.value + property var valueRange: ({isDiscrete: true, steps: 1}) + } + + // AppProperty { id: fxButton1name; path: "app.traktor.fx." + (fxUnit + 1) + ".buttons.1.name" } + QtObject { + id: fxButton1name + property string description: "Description" + property var value: "ON" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: fxButton2; path: "app.traktor.fx." + (fxUnit + 1) + ".buttons.2" } + QtObject { + id: fxButton2 + property string description: "Description" + property var value: fxEnabled2.value + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: fxButton2name; path: "app.traktor.fx." + (fxUnit + 1) + ".buttons.2.name" } + QtObject { + id: fxButton2name + property string description: "Description" + property var value: "ON" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: fxButton3; path: "app.traktor.fx." + (fxUnit + 1) + ".buttons.3" } + QtObject { + id: fxButton3 + property string description: "Description" + property var value: fxEnabled3.value + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: fxButton3name; path: "app.traktor.fx." + (fxUnit + 1) + ".buttons.3.name" } + QtObject { + id: fxButton3name + property string description: "Description" + property var value: "ON" + property var valueRange: ({isDiscrete: true, steps: 1}) + } + + // AppProperty { id: fxType; path: "app.traktor.fx." + (fxUnit + 1) + ".type" } // singleMode -> fxSelect1.description else "DRY/WET" + QtObject { + id: fxType + property string description: "Description" + property var value: 0 + property var valueRange: ({isDiscrete: true, steps: 1}) + } + + Row { + id: controlRow + TopInfoDetails { + id: topInfoDetails1 + parameter: fxDryWet + isOn: fxOn.value + label: fxType.value == 1 ? ((fxSelect1.description == "Delay") ? "DELAY" : (fxSelect1.description == "Reverb") ? "REVRB" : (fxSelect1.description == "Flanger") ? "FLANG" : (fxSelect1.description == "Flanger Pulse") ? "FLN-P" : (fxSelect1.description == "Flanger Flux") ? "FLN-F" : (fxSelect1.description == "Gater") ? "GATER" : (fxSelect1.description == "Beatmasher 2") ? "BEATM" : (fxSelect1.description == "Delay T3") ? "T3DELAY" : (fxSelect1.description == "Filter LFO") ? "FLT-O" : (fxSelect1.description == "Filter Pulse") ? "FLT-P" : (fxSelect1.description == "Filter") ? "FILTR" : (fxSelect1.description == "Filter:92 Pulse") ? "F92-O" : (fxSelect1.description == "Filter:92 Pulse") ? "F92-P" : (fxSelect1.description == "Filter:92") ? "FLT92" : (fxSelect1.description == "Phaser") ? "PHFXASR" : (fxSelect1.description == "Phaser Pulse") ? "PHS-P" : (fxSelect1.description == "Phaser Flux") ? "PHS-F" : (fxSelect1.description == "Reverse Grain") ? "REVGR" : (fxSelect1.description == "Turntable FX") ? "TTFX" : (fxSelect1.description == "Iceverb") ? "ICEVB" : (fxSelect1.description == "Reverb T3") ? "T3REVRB" : (fxSelect1.description == "Ringmodulator") ? "RINGM" : (fxSelect1.description == "Digital LoFi") ? "LOFI" : (fxSelect1.description == "Mulholland Drive") ? "MHDRV" : (fxSelect1.description == "Transpose Stretch") ? "TRANS" : (fxSelect1.description == "BeatSlicer") ? "SLICER" : (fxSelect1.description == "Formant Filter") ? "FFTR" : (fxSelect1.description == "Peak Filter") ? "PFTR" : (fxSelect1.description == "Tape Delay") ? "TPDELAY" : (fxSelect1.description == "Ramp Delay") ? "RMPDLY" : (fxSelect1.description == "Auto Bouncer") ? "ABOUNCE" : (fxSelect1.description == "Bouncer") ? "BOUNCER" : (fxKnob3name.value == "LASLI") ? "LASLI" : (fxKnob3name.value == "GRANP") ? "GRANP" : (fxKnob3name.value == "B-O-M") ? "B-O-M" : (fxKnob3name.value == "POWIN") ? "POWIN" : (fxKnob3name.value == "EVNHR") ? "EVNHR" : (fxKnob3name.value == "ZZZRP") ? "ZZZRP" : (fxKnob3name.value == "STRRS") ? "STRRS" : (fxKnob3name.value == "STRRF") ? "STRRF" : (fxKnob3name.value == "DARKM") ? "DARKM" : (fxKnob3name.value == "FTEST") ? "FTEST" : fxSelect1.description) : "DRY/WET" + buttonLabel: fxType.value == 1 ? "ON" : "" + fxEnabled: (fxType.value != 1) || fxSelect1.value + barBgColor: topLabels.barBgColor + isPatternPlayer: (fxType.value == 2 ? true : false) + } + TopInfoDetails { + id: topInfoDetails2 + parameter: fxParam1 + isOn: fxButton1.value + label: fxKnob1name.value + buttonLabel: fxButton1name.value + fxEnabled: (fxSelect1.value || ((fxType.value == 1) && fxSelect1.value) ) + barBgColor: topLabels.barBgColor + isPatternPlayer: (fxType.value == 2 ? true : false) + } + + TopInfoDetails { + id: topInfoDetails3 + parameter: fxParam2 + isOn: fxButton2.value + label: fxKnob2name.value + buttonLabel: fxButton2name.value + fxEnabled: (fxSelect2.value || ((fxType.value == 1) && fxSelect1.value) ) + barBgColor: topLabels.barBgColor + isPatternPlayer: (fxType.value == 2 ? true : false) + } + + TopInfoDetails { + id: topInfoDetails4 + parameter: fxParam3 + isOn: fxButton3.value + label: fxKnob3name.value + buttonLabel: fxButton3name.value + fxEnabled: (fxSelect3.value || ((fxType.value == 1) && fxSelect1.value) ) + barBgColor: topLabels.barBgColor + isPatternPlayer: (fxType.value == 2 ? true : false) + } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: topLabels.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.overlayTransition; easing.type: Easing.InOutQuad } } + + Item { + id: showHide + state: showHideState + states: [ + State { + name: "show"; + PropertyChanges { target: topLabels; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: topLabels; y: yPositionWhenHidden} + } + ] + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/TopInfoDetails.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/TopInfoDetails.qml new file mode 100755 index 000000000000..92ea2991b01a --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Overlays/TopInfoDetails.qml @@ -0,0 +1,152 @@ +import QtQuick 2.15 + +import '../Widgets' as Widgets +import '../Defines' as Defines + +Item { + id: fxInfoDetails + + property var parameter: ({}) // set from outside + property bool isOn: false + property string label: "DRUMLOOP" + property string sizeState: "small" + property string buttonLabel: "HP ON" + property bool fxEnabled: false + property bool indicatorEnabled: fxEnabled && label.length > 0 + property string finalValue: fxEnabled ? parameter.description : "" + property string finalLabel: fxEnabled ? label : "" + property string finalButtonLabel: fxEnabled ? buttonLabel : "" + property color barBgColor // set from outside + property bool isPatternPlayer: false + + property alias textColor: colors.colorFontFxHeader + + readonly property int macroEffectChar: 0x00B6 + readonly property bool isMacroFx: (finalLabel.charCodeAt(0) == macroEffectChar) + + width: 80 + height: 45 + + Defines.Colors { id: colors } + Defines.Settings {id: settings} + + // Level indicator for knobs + Widgets.ProgressBar { + id: slider + progressBarHeight: (sizeState == "small") ? 6 : 9 + progressBarWidth: 76 + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 3 + anchors.leftMargin: 2 + + value: parameter.value + visible: fxEnabled + + drawAsEnabled: indicatorEnabled + + progressBarBackgroundColor: parent.barBgColor + } + + // stepped progress bar + Widgets.StateBar { + id: slider2 + height: (sizeState == "small") ? 6 : 9 + width: 76 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 2 + anchors.topMargin: 3 + + stateCount: parameter.valueRange.steps + currentState: (slider2.stateCount - 1.0 + 0.2) * parameter.value // +.2 to make sure we round in the right direction + visible: parameter.valueRange != undefined && parameter.valueRange.steps > 1 && fxEnabled && label.length > 0 + barBgColor: parent.barBgColor + } + + // Diverse Elements + Item { + id: fxInfoDetailsPanel + + height: 100 + width: parent.width + + Rectangle { + id: macroIconDetails + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.topMargin: 15 + + width: 12 + height: 11 + radius: 1 + visible: isMacroFx + color: colors.colorGrey216 + + Text { + anchors.fill: parent + anchors.topMargin: -1 + anchors.leftMargin: 1 + text: "M" + font.pixelSize: fonts.miniFontSize + color: colors.colorBlack + } + } + + // fx name + Text { + id: fxInfoSampleName + font.capitalization: Font.AllUppercase + text: finalLabel + color: isPatternPlayer ? colors.colorGreenMint : settings.accentColor + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 8 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: isMacroFx ? 26 : 4 + anchors.rightMargin: 12 + elide: Text.ElideRight + } + + // value + Text { + id: fxInfoValueLarge + text: finalValue + font.family: "Pragmatica" // is monospaced + color: colors.colorWhite + visible: label.length > 0 + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 4 + font.pixelSize: 15 + anchors.topMargin: 24 + } + + // button + Rectangle { + id: fxInfoFilterButton + width: 30 + + color: ( fxEnabled ? (isOn ? (isPatternPlayer ? colors.colorGreenMint : colors.colorIndicatorLevelOrange) : colors.colorBlack) : "transparent" ) + visible: buttonLabel.length > 0 + radius: 1 + anchors.right: parent.right + anchors.rightMargin: 2 + anchors.top: parent.top + height: 15 + anchors.topMargin: 26 + + Text { + id: fxInfoFilterButtonText + font.capitalization: Font.AllUppercase + text: finalButtonLabel + color: ( fxEnabled ? (isOn ? colors.colorBlack : colors.colorGrey128) : colors.colorGrey128 ) + font.pixelSize: fonts.miniFontSize + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/Cell.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/Cell.qml new file mode 100755 index 000000000000..7df5c69b37ea --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/Cell.qml @@ -0,0 +1,54 @@ +import QtQuick 2.5 + +Item { + id: cell + property int slotId:0 + property int deckId: 0 + property int cellId: 0 + + readonly property bool isEmpty: propState.description == "Empty" + readonly property color color: isEmpty ? colors.colorDeckBrightGrey : colors.palette(computeBrightness(propState.description, propDisplayState.description), propColorId.value) + readonly property color brightColor: isEmpty ? colors.colorDeckBrightGrey : colors.palette(1., propColorId.value) + readonly property color midColor: isEmpty ? colors.colorDeckGrey : colors.palette(0.5, propColorId.value) + readonly property color dimmedColor: isEmpty ? colors.colorDeckDarkGrey : colors.palette(0., propColorId.value) + + readonly property string name: propName.value + readonly property bool isLooped: propPlayMode.description == "Looped" + + // AppProperty { id: propColorId; path: "app.traktor.decks." + deckId + ".remix.cell.columns." + slotId + ".rows." + cellId + ".color_id" } + QtObject { + id: propColorId + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propName; path: "app.traktor.decks." + deckId + ".remix.cell.columns." + slotId + ".rows." + cellId + ".name" } + QtObject { + id: propName + property string description: "Description" + property var value: 0 + } + //PlayMode can be "Looped" or "OneShot" + // AppProperty { id: propPlayMode; path: "app.traktor.decks." + deckId + ".remix.cell.columns." + slotId + ".rows." + cellId + ".play_mode" } + QtObject { + id: propPlayMode + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propState; path: "app.traktor.decks." + deckId + ".remix.cell.columns." + slotId + ".rows." + cellId + ".state" } + QtObject { + id: propState + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propDisplayState; path: "app.traktor.decks." + deckId + ".remix.cell.columns." + slotId + ".rows." + cellId + ".animation.display_state"} + QtObject { + id: propDisplayState + property string description: "Description" + property var value: 0 + } + + function computeBrightness(state, displayState) { + if (state == "Playing" && displayState == "BrightColor" ) {return 1.;} + return 0.5; + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/DeckInfo.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/DeckInfo.qml new file mode 100755 index 000000000000..bf018e723737 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/DeckInfo.qml @@ -0,0 +1,1568 @@ +import QtQuick 2.5 +import '../Defines' as Defines + +import Mixxx 1.0 as Mixxx + +//---------------------------------------------------------------------------------------------------------------------- +// Track Deck Model - provide data for the track deck view +//---------------------------------------------------------------------------------------------------------------------- + +Item { + id: viewModel + + property string group: `[Channel${viewModel.deckId}]` + readonly property var deckPlayer: Mixxx.PlayerManager.getPlayer(viewModel.group) + readonly property var currentPlayer: viewModel.deckPlayer?.currentTrack + readonly property string screenName: isLeftScreen(viewModel.deckId) ? "leftdeck" : "rightdeck" + + function onSharedDataUpdate(data) { + if (typeof data !== "object") { + return; + } + if (typeof data.group[screenName] === "string") { + viewModel.group = data.group[screenName] + console.log(`Changed group for screen ${screenName} to ${viewModel.group}`); + } + if (typeof data.shift === "object") { + propShift.value = !!data.shift[screenName] + } + if (typeof data.padsMode === "object") { + propPadsMode.value = data.padsMode[viewModel.group] + console.log(`Changed padsMode for screen ${screenName} to ${propPadsMode.value}`); + } + if (typeof data.selectedQuickFX !== "undefined") { + propSelectedQuickFX.value = data.selectedQuickFX + console.log(`Changed selectedQuickFX to ${propSelectedQuickFX.value}`); + } + if (typeof data.selectedStems === "object") { + let firstSelected = (data.selectedStems[viewModel.group] || []).findIndex(x => !!x); + propStemSelected.active = firstSelected >= 0; + if (propStemSelected.active) { + propStemSelected.idx = firstSelected; + } + console.log(`Changed selectedStems for screen ${screenName} to ${propStemSelected.idx}`); + } + if (typeof data.selectedHotcue === "object") { + let hotcue = data.selectedHotcue[viewModel.group]; + + if (hotcue) { + let model = viewModel.currentPlayer?.hotcuesModel?.get(hotcue - 1); + viewModel.hotcueId = hotcue; + viewModel.hotcuePressed = true; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } else { + viewModel.hotcuePressed = false; + } + + console.log(`Changed selectedHotcue for screen ${screenName} to ${hotcue}`); + } + if (typeof data.deckColor === "object") { + propDeckColors.a = data.deckColor["[Channel1]"] + propDeckColors.b = data.deckColor["[Channel2]"] + propDeckColors.c = data.deckColor["[Channel3]"] + propDeckColors.d = data.deckColor["[Channel4]"] + } + if (typeof data.rollpadSize === "object") { + for (let i = 0; i < 8; i++) { + switch (`${data.rollpadSize[i]}`.toLowerCase()) { + case "double": + propRollSizePad[`pad${i+1}`] = "x2" + break; + case "half": + propRollSizePad[`pad${i+1}`] = "/2" + break; + default: + propRollSizePad[`pad${i+1}`] = parseFloat(data.rollpadSize[i]) < 1 ? `1/${1/parseFloat(data.rollpadSize[i])}` : data.rollpadSize[i] + } + } + } + if (typeof data.beatjumpSize === "object") { + for (let i = 0; i < 8; i++) { + switch (`${data.beatjumpSize[i]}`.toLowerCase()) { + case "double": + propJumpSizePad[`pad${i+1}`] = "x2" + break; + case "half": + propJumpSizePad[`pad${i+1}`] = "/2" + break; + case "beatjump": + propJumpSizePad[`pad${i+1}`] = "??" + break; + default: + propJumpSizePad[`pad${i+1}`] = parseFloat(data.beatjumpSize[i]) < 1 ? `1/${1/parseFloat(data.beatjumpSize[i])}` : data.beatjumpSize[i] + } + } + } + } + Component.onCompleted: { + if (typeof engine.makeSharedDataConnection === "function") { + engine.makeSharedDataConnection(viewModel.onSharedDataUpdate) + viewModel.onSharedDataUpdate(engine.getSharedData()) + } + } + + function isLeftScreen(deckId) { + return deckId == 1 || deckId == 3; + } + + function deckLetter(deckId) { + switch (deckId) { + case 1: return "A"; + case 2: return "B"; + case 3: return "C"; + default: + console.error(`Unknown deck ${deckId}. Defaulting to D`); + case 4: + return "D"; + } + } + + function tempoNeeded(master, current) { + if (master > current) { + return (1-(current/master))*100; + } + return (master/current)*100; + } + + function toInt_round(val) { return parseInt(val+0.5); } + + function computeBeatCounterStringFromPosition(beat) { + var phraseLen = 4; + var curBeat = parseInt(beat); + + if (beat < 0.0) + curBeat = curBeat*-1; + + var value1 = parseInt(((curBeat/4)/phraseLen)+1); + var value2 = parseInt(((curBeat/4)%phraseLen)+1); + var value3 = parseInt( (curBeat%4)+1); + + if (beat < 0.0) + return "-" + value1.toString() + "." + value2.toString() + "." + value3.toString(); + + return value1.toString() + "." + value2.toString() + "." + value3.toString(); + } + + function computeBeatCounterStringFromPositionSingle(beat) { + var phraseLen = 4; + var curBeat = parseInt(beat); + + if (beat < 0.0) + curBeat = curBeat*-1; + + var value3 = parseInt( (curBeat%4)+1); + + return value3.toString(); + } + + function computeBeatCounterStringFromPositionAlt(beat) { + var phraseLen = 4; + var curBeat = parseInt(beat); + + if (beat < 0.0) + curBeat = curBeat*-1; + + var value1 = parseInt(((curBeat)/phraseLen)+1); + var value2 = parseInt( (curBeat%4)+1); + + if (beat < 0.0) + return "-" + value1.toString() + "." + value2.toString(); + + return value1.toString() + "." + value2.toString(); + } + + //////////////////////////////////// + ////// Global info properties ////// + //////////////////////////////////// + QtObject { + id: propDeckColors + property int a: 10 + property int b: 10 + property int c: 2 + property int d: 2 + } + QtObject { + id: propRollSizePad + property var pad1: 1/32 + property var pad2: 1/16 + property var pad3: 1/8 + property var pad4: 1/4 + property var pad5: 1/2 + property var pad6: 1 + property var pad7: 2 + property var pad8: 4 + } + QtObject { + id: propJumpSizePad + property var pad1: 0.5 + property var pad2: 1 + property var pad3: 2 + property var pad4: 4 + property var pad5: 8 + property var pad6: 16 + property var pad7: 32 + property var pad8: 64 + } + + readonly property int deckAColor: propDeckColors.a + readonly property int deckBColor: propDeckColors.b + readonly property int deckCColor: propDeckColors.c + readonly property int deckDColor: propDeckColors.d + + //////////////////////////////////// + /////// Track info properties ////// + //////////////////////////////////// + + property int deckId: 1 + readonly property bool trackEndWarning: propTrackEndWarning.value + readonly property bool shift: propShift.value + readonly property string artistString: isLoaded ? propArtist.value : "Mixxx" + readonly property string bpmString: isLoaded ? propBPM.value.toFixed(2).toString() : "0.00" + readonly property string beats: computeBeatCounterStringFromPosition(((propElapsedTime.value*1000-propGridOffset.value)*propMixerBpm.value)/60000.0) + readonly property string beatSingle: computeBeatCounterStringFromPositionSingle(((propElapsedTime.value*1000-propGridOffset.value)*propMixerBpm.value)/60000.0) + readonly property string beatsAlt: computeBeatCounterStringFromPositionAlt(((propElapsedTime.value*1000-propGridOffset.value)*propMixerBpm.value)/60000.0) + readonly property string masterDeckLetter: leaderGroup.replace('[Channel', '').substr(0, 1) + readonly property string masterBPM: isLoaded ? propMasterBPM.value : 0.00 + readonly property string masterBPMShort: isLoaded ? propMasterBPM.value.toFixed(2).toString() : 0.00 + readonly property string masterBPMFooter: isLoaded ? propMasterBPM.value.toFixed(2).toString() + " BPM" : "" + readonly property string masterBPMFooter2: isLoaded ? propMasterBPM.value.toFixed(2).toString() + "BPM" : "" + readonly property string bpmOffset: isLoaded ? (bpmString - masterBPM).toFixed(2).toString() : "0.00" + readonly property string tempoString: isLoaded ? (propTempo.value).toFixed(2).toString() : "0.00" + readonly property string tempoRange: toInt_round(propTempoRange.value*100).toString() + "%" + readonly property string tempoStringPer: tempoString+'%' + readonly property string tempoNeededVal: tempoNeeded(masterBPMShort, bpmString).toFixed(2).toString() + readonly property string tempoNeededString: isLoaded ? (tempoNeededVal == 0) ? "0.00" : (tempoNeededVal < 0) ? tempoNeededVal + "%" : "+" + tempoNeededVal + "%" : "0.00" + readonly property string songBPM: propSongBPM.value.toFixed(2).toString() + readonly property bool hightlightLoop: !shift + readonly property bool hightlightKey: shift + readonly property int isLoaded: (propTrackLength.value > 0) + readonly property bool showLogo: propTrackLength.value == 0 ? true : false + readonly property string keyString: propKeyForDisplay.value + readonly property string masterKey: propMasterKey.value + readonly property int keyIndex: propFinalKeyId.value + readonly property int masterKeyIndex: propMasterKeyId.value + readonly property bool hasKey: isLoaded && keyIndex >= 0 + readonly property bool hasTempo: isLoaded && !!propTempo.value + readonly property bool isKeyLockOn: propKeyLockOn.value + readonly property bool isSyncOn: propIsInSync.value + readonly property bool isStemDeck: (propIsStemDeck.value >= 2) ? true : false + readonly property bool loopActive: propLoopActive.value + readonly property string loopSizeString: propLoopSize.value < 1 ? `1/${1 / propLoopSize.value}` : `${propLoopSize.value}` + readonly property string loopSizeInt: propLoopSize.value + readonly property string remainingTimeString: (!isLoaded) ? "00:00" : utils.computeRemainingTimeString(propTrackLength.value, propElapsedTime.value) + readonly property string elapsedTimeString: (!isLoaded) ? "00:00" : utils.convertToTimeString(Math.floor(propElapsedTime.value)) + readonly property string titleString: isLoaded ? propTitle.value : "Load a Track to Deck " + deckLetter(deckId) + readonly property real phase: isPlaying && leaderGroup != group ? propPhase.value : 0 + readonly property bool touchKey: false // TODO map shift encoder touch event + readonly property bool touchTime: false // TODO map shift encoder touch event + readonly property bool touchLoop: false // TODO map shift encoder touch event + readonly property int deckType: propDeckType.value + readonly property string keyAdjustString: (keyAdjustVal < 0 ? "" : "+") + (keyAdjustVal).toFixed(0).toString() + readonly property real keyAdjustVal: propKeyAdjust.value*12 + readonly property variant loopSizeText: ["1/32", "1/16", "1/8", "1/4", "1/2", "1", "2", "4", "8", "16", "32"] + readonly property bool slicerEnabled: propEnabled.value + readonly property int slicerNo: propSlicerNo.value + readonly property int slicerSize: propSlicerSize.value + + readonly property bool headerEnabled: propHeaderEnabled.value + readonly property string headerText: propHeaderText.value + readonly property string headerTextLong: propHeaderTextLong.value + readonly property int sampleRate: propSampleRate.value + + readonly property bool isPlaying: propIsPlaying.value + + readonly property bool is1Playing: propIs1Playing.value + readonly property bool is2Playing: propIs2Playing.value + readonly property bool is3Playing: propIs3Playing.value + readonly property bool is4Playing: propIs4Playing.value + + Mixxx.ControlProxy { + group: viewModel.group + key: "track_samplerate" + id: propSampleRate + } + Mixxx.ControlProxy { + group: viewModel.leaderGroup + key: "track_samplerate" + id: propLeaderSampleRate + } + Mixxx.ControlProxy { + group: viewModel.group + id: propTempoRange + key: "rateRange" + } + QtObject { + id: propEnabled + property var value: 0 + } + QtObject { + id: propSlicerNo + property var value: 0 + } + QtObject { + id: propSlicerSize + property var value: 0 + } + QtObject { + id: propDeckType + property var value: 0 + } + Mixxx.ControlProxy { + group: viewModel.group + key: "play" + id: propIsPlaying + } + + Mixxx.ControlProxy { + group: "[Channel1]" + id: propIs1Leader + key: "sync_mode" + } + + Mixxx.ControlProxy { + group: "[Channel2]" + id: propIs2Leader + key: "sync_mode" + } + + Mixxx.ControlProxy { + group: "[Channel3]" + id: propIs3Leader + key: "sync_mode" + } + + Mixxx.ControlProxy { + group: "[Channel4]" + id: propIs4Leader + key: "sync_mode" + } + + readonly property string leaderGroup: propIs1Leader.value >= 2 ? `[Channel1]` : propIs2Leader.value >= 2 ? `[Channel2]` : propIs3Leader.value >= 2 ? `[Channel3]` : propIs4Leader.value >= 2 ? `[Channel4]` : viewModel.group + + Mixxx.ControlProxy { + group: "[Channel1]" + key: "play" + id: propIs1Playing + } + Mixxx.ControlProxy { + group: "[Channel2]" + key: "play" + id: propIs2Playing + } + Mixxx.ControlProxy { + group: "[Channel3]" + key: "play" + id: propIs3Playing + } + Mixxx.ControlProxy { + group: "[Channel4]" + key: "play" + id: propIs4Playing + } + + QtObject { + id: propTitle + property var value: viewModel.currentPlayer?.title || "Unknown" + } + QtObject { + id: propArtist + property var value: viewModel.currentPlayer?.artist || "Unknown" + } + Mixxx.ControlProxy { + group: viewModel.group + id: propSongBPM + key: "file_bpm" + } + + Mixxx.ControlProxy { + group: viewModel.group + id: propKey + key: "key" + } + QtObject { + id: propKeyForDisplay + property var value: [ + "No key", + "1d", + "8d", + "3d", + "10d", + "5d", + "12d", + "7d", + "2d", + "9d", + "4d", + "11d", + "6d", + "10m", + "5m", + "12m", + "7m", + "2m", + "9m", + "4m", + "11m", + "6m", + "1m", + "8m", + "3m" + ][propKey.value] + } + QtObject { + id: propMasterKey + property var value: 0 + } + QtObject { + id: propMixerBpm + property var value: 0 + } + QtObject { + id: propMixerBpmMaster + property var value: 160 + } + QtObject { + id: propFinalKeyId + property var value: propKey.value + } + Mixxx.ControlProxy { + group: viewModel.leaderGroup + id: propMasterKeyId + key: "key" + } + QtObject { + id: propKeyAdjust + property var value: 0 + } + QtObject { + id: propGridOffset + property var value: 0 + } + QtObject { + id: propGridOffsetMaster + property var value: 10000 + } + + Mixxx.ControlProxy { + group: viewModel.group + id: propKeyLockOn + key: "keylock" + } + Mixxx.ControlProxy { + group: viewModel.group + key: "bpm" + id: propBPM + } + Mixxx.ControlProxy { + group: '[InternalClock]' + key: "bpm" + id: propMasterBPM + } + Mixxx.ControlProxy { + group: viewModel.group + key: "visual_bpm" + id: propTempo + } + QtObject { + id: propTempoAbsolute + property var value: 0 + } + + Mixxx.ControlProxy { + group: viewModel.group + key: "beat_closest" + id: propBeatClosest + } + Mixxx.ControlProxy { + group: viewModel.group + key: "track_samples" + id: propSample + } + QtObject { + id: propBeatSample + property var value: (propSampleRate.value * 60) / propBPM.value + } + QtObject { + id: propBeatSampleOffset + property var value: propBeatClosest.value % propBeatSample.value + } + QtObject { + id: propBeat + property var value: (propTrackPosition.value * propSample.value / 2) / propBeatSample.value + } + Mixxx.ControlProxy { + group: viewModel.leaderGroup + key: "beat_closest" + id: propLeaderBeatClosest + } + Mixxx.ControlProxy { + group: viewModel.leaderGroup + key: "track_samples" + id: propLeaderSample + } + QtObject { + id: propLeaderBeatSample + property var value: (propLeaderSampleRate.value * 60) / propMasterBPM.value + } + QtObject { + id: propLeaderBeatSampleOffset + property var value: propLeaderBeatClosest.value % propLeaderBeatSample.value + } + QtObject { + id: propLeaderBeat + property var value: (propLeaderTrackPosition.value * propLeaderSample.value / 2) / propLeaderBeatSample.value + } + QtObject { + id: propPhase + property var value: (propLeaderBeat.value-propBeat.value - 0.5) % 1 - 0.5 + } + Mixxx.ControlProxy { + group: viewModel.group + key: "beatloop_size" + id: propLoopSize + } + Mixxx.ControlProxy { + id: propLoopActive + group: viewModel.group + key: "loop_enabled" + } + QtObject { + id: proploopActive + property var value: 0 + } + Mixxx.ControlProxy { + id: propTrackLength + group: viewModel.group + key: "duration" + } + Mixxx.ControlProxy { + id: propTrackPosition + group: viewModel.group + key: "playposition" + } + Mixxx.ControlProxy { + id: propLeaderTrackPosition + group: viewModel.leaderGroup + key: "playposition" + } + QtObject { + id: propElapsedTime + property var value: parseInt(propTrackPosition.value * propTrackLength.value) + } + Mixxx.ControlProxy { + group: viewModel.group + key: `end_of_track` + id: propTrackEndWarning + } + + QtObject { + id: propHeaderEnabled + property var value: false + } + QtObject { + id: propHeaderText + property var value: "HeaderText" + } + QtObject { + id: propHeaderTextLong + property var value: "HeaderTextLong" + } + + Mixxx.ControlProxy { + group: viewModel.group + key: "stem_count" + id: propIsStemDeck + } + + Timer { + id: loopAdjust + property bool show: false + + triggeredOnStart: true + interval: settings.loopOverlayTimer + repeat: false + running: false + + onTriggered: { + show = !show + } + } + + Mixxx.ControlProxy { + group: viewModel.group + key: "beats_translate_curpos" + id: propBeatsTranslateCurpos + onValueChanged: { + loopAdjust.running = true + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: "beats_adjust_faster" + id: propBeatsAdjustFaster + onValueChanged: { + loopAdjust.running = true + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: "beats_adjust_slower" + id: propBeatsAdjustSlower + onValueChanged: { + loopAdjust.running = true + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: "beats_translate_later" + id: propBeatsTranslateLater + onValueChanged: { + loopAdjust.running = true + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: "beats_translate_earlier" + id: propBeatsTranslateEarlier + onValueChanged: { + loopAdjust.running = true + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: "rateRange" + id: propRateRange + onValueChanged: { + loopAdjust.running = true + } + } + + readonly property bool adjustEnabled: settings.showBPMGridAdjust ? loopAdjust.show : false + + QtObject { + id: propPadsMode + property var value: 0 + } + QtObject { + id: propSelectedQuickFX + property var value: null + } + readonly property var quickFXSelected: propSelectedQuickFX.value + property bool padsModeJump: propPadsMode.value == 1 + property bool padsModeLoop: propPadsMode.value == 5 + property bool padsModeRoll: propPadsMode.value == 3 + property bool padsModeTone: propPadsMode.value == 11 + property bool padsModeBank1: propPadsMode.value == 12 + property bool padsModeBank2: propPadsMode.value == 13 + + Mixxx.ControlProxy { + id: propIsInSync + group: root.group + key: "sync_enabled" + } + + Mixxx.ControlProxy { + id: propBrowser + group: "[Skin]" + key: "show_maximized_library" + } + readonly property bool isInBrowserMode: propBrowser.value + + QtObject { + id: propShift + property bool value: false + } + + Mixxx.ControlProxy { + id: propZoom + + group: root.group + key: "waveform_zoom" + onValueChanged: { + loopAdjust.running = true + } + } + + readonly property int zoomLevel: propZoom.value + + //fx and overlays + property var fxModel: Mixxx.EffectsManager.visibleEffectsModel + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: "mix_mode" + id: propFx1Type + } + readonly property int fx1Type: propFx1Type.value + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: "mix_mode" + id: propFx2Type + } + readonly property int fx2Type: propFx2Type.value + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: "mix_mode" + id: propFx3Type + } + readonly property int fx3Type: propFx3Type.value + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: "mix_mode" + id: propFx4Type + } + readonly property int fx4Type: propFx4Type.value + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: "mix" + id: propFx1DryWet + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: "mix" + id: propFx2DryWet + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect1]" + key: `meta` + id: propFx1Knob1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect2]" + key: `meta` + id: propFx1Knob2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect3]" + key: `meta` + id: propFx1Knob3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2_Effect1]" + key: `meta` + id: propFx2Knob1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2_Effect2]" + key: `meta` + id: propFx2Knob2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2_Effect3]" + key: `meta` + id: propFx2Knob3 + } + + Mixxx.ControlProxy { + id: propFx1Knob1Name + group: "[EffectRack1_EffectUnit1_Effect1]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect2]" + key: "loaded_effect" + id: propFx1Knob2Name + } + Mixxx.ControlProxy { + id: propFx1Knob3Name + group: "[EffectRack1_EffectUnit1_Effect3]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx2Knob1Name + group: "[EffectRack1_EffectUnit2_Effect1]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx2Knob2Name + group: "[EffectRack1_EffectUnit2_Effect2]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx2Knob3Name + group: "[EffectRack1_EffectUnit2_Effect3]" + key: "loaded_effect" + } + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: "enabled" + id: propFx1Enabled + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: `enabled` + id: propFx2Enabled + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect1]" + key: `enabled` + id: propFx1Button1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect2]" + key: `enabled` + id: propFx1Button2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1_Effect3]" + key: `enabled` + id: propFx1Button3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2_Effect1]" + key: `enabled` + id: propFx2Button1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2_Effect2]" + key: `enabled` + id: propFx2Button2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2_Effect3]" + key: `enabled` + id: propFx2Button3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: `group_[Channel1]_enable` + id: propFx1Ch1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: `group_[Channel2]_enable` + id: propFx1Ch2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: `group_[Channel3]_enable` + id: propFx1Ch3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: `group_[Channel4]_enable` + id: propFx1Ch4 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: `group_[Channel1]_enable` + id: propFx2Ch1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: `group_[Channel2]_enable` + id: propFx2Ch2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: `group_[Channel3]_enable` + id: propFx2Ch3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: `group_[Channel4]_enable` + id: propFx2Ch4 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: `group_[Channel1]_enable` + id: propFx3Ch1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: `group_[Channel2]_enable` + id: propFx3Ch2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: `group_[Channel3]_enable` + id: propFx3Ch3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: `group_[Channel4]_enable` + id: propFx3Ch4 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: `group_[Channel1]_enable` + id: propFx4Ch1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: `group_[Channel2]_enable` + id: propFx4Ch2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: `group_[Channel3]_enable` + id: propFx4Ch3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: `group_[Channel4]_enable` + id: propFx4Ch4 + } + + readonly property real fx1DryWet: propFx1DryWet.value + readonly property real fx2DryWet: propFx2DryWet.value + readonly property real fx1Knob1: propFx1Knob1.value + readonly property real fx1Knob2: propFx1Knob2.value + readonly property real fx1Knob3: propFx1Knob3.value + readonly property real fx2Knob1: propFx2Knob1.value + readonly property real fx2Knob2: propFx2Knob2.value + readonly property real fx2Knob3: propFx2Knob3.value + + readonly property string fx1Knob1Name: viewModel.fxModel.get(propFx1Knob1Name.value).display + readonly property string fx1Knob2Name: viewModel.fxModel.get(propFx1Knob2Name.value).display + readonly property string fx1Knob3Name: viewModel.fxModel.get(propFx1Knob3Name.value).display + readonly property string fx2Knob1Name: viewModel.fxModel.get(propFx2Knob1Name.value).display + readonly property string fx2Knob2Name: viewModel.fxModel.get(propFx2Knob2Name.value).display + readonly property string fx2Knob3Name: viewModel.fxModel.get(propFx2Knob3Name.value).display + + readonly property bool fx1Enabled: propFx1Enabled.value + readonly property bool fx2Enabled: propFx2Enabled.value + readonly property bool fx1Button1: propFx1Button1.value + readonly property bool fx1Button2: propFx1Button2.value + readonly property bool fx1Button3: propFx1Button3.value + readonly property bool fx2Button1: propFx2Button1.value + readonly property bool fx2Button2: propFx2Button2.value + readonly property bool fx2Button3: propFx2Button3.value + + readonly property bool fx1Ch1: propFx1Ch1.value + readonly property bool fx1Ch2: propFx1Ch2.value + readonly property bool fx1Ch3: propFx1Ch3.value + readonly property bool fx1Ch4: propFx1Ch4.value + readonly property bool fx2Ch1: propFx2Ch1.value + readonly property bool fx2Ch2: propFx2Ch2.value + readonly property bool fx2Ch3: propFx2Ch3.value + readonly property bool fx2Ch4: propFx2Ch4.value + + onFx1DryWetChanged: {fx1Timer.running = true} + onFx2DryWetChanged: {fx2Timer.running = true} + onFx1Knob1Changed: {fx1Timer.running = true} + onFx1Knob2Changed: {fx1Timer.running = true} + onFx1Knob3Changed: {fx1Timer.running = true} + onFx2Knob1Changed: {fx2Timer.running = true} + onFx2Knob2Changed: {fx2Timer.running = true} + onFx2Knob3Changed: {fx2Timer.running = true} + onFx1EnabledChanged: {fx1Timer.running = true} + onFx2EnabledChanged: {fx2Timer.running = true} + onFx1Button1Changed: {fx1Timer.running = true} + onFx1Button2Changed: {fx1Timer.running = true} + onFx1Button3Changed: {fx1Timer.running = true} + onFx2Button1Changed: {fx2Timer.running = true} + onFx2Button2Changed: {fx2Timer.running = true} + onFx2Button3Changed: {fx2Timer.running = true} + onFx1Ch1Changed: {fx1Timer.running = true} + onFx1Ch2Changed: {fx1Timer.running = true} + onFx1Ch3Changed: {fx1Timer.running = true} + onFx1Ch4Changed: {fx1Timer.running = true} + onFx2Ch1Changed: {fx2Timer.running = true} + onFx2Ch2Changed: {fx2Timer.running = true} + onFx2Ch3Changed: {fx2Timer.running = true} + onFx2Ch4Changed: {fx2Timer.running = true} + onFx1Knob1NameChanged: {fx1Timer.running = true} + onFx1Knob2NameChanged: {fx1Timer.running = true} + onFx1Knob3NameChanged: {fx1Timer.running = true} + onFx2Knob1NameChanged: {fx2Timer.running = true} + onFx2Knob2NameChanged: {fx2Timer.running = true} + onFx2Knob3NameChanged: {fx2Timer.running = true} + + onLoopSizeStringChanged: {loopTimer.running = true} + onLoopActiveChanged: {loopTimer.running = true} + + Timer { + id: loopTimer + property bool showLoop: false + + triggeredOnStart: true + interval: settings.loopOverlayTimer + repeat: false + running: false + + onTriggered: { + showLoop = !showLoop + } + } + + property bool showLoopInfo: loopTimer.showLoop + + onBpmStringChanged: {bpmTimer.running = true} + + Timer { + id: bpmTimer + property bool showBPM: false + + triggeredOnStart: true + interval: settings.bpmOverlayTimer + repeat: false + running: false + + onTriggered: { + showBPM = !showBPM + } + } + + property bool showBPMInfo: bpmTimer.showBPM && bpmTimer.running + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: "mix" + id: propFx3DryWet + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: "mix" + id: propFx4DryWet + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3_Effect1]" + key: `meta` + id: propFx3Knob1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3_Effect2]" + key: `meta` + id: propFx3Knob2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3_Effect3]" + key: `meta` + id: propFx3Knob3 + } + Mixxx.ControlProxy { + id: propFx4Knob1 + group: "[EffectRack1_EffectUnit4_Effect1]" + key: `meta` + } + Mixxx.ControlProxy { + id: propFx4Knob2 + group: "[EffectRack1_EffectUnit4_Effect2]" + key: `meta` + } + Mixxx.ControlProxy { + id: propFx4Knob3 + group: "[EffectRack1_EffectUnit4_Effect3]" + key: `meta` + } + + Mixxx.ControlProxy { + id: propFx3Knob1Name + group: "[EffectRack1_EffectUnit3_Effect1]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx3Knob2Name + group: "[EffectRack1_EffectUnit3_Effect2]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx3Knob3Name + group: "[EffectRack1_EffectUnit3_Effect3]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx4Knob1Name + group: "[EffectRack1_EffectUnit4_Effect1]" + key: `loaded_effect` + } + Mixxx.ControlProxy { + id: propFx4Knob2Name + group: "[EffectRack1_EffectUnit4_Effect2]" + key: "loaded_effect" + } + Mixxx.ControlProxy { + id: propFx4Knob3Name + group: "[EffectRack1_EffectUnit4_Effect3]" + key: `loaded_effect` + } + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: "enabled" + id: propFx3Enabled + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: "enabled" + id: propFx4Enabled + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3_Effect1]" + key: `enabled` + id: propFx3Button1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3_Effect2]" + key: `enabled` + id: propFx3Button2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3_Effect3]" + key: `enabled` + id: propFx3Button3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4_Effect1]" + key: `enabled` + id: propFx4Button1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4_Effect2]" + key: `enabled` + id: propFx4Button2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4_Effect3]" + key: `enabled` + id: propFx4Button3 + } + + readonly property real fx3DryWet: propFx3DryWet.value + readonly property real fx4DryWet: propFx3DryWet.value + readonly property real fx3Knob1: propFx3Knob1.value + readonly property real fx3Knob2: propFx3Knob2.value + readonly property real fx3Knob3: propFx3Knob3.value + readonly property real fx4Knob1: propFx4Knob1.value + readonly property real fx4Knob2: propFx4Knob2.value + readonly property real fx4Knob3: propFx4Knob3.value + + readonly property string fx3Knob1Name: viewModel.fxModel.get(propFx3Knob1Name.value).display + readonly property string fx3Knob2Name: viewModel.fxModel.get(propFx3Knob2Name.value).display + readonly property string fx3Knob3Name: viewModel.fxModel.get(propFx3Knob3Name.value).display + readonly property string fx4Knob1Name: viewModel.fxModel.get(propFx4Knob1Name.value).display + readonly property string fx4Knob2Name: viewModel.fxModel.get(propFx4Knob2Name.value).display + readonly property string fx4Knob3Name: viewModel.fxModel.get(propFx4Knob3Name.value).display + + readonly property bool fx3Enabled: propFx3Enabled.value + readonly property bool fx4Enabled: propFx4Enabled.value + readonly property bool fx3Button1: propFx3Button1.value + readonly property bool fx3Button2: propFx3Button2.value + readonly property bool fx3Button3: propFx3Button3.value + readonly property bool fx4Button1: propFx4Button1.value + readonly property bool fx4Button2: propFx4Button2.value + readonly property bool fx4Button3: propFx4Button3.value + + readonly property bool fx3Ch1: propFx3Ch1.value + readonly property bool fx3Ch2: propFx3Ch2.value + readonly property bool fx3Ch3: propFx3Ch3.value + readonly property bool fx3Ch4: propFx3Ch4.value + readonly property bool fx4Ch1: propFx4Ch1.value + readonly property bool fx4Ch2: propFx4Ch2.value + readonly property bool fx4Ch3: propFx4Ch3.value + readonly property bool fx4Ch4: propFx4Ch4.value + + onFx3DryWetChanged: {fx3Timer.running = true} + onFx4DryWetChanged: {fx4Timer.running = true} + onFx3Knob1Changed: {fx3Timer.running = true} + onFx3Knob2Changed: {fx3Timer.running = true} + onFx3Knob3Changed: {fx3Timer.running = true} + onFx4Knob1Changed: {fx4Timer.running = true} + onFx4Knob2Changed: {fx4Timer.running = true} + onFx4Knob3Changed: {fx4Timer.running = true} + onFx3EnabledChanged: {fx3Timer.running = true} + onFx4EnabledChanged: {fx4Timer.running = true} + onFx3Button1Changed: {fx3Timer.running = true} + onFx3Button2Changed: {fx3Timer.running = true} + onFx3Button3Changed: {fx3Timer.running = true} + onFx4Button1Changed: {fx4Timer.running = true} + onFx4Button2Changed: {fx4Timer.running = true} + onFx4Button3Changed: {fx4Timer.running = true} + onFx3Ch1Changed: {fx3Timer.running = true} + onFx3Ch2Changed: {fx3Timer.running = true} + onFx3Ch3Changed: {fx3Timer.running = true} + onFx3Ch4Changed: {fx3Timer.running = true} + onFx4Ch1Changed: {fx4Timer.running = true} + onFx4Ch2Changed: {fx4Timer.running = true} + onFx4Ch3Changed: {fx4Timer.running = true} + onFx4Ch4Changed: {fx4Timer.running = true} + onFx3Knob1NameChanged: {fx3Timer.running = true} + onFx3Knob2NameChanged: {fx3Timer.running = true} + onFx3Knob3NameChanged: {fx3Timer.running = true} + onFx4Knob1NameChanged: {fx4Timer.running = true} + onFx4Knob2NameChanged: {fx4Timer.running = true} + onFx4Knob3NameChanged: {fx4Timer.running = true} + + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit1]" + key: `group_${viewModel.group}_enable` + id: propfx1 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit2]" + key: `group_${viewModel.group}_enable` + id: propfx2 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit3]" + key: `group_${viewModel.group}_enable` + id: propfx3 + } + Mixxx.ControlProxy { + group: "[EffectRack1_EffectUnit4]" + key: `group_${viewModel.group}_enable` + id: propfx4 + } + + readonly property bool fx1On: propfx1.value + readonly property bool fx2On: propfx2.value + readonly property bool fx3On: propfx3.value + readonly property bool fx4On: propfx4.value + + Timer { + id: fx1Timer + property bool blink: false + + triggeredOnStart: true + interval: settings.fxOverlayTimer + repeat: false + running: fx1On + + onTriggered: { + blink = !blink + } + } + + Timer { + id: fx2Timer + property bool blink: false + + triggeredOnStart: true + interval: settings.fxOverlayTimer + repeat: false + running: fx2On + + onTriggered: { + blink = !blink + } + } + + Timer { + id: fx3Timer + property bool blink: false + + triggeredOnStart: true + interval: settings.fxOverlayTimer + repeat: false + running: fx3On + + onTriggered: { + blink = !blink + } + } + + Timer { + id: fx4Timer + property bool blink: false + + triggeredOnStart: true + interval: settings.fxOverlayTimer + repeat: false + running: fx4On + + onTriggered: { + blink = !blink + } + } + + readonly property bool showFx1: fx1On && fx1Timer.blink + readonly property bool showFx2: fx2On && fx2Timer.blink + readonly property bool showFx3: fx3On && fx3Timer.blink + readonly property bool showFx4: fx4On && fx4Timer.blink + + Mixxx.ControlProxy { + id: propView + group: "[Skin]" + key: "show_maximized_library" + } + + readonly property bool viewButton: propView.value && false + + property int hotcueId: 0 + readonly property bool hotcueDisplay: hotcuePressed || cueTimer.running + property string hotcueName: "" + property int hotcueType: 0 + + property bool hotcuePressed: false + onHotcuePressedChanged: {hotcuePressed == false ? cueTimer.restart() : hotcuePressed = hotcuePressed } + + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_1_activate` + id: propHotcue1Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(0); + viewModel.hotcueId = 1; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_2_activate` + id: propHotcue2Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(1); + viewModel.hotcueId = 2; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_3_activate` + id: propHotcue3Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(2); + viewModel.hotcueId = 3; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_4_activate` + id: propHotcue4Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(3); + viewModel.hotcueId = 4; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_5_activate` + id: propHotcue5Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(4); + viewModel.hotcueId = 5; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_6_activate` + id: propHotcue6Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(5); + viewModel.hotcueId = 6; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_7_activate` + id: propHotcue7Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(6); + viewModel.hotcueId = 7; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + Mixxx.ControlProxy { + group: viewModel.group + key: `hotcue_8_activate` + id: propHotcue8Activated + onValueChanged: { + let model = viewModel.currentPlayer?.hotcuesModel?.get(7); + viewModel.hotcueId = 8; + viewModel.hotcuePressed = value; + viewModel.hotcueName = model?.label || "Unnamed cue"; + viewModel.hotcueType = model?.isLoop ? 5 : 0; + } + } + + Timer { + id: cueTimer + property bool blink: false + + triggeredOnStart: true + interval: 1000 + repeat: false + running: false + } + + /////////////////////////////////////////////////// + /////// Stem Deck properties ////////////////////// + /////////////////////////////////////////////////// + + Mixxx.ControlProxy { + group: viewModel.group + key: `stem_count` + id: propStemCount + } + + readonly property bool isStemsActive: propStemCount.value > 0 + readonly property int stemCount: propStemCount.value + + QtObject { + id: propStemSelected + property var idx: 0 + property bool active: false + } + readonly property bool stemSelected: propStemSelected.active + readonly property var stemSelectedIdx: propStemSelected.idx + + readonly property string stemSelectedName: viewModel.currentPlayer?.stemsModel.get(viewModel.stemSelectedIdx).label || "Unknown" + readonly property real stemSelectedVolume: isStemsActive ? [propStem1Volume,propStem2Volume,propStem3Volume,propStem4Volume][viewModel.stemSelectedIdx].value : 0.0 + readonly property bool stemSelectedMuted: isStemsActive ? [propStem1Muted,propStem2Muted,propStem3Muted,propStem4Muted][viewModel.stemSelectedIdx].value : false + readonly property int stemSelectedQuickFXId: isStemsActive ? [propStem1FX,propStem2FX,propStem3FX,propStem4FX][viewModel.stemSelectedIdx].value : 0 + readonly property real stemSelectedQuickFXValue: isStemsActive ? [propStem1FXValue,propStem2FXValue,propStem3FXValue,propStem4FXValue][viewModel.stemSelectedIdx].value : 0.0 + readonly property bool stemSelectedQuickFXOn: isStemsActive ? [propStem1FXOn,propStem2FXOn,propStem3FXOn,propStem4FXOn][viewModel.stemSelectedIdx].value : false + readonly property string stemSelectedQuickFXName: Mixxx.EffectsManager.quickChainPresetModel.get(viewModel.stemSelectedQuickFXId).display || "---" + readonly property color stemSelectedBrightColor: viewModel.currentPlayer?.stemsModel.get(viewModel.stemSelectedIdx).color ?? "grey" + readonly property color stemSelectedMidColor: isStemsActive ? stemSelectedBrightColor : "black" + + Mixxx.ControlProxy { + id: propStem1Volume + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem1]` + key: `volume` + } + Mixxx.ControlProxy { + id: propStem1Muted + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem1]` + key: `mute` + } + Mixxx.ControlProxy { + id: propStem1FX + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem1]]` + key: `loaded_chain_preset` + } + Mixxx.ControlProxy { + id: propStem1FXOn + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem1]]` + key: `enabled` + } + Mixxx.ControlProxy { + id: propStem1FXValue + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem1]]` + key: `super1` + } + Mixxx.ControlProxy { + id: propStem2Volume + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem2]` + key: `volume` + } + Mixxx.ControlProxy { + id: propStem2Muted + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem2]` + key: `mute` + } + Mixxx.ControlProxy { + id: propStem2FX + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem2]]` + key: `loaded_chain_preset` + } + Mixxx.ControlProxy { + id: propStem2FXOn + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem2]]` + key: `enabled` + } + Mixxx.ControlProxy { + id: propStem2FXValue + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem2]]` + key: `super1` + } + Mixxx.ControlProxy { + id: propStem3Volume + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem3]` + key: `volume` + } + Mixxx.ControlProxy { + id: propStem3Muted + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem3]` + key: `mute` + } + Mixxx.ControlProxy { + id: propStem3FX + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem3]]` + key: `loaded_chain_preset` + } + Mixxx.ControlProxy { + id: propStem3FXOn + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem3]]` + key: `enabled` + } + Mixxx.ControlProxy { + id: propStem3FXValue + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem3]]` + key: `super1` + } + Mixxx.ControlProxy { + id: propStem4Volume + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem4]` + key: `volume` + } + Mixxx.ControlProxy { + id: propStem4Muted + group: `${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem4]` + key: `mute` + } + Mixxx.ControlProxy { + id: propStem4FX + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem4]]` + key: `loaded_chain_preset` + } + Mixxx.ControlProxy { + id: propStem4FXOn + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem4]]` + key: `enabled` + } + Mixxx.ControlProxy { + id: propStem4FXValue + group: `[QuickEffectRack1_${viewModel.group.substr(0, viewModel.group.length - 1)}_Stem4]]` + key: `super1` + } + + /////////////////////////////////////////////////// + /////// Stripe properties ///////////////////////// + /////////////////////////////////////////////////// + + readonly property var hotcues: viewModel.currentPlayer?.hotcuesModel + + /// Loop size + readonly property var loopSizePad1: "1/4" + readonly property var loopSizePad2: "1/2" + readonly property var loopSizePad3: "1" + readonly property var loopSizePad4: "2" + readonly property var loopSizePad5: "4" + readonly property var loopSizePad6: "8" + readonly property var loopSizePad7: "16" + readonly property var loopSizePad8: "32" + + readonly property var jumpSizePad1: propJumpSizePad.pad1 + readonly property var jumpSizePad2: propJumpSizePad.pad2 + readonly property var jumpSizePad3: propJumpSizePad.pad3 + readonly property var jumpSizePad4: propJumpSizePad.pad4 + readonly property var jumpSizePad5: propJumpSizePad.pad5 + readonly property var jumpSizePad6: propJumpSizePad.pad6 + readonly property var jumpSizePad7: propJumpSizePad.pad7 + readonly property var jumpSizePad8: propJumpSizePad.pad8 + + readonly property var rollSizePad1: propRollSizePad.pad1 + readonly property var rollSizePad2: propRollSizePad.pad2 + readonly property var rollSizePad3: propRollSizePad.pad3 + readonly property var rollSizePad4: propRollSizePad.pad4 + readonly property var rollSizePad5: propRollSizePad.pad5 + readonly property var rollSizePad6: propRollSizePad.pad6 + readonly property var rollSizePad7: propRollSizePad.pad7 + readonly property var rollSizePad8: propRollSizePad.pad8 + } diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/HotCue.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/HotCue.qml new file mode 100755 index 000000000000..005df81c6fe9 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/HotCue.qml @@ -0,0 +1,42 @@ +import QtQuick 2.5 + +Item { + id: hotcue + readonly property real position: propPosition.value + readonly property real length: propLength.value + readonly property string type: propType.value + readonly property string name: propName.value + readonly property bool exists: propExists.value + property int index: 0 + + // AppProperty { id: propPosition; path: "app.traktor.decks." + deckId + ".track.cue.hotcues." + (index + 1) + ".start_pos" } + QtObject { + id: propPosition + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propLength; path: "app.traktor.decks." + deckId + ".track.cue.hotcues." + (index + 1) + ".length" } + QtObject { + id: propLength + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propType; path: "app.traktor.decks." + deckId + ".track.cue.hotcues." + (index + 1) + ".type" } + QtObject { + id: propType + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propName; path: "app.traktor.decks." + deckId + ".track.cue.hotcues." + (index + 1) + ".name" } + QtObject { + id: propName + property string description: "Description" + property var value: 0 + } + // AppProperty { id: propExists; path: "app.traktor.decks." + deckId + ".track.cue.hotcues." + (index + 1) + ".exists" } + QtObject { + id: propExists + property string description: "Description" + property var value: 0 + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/HotCues.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/HotCues.qml new file mode 100755 index 000000000000..3961fc236235 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/HotCues.qml @@ -0,0 +1,65 @@ +import QtQuick 2.5 + +Item { + id: hotcuesModel + property int deckId: 0 + + readonly property alias activeHotcue: activeHotcueModel + readonly property var array: + [ + hotcueModel1, + hotcueModel2, + hotcueModel3, + hotcueModel4, + hotcueModel5, + hotcueModel6, + hotcueModel7, + hotcueModel8 + ] + + Item { + id: activeHotcueModel + readonly property real position: activePos.value + readonly property real length: activeLength.value + readonly property string type: activeType.value + readonly property string name: activeName.value + + // AppProperty { id: activePos; path: "app.traktor.decks." + deckId + ".track.cue.active.start_pos" } + QtObject { + id: activePos + property string description: "Description" + property var value: 0 + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: activeLength; path: "app.traktor.decks." + deckId + ".track.cue.active.length" } + QtObject { + id: activeLength + property string description: "Description" + property var value: 0 + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: activeType; path: "app.traktor.decks." + deckId + ".track.cue.active.type" } + QtObject { + id: activeType + property string description: "Description" + property var value: 0 + property var valueRange: ({isDiscrete: true, steps: 1}) + } + // AppProperty { id: activeName; path: "app.traktor.decks." + deckId + ".track.cue.active.name" } + QtObject { + id: activeName + property string description: "Description" + property var value: 0 + property var valueRange: ({isDiscrete: true, steps: 1}) + } + } + + HotCue { id: hotcueModel1; index: 0 } + HotCue { id: hotcueModel2; index: 1 } + HotCue { id: hotcueModel3; index: 2 } + HotCue { id: hotcueModel4; index: 3 } + HotCue { id: hotcueModel5; index: 4 } + HotCue { id: hotcueModel6; index: 5 } + HotCue { id: hotcueModel7; index: 6 } + HotCue { id: hotcueModel8; index: 7 } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/BrowserView.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/BrowserView.qml new file mode 100755 index 000000000000..6436f4153330 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/BrowserView.qml @@ -0,0 +1,322 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Browser' as BrowserView +import '../Widgets' as Widgets + +//---------------------------------------------------------------------------------------------------------------------- +// BROWSER VIEW +// +// The Browser View is connected to traktors QBrowser from which it receives its data model. The navigation through the +// data is done by calling funcrtions invoked from QBrowser. +//---------------------------------------------------------------------------------------------------------------------- + +Item { + id: qmlBrowser + required property var deckInfo + property string propertiesPath: "" + property bool isActive: false + property bool enterNode: false + property bool exitNode: false + property int increment: 0 + property color focusColor: colors.colorDeckBlueBright + property int speed: 150 + property real sortingKnobValue: 0 + property int pageSize: 10 + property int fastScrollCenter: 3 + property bool leftScreen: deckInfo.isLeftScreen(deckInfo.deckId) + + readonly property int maxItemsOnScreen: 8 + + // This is used by the footer to change/display the sorting! + property alias sortingId: browser.sorting + property alias sortingDirection: browser.sortingDirection + property alias isContentList: browser.isContentList + + anchors.fill: parent + + enum WidgetKind { + None, + Searchbar, + Sidebar, + LibraryView + } + + Mixxx.ControlProxy { + id: focusWidget + + group: "[Library]" + key: "focused_widget" + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectTrackKnob" + onValueChanged: (value) => { + console.log("SelectTrackKnob", value) + if (value != 0) { + focusWidget.value = BrowserView.WidgetKind.LibraryView; + moveSelectionVertical(value); + } + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectPrevTrack" + onValueChanged: (value) => { + console.log("SelectPrevTrack", value) + if (value != 0) { + focusWidget.value = BrowserView.WidgetKind.LibraryView; + moveSelectionVertical(-1); + } + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectNextTrack" + onValueChanged: (value) => { + console.log("SelectNextTrack", value) + if (value != 0) { + focusWidget.value = BrowserView.WidgetKind.LibraryView; + moveSelectionVertical(1); + } + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveVertical" + onValueChanged: (value) => { + console.log("MoveVertical", value, focusWidget.value == BrowserView.WidgetKind.LibraryView) + // if (value != 0 && focusWidget.value == BrowserView.WidgetKind.LibraryView) + moveSelectionVertical(value); + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveUp" + onValueChanged: (value) => { + console.log("MoveUp", value) + if (value != 0 && focusWidget.value == BrowserView.WidgetKind.LibraryView) + moveSelectionVertical(-1); + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveDown" + onValueChanged: (value) => { + console.log("MoveDown", value) + if (value != 0 && focusWidget.value == BrowserView.WidgetKind.LibraryView) + moveSelectionVertical(1); + } + } + + function moveSelectionVertical(value) { + if (value == 0) + return ; + + const rowCount = browser.dataSet.rowCount(); + if (rowCount == 0) + return ; + + browser.currentIndex = Mixxx.MathUtils.positiveModulo(browser.currentIndex + value, rowCount); + } + + //-------------------------------------------------------------------------------------------------------------------- + + onIncrementChanged: { + if (qmlBrowser.increment != 0) { + var newValue = clamp(browser.currentIndex + qmlBrowser.increment, 0, contentList.count - 1); + + // center selection if user is _fast scrolling_ but we're at the _beginning_ or _end_ of the list + if (qmlBrowser.increment >= pageSize) { + var centerTop = fastScrollCenter; + + if (browser.currentIndex < centerTop) { + newValue = centerTop; + } + } + if (qmlBrowser.increment <= (-pageSize)) { + var centerBottom = contentList.count - 1 - fastScrollCenter; + + if (browser.currentIndex > centerBottom) { + newValue = centerBottom; + } + } + + browser.changeCurrentIndex(newValue); + qmlBrowser.increment = 0; + } + } + + onExitNodeChanged: { + if (qmlBrowser.exitNode) { + browser.exitNode() + } + + qmlBrowser.exitNode = false; + } + + //-------------------------------------------------------------------------------------------------------------------- + + onEnterNodeChanged: { + if (qmlBrowser.enterNode) { + var movedDown = browser.enterNode(screen.focusDeckId, contentList.currentIndex); + if (movedDown) { + browser.relocateCurrentIndex() + } + } + + qmlBrowser.enterNode = false; + } + + function clamp(val, min, max) { + return Math.max(min, Math.min(val, max)); + } + + // Traktor.Browser + // { + // id: browser; + // isActive: qmlBrowser.isActive + // } + Item { + id: browser; + property bool changeCurrentIndex: false + property int currentIndex: 0 + property bool currentPath: false + property var dataSet: Mixxx.Library.model + property bool enterNode: false + property bool exitNode: false + property bool iconId: false + property bool isContentList: false + property bool relocateCurrentIndex: false + property bool sorting: false + property bool sortingDirection: false + } + + Rectangle { + id: background + anchors.fill: parent + color: "black" + } + + //-------------------------------------------------------------------------------------------------------------------- + // LIST VIEW -- NEEDS A MODEL CONTAINING THE LIST OF ITEMS TO SHOW AND A DELEGATE TO DEFINE HOW ONE ITEM LOOKS LIKE + //------------------------------------------------------------------------------------------------------------------- + + // zebra filling up the rest of the list if smaller than maxItemsOnScreen (= 8 entries) + Grid { + anchors.top: contentList.top + anchors.topMargin: contentList.topMargin + contentList.contentHeight + 1 // +1 = for spacing + anchors.right: parent.right + anchors.left: parent.left + anchors.leftMargin: 3 + columns: 1 + spacing: 1 + + Repeater { + model: (contentList.count < qmlBrowser.maxItemsOnScreen) ? (qmlBrowser.maxItemsOnScreen - contentList.count) : 0 + Rectangle { + color: ( (contentList.count + index)%2 == 0) ? colors.colorGrey32 : "Black" + width: qmlBrowser.width; + height: settings.browserFontSize*2 } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + ListView { + id: contentList + anchors.fill: parent + verticalLayoutDirection: ListView.TopToBottom + // the top/bottom margins are applied only at the beginning/end of the list in order to show half entries while scrolling + // and keep the list delegates in the same position always. + + // the commented out margins caused browser anchor problems leading to a disappearing browser! check later !? + anchors.topMargin: 17 // ( (contentList.count < qmlBrowser.maxItemsOnScreen ) || (currentIndex < 4 )) ? 17 : 0 + anchors.bottomMargin: 18 // ( (contentList.count >= qmlBrowser.maxItemsOnScreen) && (currentIndex >= contentList.count - 4)) ? 18 : 0 + clip: false + spacing: 1 + preferredHighlightBegin: 119 - 17 // -17 because of the reduced height due to the topMargin + preferredHighlightEnd: 152 - 17 // -17 because of the reduced height due to the topMargin + highlightRangeMode: ListView.ApplyRange + highlightMoveDuration: 0 + delegate: BrowserView.ListDelegate {id: listDelegate; masterBPM: deckInfo.masterBPM; masterKey: deckInfo.masterKey; keyIndex: deckInfo.keyIndex; isPlaying: deckInfo.isPlaying; adjacentKeys: settings.adjacentKeys;} + model: browser.dataSet + currentIndex: browser.currentIndex + focus: true + cacheBuffer: 10 + visible: settings.showBrowserOnFullScreen ? ((deckInfo.isInBrowserMode && leftScreen) || (deckInfo.viewButton && !deckInfo.isInBrowserMode) || deckInfo.favorites) : true + } + + ListView { + id: contentListRight + anchors.fill: parent + verticalLayoutDirection: ListView.TopToBottom + // the top/bottom margins are applied only at the beginning/end of the list in order to show half entries while scrolling + // and keep the list delegates in the same position always. + + // the commented out margins caused browser anchor problems leading to a disappearing browser! check later !? + anchors.topMargin: 0 // ( (contentList.count < qmlBrowser.maxItemsOnScreen ) || (currentIndex < 4 )) ? 17 : 0 + anchors.bottomMargin: 0 // ( (contentList.count >= qmlBrowser.maxItemsOnScreen) && (currentIndex >= contentList.count - 4)) ? 18 : 0 + clip: false + spacing: 0 + preferredHighlightBegin: 0 // -17 because of the reduced height due to the topMargin + preferredHighlightEnd: 240 // -17 because of the reduced height due to the topMargin + highlightRangeMode: ListView.ApplyRange + highlightMoveDuration: 0 + delegate: BrowserView.TrackView {id: trackView; masterBPM: deckInfo.masterBPM;} + model: browser.dataSet + currentIndex: browser.currentIndex + focus: true + cacheBuffer: 10 + visible: settings.showBrowserOnFullScreen ? (deckInfo.isInBrowserMode && !leftScreen) : false + } + + BrowserView.BrowserHeader { + id: browserHeader + nodeIconId: browser.iconId + currentDeck: deckInfo.deckId + state: "show" + pathStrings: browser.currentPath + + Behavior on height { NumberAnimation { duration: speed; } } + + visible: settings.showBrowserOnFullScreen ? !(deckInfo.isInBrowserMode && !leftScreen) : true + } + + //-------------------------------------------------------------------------------------------------------------------- + + BrowserView.BrowserFooter { + id: browserFooter + state: "show" + propertiesPath: qmlBrowser.propertiesPath + sortingKnobValue: qmlBrowser.sortingKnobValue + maxCount: contentList.count + count: browser.currentIndex + 1 + deckInfo: qmlBrowser.deckInfo + + Behavior on height { NumberAnimation { duration: speed; } } + + visible: settings.showBrowserOnFullScreen ? !(deckInfo.isInBrowserMode && !leftScreen) : true + } + + BrowserView.TrackFooter { + id: trackFooter + state: "show" + propertiesPath: qmlBrowser.propertiesPath + sortingKnobValue: qmlBrowser.sortingKnobValue + maxCount: contentList.count + count: browser.currentIndex + 1 + deckInfo: qmlBrowser.deckInfo + + Behavior on height { NumberAnimation { duration: speed; } } + + visible: settings.showBrowserOnFullScreen ? (deckInfo.isInBrowserMode && !leftScreen) : false + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/Dimensions.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/Dimensions.qml new file mode 100755 index 000000000000..bedc4e594519 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/Dimensions.qml @@ -0,0 +1,15 @@ +import QtQuick 2.15 + +QtObject { + + readonly property real infoBoxesWidth: 150 + readonly property real firstRowHeight: 33 + readonly property real secondRowHeight: 72 + readonly property real thirdRowHeight: 72 + readonly property real spacing: 6 + readonly property real largeBoxWidth: 2*infoBoxesWidth + spacing + readonly property real cornerRadius: 5 + readonly property real screenTopMargin: 3 // might need to be adapted based on the tolerances of hardware manufacturing + readonly property real screenLeftMargin: spacing // might need to be adapted based on the tolerances of hardware manufacturing + readonly property real titleTextMargin: spacing +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/EmptyDeck.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/EmptyDeck.qml new file mode 100755 index 000000000000..5ceca5ff3d5e --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/EmptyDeck.qml @@ -0,0 +1,47 @@ +import QtQuick 2.15 +import '../Overlays' as Overlays +import '../Defines' as Defines +import '../Widgets' as Widgets + +Item { + id: display + anchors.fill: parent + property color deckColor: "black" + property var deckInfo: ({}) + Dimensions {id: dimensions} + + property real infoBoxesWidth: dimensions.infoBoxesWidth + property real firstRowHeight: dimensions.firstRowHeight + + Rectangle { + id: background + color: colors.defaultBackground + anchors.fill: parent + } + + Image { + id: logoImage + anchors.fill: parent + + source: engine.getSetting("idleBackground") || "../../../../../images/templates/logo_mixxx.png" + fillMode: Image.PreserveAspectFit + } + + // DECK HEADER // + // Widgets.DeckHeader + // { + // id: deckHeader + + // title: deckInfo.headerEnabled ? deckInfo.headerText : "Live Input" + // artist: deckInfo.headerEnabled ? deckInfo.headerTextLong : "Live Input" + + // height: display.firstRowHeight-6 + // width: 4*(display.infoBoxesWidth/2+1)+10 + + // anchors.left: parent.left + // anchors.top: parent.top + // anchors.topMargin: 3 + // anchors.leftMargin: 4 + + // } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/StemDeck.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/StemDeck.qml new file mode 100755 index 000000000000..e0a260721422 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/StemDeck.qml @@ -0,0 +1,28 @@ +import QtQuick 2.5 +import '../Widgets' as Widgets +import '../Overlays' as Overlays + +//---------------------------------------------------------------------------------------------------------------------- +// Stem Screen View - UI of the screen for stems +//---------------------------------------------------------------------------------------------------------------------- + +Item { + id: display + + // MODEL PROPERTIES // + required property var deckInfo + + width: 320 + height: 240 + + TrackDeck { + id: trackScreen + deckInfo: display.deckInfo + anchors.fill: parent + } + + // STEM OVERLAY // + Widgets.StemOverlay { + deckInfo: display.deckInfo + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/TrackDeck.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/TrackDeck.qml new file mode 100755 index 000000000000..2fc98a5117a9 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Views/TrackDeck.qml @@ -0,0 +1,222 @@ +import QtQuick 2.5 +import QtQuick.Layouts 1.1 +import '../Waveform' as WF +import '../Overlays' as Overlays + +import '../Widgets' as Widgets + +//---------------------------------------------------------------------------------------------------------------------- +// Track Screen View - UI of the screen for track +//---------------------------------------------------------------------------------------------------------------------- + +Item { + id: display + Dimensions {id: dimensions} + + // MODEL PROPERTIES // + required property var deckInfo + property int deckId: 1 + property real boxesRadius: dimensions.cornerRadius + property real infoBoxesWidth: dimensions.infoBoxesWidth +4 + property real firstRowHeight: dimensions.firstRowHeight + property real secondRowHeight: dimensions.secondRowHeight + property real spacing: dimensions.spacing-3 + property real screenTopMargin: dimensions.screenTopMargin + property real screenLeftMargin: dimensions.screenLeftMargin-2 + + width: 320 + height: 240 + + Rectangle { + id: displayBackground + anchors.fill: parent + color: colors.defaultBackground + } + + Image { + id: emptyTrackDeckImage + anchors.fill: parent + visible: deckInfo.showLogo + + source: engine.getSetting("idleBackground") || "../../../../../images/templates/logo_mixxx.png" + fillMode: Image.PreserveAspectFit + } + + ColumnLayout { + id: content + spacing: display.spacing + + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: display.screenTopMargin + anchors.leftMargin: display.screenLeftMargin + + // FIRST ROW // + RowLayout { + id: firstRow + + spacing: 1 + + // DECK HEADER // + Widgets.DeckHeader { + id: deckHeader + + deckInfo: display.deckInfo + + title: deckInfo.headerEnabled ? deckInfo.headerTextShort : deckInfo.titleString + artist: deckInfo.headerEnabled ? deckInfo.headerTextLong : deckInfo.artistString + + height: display.firstRowHeight-6 + width: deckInfo.headerEnabled ? 4*(display.infoBoxesWidth/2+1)+1 : 3*(display.infoBoxesWidth/2+1)+3 + } + + // TIME DISPLAY // + Item { + id: timeBox2 + width: (display.infoBoxesWidth/2+1) + height: display.firstRowHeight-6 + + Rectangle { + anchors.fill: parent + color: trackEndBlinkTimer2.blink ? colors.colorRed : colors.colorDeckGrey + radius: display.boxesRadius + visible: !deckInfo.headerEnabled + } + + Text { + text: settings.timeBox == 0 ? deckInfo.remainingTimeString : settings.timeBox == 1 ? deckInfo.elapsedTimeString : settings.timeBox == 2 ? deckInfo.timeToCue : settings.timeBox == 3 ? deckInfo.beats : settings.timeBox == 4 ? deckInfo.beatsAlt : settings.timeBox == 5 ? deckInfo.beatsToCue : settings.timeBox == 6 ? deckInfo.beatsToCueAlt : deckInfo.remainingTimeString + font.pixelSize: 22 + font.family: "Roboto" + font.weight: Font.Medium + color: settings.timeTextColorChange && trackEndBlinkTimer2.blink ? "black" : "white" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: !deckInfo.shift && !deckInfo.headerEnabled + } + + Text { + text: settings.timeBoxShift == 0 ? deckInfo.remainingTimeString : settings.timeBoxShift == 1 ? deckInfo.elapsedTimeString : settings.timeBoxShift == 2 ? deckInfo.timeToCue : settings.timeBoxShift == 3 ? deckInfo.beats : settings.timeBoxShift == 4 ? deckInfo.beatsAlt : settings.timeBoxShift == 5 ? deckInfo.beatsToCue : settings.timeBoxShift == 6 ? deckInfo.beatsToCueAlt : deckInfo.remainingTimeString + font.pixelSize: 22 + font.family: "Roboto" + font.weight: Font.Medium + color: settings.timeTextColorChange && trackEndBlinkTimer2.blink ? "black" : "white" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: deckInfo.shift && !deckInfo.headerEnabled + } + + Timer { + id: trackEndBlinkTimer2 + property bool blink: false + + interval: 500 + repeat: true + running: deckInfo.trackEndWarning + + onTriggered: { + blink = !blink; + } + + onRunningChanged: { + blink = running; + } + } + } + } + + // PHASE METER // + Widgets.PhaseMeter { + id: phase + height: settings.hidePhase ? 0 : 16 + width: 317 + visible: deckInfo.isLoaded + + phase: deckInfo.phase + } + + //WAVEFORM + + property string deckSizeState: "large" + readonly property int waveformHeight: 129 + property bool isInEditMode: false + property bool showLoopSize: true + property string propertiesPath: "" + + WF.WaveformContainer { + id: waveformContainer + + deckInfo: display.deckInfo + + deckId: deckInfo.deckId + deckSizeState: content.deckSizeState + propertiesPath: content.propertiesPath + + // anchors.left: parent.left + width: 316 + // anchors.top: phase.bottom + showLoopSize: content.showLoopSize + isInEditMode: content.isInEditMode + + // the height of the waveform is defined as the remaining space of deckHeight - stripe.height - spacerWaveStripe.height + height: (settings.alwaysShowTempoInfo || deckInfo.adjustEnabled ? (settings.hideWaveformOverview ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38) : (!deckInfo.showBPMInfo ? (settings.hideWaveformOverview ? content.waveformHeight + display.secondRowHeight-13 : content.waveformHeight) : (settings.hideWaveformOverview ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38))) + (settings.hidePhase && settings.hidePhrase ? 16 : 0) + (!settings.hidePhase && !settings.hidePhrase ? -16 : 0) + visible: deckInfo.isLoaded && !settings.hideWaveforms + + Behavior on height { PropertyAnimation { duration: 90} } + } + } + + WF.WaveformOverview { + height: settings.hideWaveformOverview ? 0 : settings.hideWaveforms ? 150 : display.secondRowHeight-13 + width: 314 + anchors.left: parent.left + anchors.leftMargin: 6 + anchors.top: display.top + anchors.topMargin: settings.hideWaveforms ? 90 : 178 + } + + Overlays.TopControls { + id: fx1 + fxUnit: 0 + showHideState: (deckInfo.showFx1 && settings.fxOverlays && !settings.hideEffectsOverlay1) || (deckInfo.padsModeFx1 && (settings.fx1unit == 1)) || (deckInfo.padsModeFx2 && (settings.fx2unit == 1)) ? "show" : "hide" + } + + Overlays.TopControls { + id: fx2 + fxUnit: 1 + showHideState: deckInfo.showFx2 && settings.fxOverlays && !settings.hideEffectsOverlay1 || (deckInfo.padsModeFx1 && (settings.fx1unit == 2)) || (deckInfo.padsModeFx2 && (settings.fx2unit == 2)) ? "show" : "hide" + } + + Overlays.TopControls { + id: fx3 + fxUnit: 2 + showHideState: deckInfo.showFx3 && settings.fxOverlays && !settings.hideEffectsOverlay2 || (deckInfo.padsModeFx1 && (settings.fx1unit == 3)) || (deckInfo.padsModeFx2 && (settings.fx2unit == 3)) ? "show" : "hide" + } + + Overlays.QuickFXSelector { + deckInfo: display.deckInfo + } + + Overlays.TopControls { + id: fx4 + fxUnit: 3 + showHideState: (deckInfo.showFx4 && settings.fxOverlays && !settings.hideEffectsOverlay2 || (deckInfo.padsModeFx1 && (!settings.fx1unit == 4)) ||(deckInfo.padsModeFx2 && (settings.fx2unit == 4))) ? "show" : "hide" + } + + Widgets.TempoAdjust { + id: tempoInfo + deckId: deckInfo.deckId + height: 38 + y: settings.hideWaveformOverview ? 197 : 140 + visible: (deckInfo.isLoaded ? (settings.alwaysShowTempoInfo || deckInfo.adjustEnabled ? true : deckInfo.showBPMInfo) : false) && !settings.hideWaveforms + } + + Widgets.TempoAdjust { + id: tempoInfo2 + deckId: deckInfo.deckId + height: 38 + y: 50 + visible: settings.hideWaveforms + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/StemColorIndicators.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/StemColorIndicators.qml new file mode 100755 index 000000000000..2da71cca02d2 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/StemColorIndicators.qml @@ -0,0 +1,82 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Defines' as Defines +import '../Defines' as Defines +import '../ViewModels' as ViewModels + +Item { + id: view + + property int deckId: 1 + + Defines.Colors {id: colors} + Defines.Settings {id: settings} + + required property var deckInfo + + readonly property int stemCount: deckInfo.stemCount + readonly property var stemColors: ["green", "blue", "red", settings.accentColor] + + property var indicatorHeight: [31 , 31 , 31 , 31] + + //-------------------------------------------------------------------------------------------------------------------- + // There is one pixel space between the color-indicator-rectangles. In this space, you can see the beatgrid/cuePoints, + // which is not what we want. Therefore I added this Rectalgles in the same color as the background. This rectangles hide + // the beatgrid/cuePoints. + Rectangle { x: 0; y: 0; width: 5; height: view.height; color: colors.colorBlack75 } + Rectangle { x: view.width - width; y: 0; width: 5; height: view.height; color: colors.colorBlack75 } + + //-------------------------------------------------------------------------------------------------------------------- + + readonly property var deckPlayer: Mixxx.PlayerManager.getPlayer(`[Channel${deckId}]`) + readonly property var currentPlayer: deckPlayer.currentPlayer + + function indicatorY(index) { + var y = 0; + for (var i=0; i 1 ? 2 : 1) + // width: view.width + // height: 31 + // clip: true + + // deckId: view.deckId + // streamId: index + 1 + // sampleWidth: view.sampleWidth + // waveformPosition: view.waveformPosition + // waveformColors: colors.getWaveformColors(colorIds[index]) + // } + // } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformContainer.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformContainer.qml new file mode 100755 index 000000000000..a08c7c66b81d --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformContainer.qml @@ -0,0 +1,241 @@ +import QtQuick 2.15 + +import '../Defines' +import '../Widgets' as Widgets +import '../Overlays' as Overlays +import '../ViewModels' as ViewModels + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Item { + id: view + property int deckId: deckInfo.deckId + property string deckSizeState: "large" + property bool showLoopSize: false + property bool isInEditMode: false + property string propertiesPath: "" + property int zoomLevel: deckInfo.zoomLevel + readonly property int minSampleWidth: 2048 + property int sampleWidth: minSampleWidth << zoomLevel + property bool hideLoop: false + property bool hideBPM: false + property bool hideKey: false + + readonly property bool trackIsLoaded: deckInfo.isLoaded + + //-------------------------------------------------------------------------------------------------------------------- + + required property var deckInfo + + //-------------------------------------------------------------------------------------------------------------------- + // WAVEFORM Position + //------------------------------------------------------------------------------------------------------------------ + + 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" + } + + MixxxControls.WaveformDisplay { + id: singleWaveform + group: `[Channel${view.deckId}]` + x: 0 + width: 316 + height: (settings.alwaysShowTempoInfo || deckInfo.adjustEnabled ? (settings.hideWaveformOverview ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38) : (!deckInfo.showBPMInfo ? (settings.hideWaveformOverview ? content.waveformHeight + display.secondRowHeight-13 : content.waveformHeight) : (settings.hideWaveformOverview ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38))) + (settings.hidePhase && settings.hidePhrase ? 16 : 0) + (!settings.hidePhase && !settings.hidePhrase ? -16 : 0) + + Behavior on height { PropertyAnimation { duration: 90} } + anchors.fill: parent + 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.5 + gainLow: 1.0 + gainMid: 1.0 + gainHigh: 1.0 + } + + Mixxx.WaveformRendererStem { + gainAll: 1.5 + } + + Mixxx.WaveformRendererBeat { + color: settings.hideBeatgrid ? 'transparent' : Qt.rgba(0.81, 0.81, 0.81, settings.beatgridVisibility) + } + + Mixxx.WaveformRendererMark { + playMarkerColor: 'cyan' + playMarkerBackground: 'transparent' + defaultMark: Mixxx.WaveformMark { + align: "bottom|right" + color: "#FF0000" + textColor: "#FFFFFF" + text: " %1 " + } + + untilMark.showTime: settings.showTimeToCue + untilMark.showBeats: settings.showBeatToCue + untilMark.align: settings.distanceToCueAlignment == "bottom" ? Qt.AlignBottom : settings.distanceToCueAlignment == "top" ? Qt.AlignTop : Qt.AlignCenter + untilMark.textSize: settings.distanceToCueFontSize + + Mixxx.WaveformMark { + control: "cue_point" + text: 'C' + align: 'top|right' + color: 'red' + 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" + 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" + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // Stem Color Indicators (Rectangles) + //-------------------------------------------------------------------------------------------------------------------- + + StemColorIndicators { + id: stemColorIndicators + deckId: view.deckId + deckInfo: view.deckInfo + anchors.fill: singleWaveform + anchors.rightMargin: 309 + visible: deckInfoModel.isStemDeck + indicatorHeight: !settings.hidePhase && !settings.hidePhrase ? (deckInfo.showBPMInfo ? [19 , 19 , 19 , 20] : [27 , 27 , 27 , 27]) : (deckInfo.showBPMInfo ? [23 , 23 , 23 , 23] : [31 , 31 , 31 , 31]) + } + + Widgets.LoopSize { + id: loopSize + anchors.topMargin: 1 + anchors.fill: parent + visible: (deckInfo.showLoopInfo || deckInfo.loopActive || settings.alwaysShowLoopSize) && !hideLoop + } + + Widgets.KeyDisplay { + id: keyDisplay + anchors.topMargin: 1 + anchors.fill: parent + visible: !hideKey + } + + Widgets.BpmDisplay { + id: bpmDisplay + anchors.bottomMargin: 1 + anchors.top: singleWaveform.bottom + anchors.fill: parent + visible: !hideBPM && (!deckInfo.showBPMInfo && !settings.alwaysShowTempoInfo && !deckInfo.adjustEnabled) || settings.hideWaveforms + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformOverview.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformOverview.qml new file mode 100644 index 000000000000..7f48abeafb98 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformOverview.qml @@ -0,0 +1,228 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +import "." as Skin +import Mixxx 1.0 as Mixxx + +Item { + id: waveform + + property int deckId: deckInfo.deckId + + readonly property string group: `[Channel${deckId}]` + + layer.enabled: true + + Item { + id: progression + + property real windowWidth: Window.width + Mixxx.ControlProxy { + id: propPosition + group: waveform.group + key: "playposition" + } + + Mixxx.ControlProxy { + id: propVisible + group: waveform.group + key: "track_loaded" + } + + width: propPosition.value * (320 - 12) + visible: propVisible.value + + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + + clip: true + + Rectangle { + anchors.fill: parent + anchors.leftMargin: -border.width + anchors.topMargin: -border.width + anchors.bottomMargin: -border.width + border.width: 2 + border.color:"black" + color: Qt.rgba(0.39, 0.80, 0.96, 0.3) + } + } + + Mixxx.WaveformOverview { + readonly property var player: Mixxx.PlayerManager.getPlayer(waveform.group) + id: waveformOverview + anchors.fill: parent + anchors.topMargin: 6 + + track: player.currentTrack + } + + Mixxx.ControlProxy { + id: samplesControl + + group: waveform.group + key: "track_samples" + } + + // // Hotcue + // Repeater { + // model: 16 + + // S4MK3.HotcuePoint { + // required property int index + + // Mixxx.ControlProxy { + // id: samplesControl + + // group: waveform.group + // key: "track_samples" + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // Mixxx.ControlProxy { + // id: hotcueEnabled + // group: waveform.group + // key: `hotcue_${index + 1}_status` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // Mixxx.ControlProxy { + // id: hotcuePosition + // group: waveform.group + // key: `hotcue_${index + 1}_position` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // Mixxx.ControlProxy { + // id: hotcueColor + // group: waveform.group + // key: `hotcue_${number}_color` + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // anchors.top: parent.top + // // anchors.left: parent.left + // anchors.bottom: parent.bottom + // visible: hotcueEnabled.value + + // number: this.index + 1 + // type: S4MK3.HotcuePoint.Type.OneShot + // position: hotcuePosition.value / samplesControl.value + // color: `#${(hotcueColor.value >> 16).toString(16).padStart(2, '0')}${((hotcueColor.value >> 8) & 255).toString(16).padStart(2, '0')}${(hotcueColor.value & 255).toString(16).padStart(2, '0')}` + // } + // } + + // // Intro + // S4MK3.HotcuePoint { + + // Mixxx.ControlProxy { + // id: introStartEnabled + // group: waveform.group + // key: `intro_start_enabled` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // Mixxx.ControlProxy { + // id: introStartPosition + // group: waveform.group + // key: `intro_start_position` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // anchors.top: parent.top + // anchors.bottom: parent.bottom + // visible: introStartEnabled.value + + // type: S4MK3.HotcuePoint.Type.IntroIn + // position: introStartPosition.value / samplesControl.value + // } + + // // Extro + // S4MK3.HotcuePoint { + + // Mixxx.ControlProxy { + // id: introEndEnabled + // group: waveform.group + // key: `intro_end_enabled` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // Mixxx.ControlProxy { + // id: introEndPosition + // group: waveform.group + // key: `intro_end_position` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // 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: waveform.group + // key: `loop_start_position` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // anchors.top: parent.top + // anchors.bottom: parent.bottom + // visible: loopStartPosition.value > 0 + + // type: S4MK3.HotcuePoint.Type.LoopIn + // position: loopStartPosition.value / samplesControl.value + // } + + // // Loop out + // S4MK3.HotcuePoint { + // Mixxx.ControlProxy { + // id: loopEndPosition + // group: waveform.group + // key: `loop_end_position` + + // onValueChanged: (value) => { + // redraw(waveform) + // } + // } + + // anchors.top: parent.top + // anchors.bottom: parent.bottom + // visible: loopEndPosition.value > 0 + + // type: S4MK3.HotcuePoint.Type.LoopOut + // position: loopEndPosition.value / samplesControl.value + // } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/BpmDisplay.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/BpmDisplay.qml new file mode 100755 index 000000000000..8ab0a41e58bb --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/BpmDisplay.qml @@ -0,0 +1,27 @@ +import QtQuick 2.15 + +Item { + anchors.fill: parent + property int deckId: 0 + + Rectangle { + id: bpmBackground + width: 60 + height: 20 + color: colors.grayBackground + anchors.right: parent.right + anchors.bottom: parent.bottom + } + + Text { + text: deckInfo.bpmString + color: "white" + font.pixelSize: 17 + font.family: "Pragmatica" + anchors.fill: bpmBackground + anchors.rightMargin: 2 + anchors.topMargin: 1 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/DeckHeader.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/DeckHeader.qml new file mode 100755 index 000000000000..34c61544f924 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/DeckHeader.qml @@ -0,0 +1,90 @@ +import QtQuick 2.5 +import '../Defines' as Defines +import '../Defines' as Defines + +//here we assume that `colors` and `dimensions` already exists in the object hierarchy +Item { + id: widget + + property string title: '' + property string artist: '' + property color backgroundColor: colors.defaultBackground + height: dimensions.firstRowHeight + property int radius: dimensions.cornerRadius + + Defines.Settings {id: settings} + Defines.Colors {id: colors} + + required property var deckInfo + + property int deckA: deckInfo.deckAColor + property int deckB: deckInfo.deckBColor + property int deckC: deckInfo.deckCColor + property int deckD: deckInfo.deckDColor + + function colorForDeck(deckId,deckA,deckB,deckC,deckD) { + switch (deckId) { + case 1: return colorForDeckSingle(deckA); + case 2: return colorForDeckSingle(deckB); + case 3: return colorForDeckSingle(deckC); + default: return colorForDeckSingle(deckD); + } + } + + function colorForDeckSingle(deck) { + switch (deck) { + case 0: return colors.red; + case 1: return colors.darkOrange; + case 2: return colors.lightOrange; + case 3: return colors.warmYellow; + case 4: return colors.yellow; + case 5: return colors.lime; + case 6: return colors.green; + case 7: return colors.mint; + case 8: return colors.cyan; + case 9: return colors.turquoise; + case 10: return colors.blue; + case 11: return colors.plum; + case 12: return colors.violet; + case 13: return colors.purple; + case 14: return colors.magenta; + case 15: return colors.fuchsia; + default: return colors.white; + } + } + + Rectangle { + id: headerBg + color: colorForDeck(deckInfo.deckId,deckA,deckB,deckC,deckD) + anchors.fill: parent + radius: widget.radius + + Text { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 2 + anchors.topMargin: 2 + font.family: "Roboto" + font.weight: Font.Normal + font.pixelSize: 20 + color: "black" + text: widget.title + elide: Text.ElideRight + visible: deckInfo.shift ? false : true + } + + Text { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 2 + anchors.topMargin: 2 + font.family: "Roboto" + font.weight: Font.Normal + font.pixelSize: 20 + color: "black" + text: widget.artist + elide: Text.ElideRight + visible: deckInfo.shift ? true : false + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/KeyDisplay.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/KeyDisplay.qml new file mode 100755 index 000000000000..fd3f3ac53fd0 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/KeyDisplay.qml @@ -0,0 +1,46 @@ +import '../Defines' as Defines + +import QtQuick 2.15 + +Item { + anchors.fill: parent + + Defines.Colors { + id: colors + } + + property int deckId: 0 + + Rectangle { + id: keyBackground + width: 60 + height: 20 + color: deckInfo.isKeyLockOn ? colors.musicalKeyColors[deckInfo.keyIndex] : colors.musicalKeyColorsDark[deckInfo.keyIndex] + anchors.right: parent.right + anchors.top: parent.top + Rectangle { + id: keyBorder + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: keyBackground.width -2 + height: keyBackground.height -2 + color: "transparent" + border.color: colors.defaultBackground + border.width: 2 + } + } + + Text { + text: deckInfo.hasKey && (deckInfo.keyAdjustString != "-0") && (deckInfo.keyAdjustString != "+0") ? (settings.camelotKey ? utils.camelotConvert(deckInfo.keyString) : deckInfo.keyString) + deckInfo.keyAdjustString + : deckInfo.hasKey ? (settings.camelotKey ? utils.camelotConvert(deckInfo.keyString) : deckInfo.keyString) + : "No key" + color: deckInfo.isKeyLockOn ? "black" : "white" + font.pixelSize: 15 + font.family: "Pragmatica" + anchors.fill: keyBackground + anchors.rightMargin: 2 + anchors.topMargin: 1 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/LoopSize.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/LoopSize.qml new file mode 100755 index 000000000000..454fc3d3c5d8 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/LoopSize.qml @@ -0,0 +1,87 @@ +import QtQuick 2.15 + +import Mixxx 1.0 as Mixxx + +import '../Defines' as Defines + +Item { + anchors.fill: parent + + Defines.Colors {id: colors} + Defines.Durations { id: durations } + + Mixxx.ControlProxy { + group: `[Channel${parent.deckId}]` + key: "beatloop_size" + id: loopSize + property string description: "Description" + } + + property int deckId: 0 + + property color loopActiveColor: colors.cueColors[settings.cueLoopColor] + property color loopDimmedColor: colors.cueColorsDark[settings.cueLoopColor] + + Rectangle { + id: loopSizeBackground + width: 40 + height: width + radius: width * 0.5 + opacity: loopActiveBlinkTimer.blink ? 0.25 : 1 + color: deckInfo.loopActive ? (loopActiveBlinkTimer.blink ? loopActiveColor : (settings.loopActiveRedFlash ? colors.colorRed : loopDimmedColor)) + : deckInfo.loopActive ? (deckInfo.shift ? loopDimmedColor : loopActiveColor) + : deckInfo.shift ? colors.colorDeckDarkGrey : colors.colorDeckGrey + Behavior on opacity { NumberAnimation { duration: durations.mainTransitionSpeed; easing.type: Easing.Linear} } + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + Rectangle { + id: loopLengthBorder + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: loopSizeBackground.width -2 + height: width + radius: width * 0.5 + color: "transparent" + border.color: loopActiveColor + border.width: 2 + } + } + + Text { + text: loopSize.value < 1/8 ? `/${1 / loopSize.value}` : loopSize.value < 1 ? `1/${1 / loopSize.value}` : `${loopSize.value}` + color: deckInfo.loopActive ? "black" : ( deckInfo.shift ? colors.colorDeckGrey : colors.defaultTextColor ) + font.pixelSize: fonts.extraLargeValueFontSize + font.family: "Pragmatica" + anchors.fill: loopSizeBackground + anchors.rightMargin: 2 + anchors.topMargin: 1 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + onTextChanged: { + if (loopSize.value < 1) { + font.pixelSize = 18 + } else if ( loopSize.value > 8 ) { + font.pixelSize = 24 + } else { + font.pixelSize = 25 + } + } + } + + Timer { + id: loopActiveBlinkTimer + property bool blink: false + + interval: 333 + repeat: true + running: deckInfo.loopActive + + onTriggered: { + blink = !blink; + } + + onRunningChanged: { + blink = running; + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/PhaseMeter.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/PhaseMeter.qml new file mode 100755 index 000000000000..236d691fdab0 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/PhaseMeter.qml @@ -0,0 +1,94 @@ +import QtQuick 2.5 +import '../Defines' as Defines + +Item { + id: widget + + height: 16 + + function colorForPhase(phase) { + switch (phase) { + case 0: return colors.red; + case 1: return colors.darkOrange; + case 2: return colors.lightOrange; + case 3: return colors.phaseColor; + case 4: return colors.yellow; + case 5: return colors.lime; + case 6: return colors.green; + case 7: return colors.mint; + case 8: return colors.cyan; + case 9: return colors.turquoise; + case 10: return colors.blue; + case 11: return colors.plum; + case 12: return colors.violet; + case 13: return colors.purple; + case 14: return colors.magenta; + case 15: return colors.fuchsia; + case 16: return colors.colorWhite; + } + return colors.lightOrange; + } + + property real phase: 0.0 + + Defines.Settings {id: settings} + property int phaseAColor: settings.phaseAColor + property int phaseBColor: settings.phaseBColor + property int phaseCColor: settings.phaseCColor + property int phaseDColor: settings.phaseDColor + property int deckId: deckInfo.deckId + + property color phaseColor: colorForPhase(deckId == 1 ? phaseAColor : deckId == 2 ? phaseBColor : deckId == 3 ? phaseCColor : phaseDColor) + property color phaseHeadColor: "#FCB262" + property color separatorColor: "#88ffffff" + property color backgroundColor: colors.grayBackground + property real phasePosition: parent.width * (0.5 + widget.phase) + property real phaseBarWidth: parent.width * Math.abs(widget.phase) + + // Background + Rectangle { + anchors.fill: parent + color: widget.backgroundColor + } + + // Phase Bar + Rectangle { + color: widget.phaseColor + height: parent.height + width: phaseBarWidth + x: widget.phase < 0 ? widget.phasePosition : (parent.width/2) + } + + // Phase Head + Rectangle { + color: widget.phaseHeadColor + height: parent.height + width: 1 + x: widget.phase < 0 ? widget.phasePosition : (widget.phasePosition - width) + visible: Math.round(phaseBarWidth) !== 0 // hide phase head when phase is 0 + } + + // Separator at 0.25 + Rectangle { + color: widget.separatorColor + height: parent.height + width: 1 + x: parent.width * 0.25 - 1 + } + + // center Separator + Rectangle { + color: widget.separatorColor + height: parent.height + width: 1 + x: parent.width * 0.50 - 1 + } + + // Separator at 0.75 + Rectangle { + color: widget.separatorColor + height: parent.height + width: 1 + x: parent.width * 0.75 - 1 + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/ProgressBar.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/ProgressBar.qml new file mode 100755 index 000000000000..6c8c25dcd4cd --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/ProgressBar.qml @@ -0,0 +1,60 @@ +import QtQuick 2.15 + +import '../Defines' as Defines + +Item { + + id: progressBarContainer + + Defines.Colors { id: colors} + Defines.Settings { id: settings} + + property color progressBarColorIndicatorLevel: settings.accentColor // set from outside + property real value: 0.0 + property bool drawAsEnabled: true + + property alias progressBarWidth: progressBar.width + property alias progressBarHeight: progressBarContainer.height + property alias progressBarBackgroundColor: progressBar.color // set from outside + + onValueChanged: { + var val = Math.max( Math.min(value, 1.0), 0.0) + valueIndicator.width = val * (progressBar.width - 3) + } + + height: 6 + width: 80 + + // Progress Background + Rectangle { + id: progressBar + + anchors.left: parent.left + anchors.top: parent.top + height: parent.height + width: 102 // default value - set from outside + + color: colors.colorWhite09 // set in BottomInfoDetails + + // Progress Level + Rectangle { + id: valueIndicator + width: 0 // set in parent + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + color: progressBarContainer.progressBarColorIndicatorLevel + visible: drawAsEnabled ? true : false + } + // Progress Indicator Thumb + Rectangle { + id: indicatorThumb + color: colors.colorWhite + width: 2 + height: parent.height + anchors.verticalCenter: parent.verticalCenter + anchors.left: valueIndicator.right + visible: drawAsEnabled ? true : false + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/Slider.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/Slider.qml new file mode 100755 index 000000000000..23206eeb43cd --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/Slider.qml @@ -0,0 +1,111 @@ +import QtQuick 2.5 + +Item { + id: item + property color backgroundColor: "grey" + property color sliderColor: "red" + property color cursorColor: "white" + property color centerColor: "black" + + property real min: 0 + property real max: 1 + property real value: 0.5 + property real radius: 0 + property real cursorWidth: 5 + property bool centered: false + + Item { + id: toBeMasked_noCenter + anchors.fill: parent + + property real cursorPosition: (parent.width - item.cursorWidth) * ( item.value / (item.max-item.min) ) + + //background + Rectangle { + anchors.fill: parent + color: item.backgroundColor + } + + //colored part of the slider + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + width: toBeMasked_noCenter.cursorPosition + height: parent.height + + color: item.sliderColor + } + + //cursor + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: toBeMasked_noCenter.cursorPosition + width: item.cursorWidth + height: parent.height + color: item.cursorColor + } + + visible: false + } + + Item { + id: toBeMasked_centered + anchors.fill: parent + property real x0: (parent.width - item.cursorWidth)/2 + property real cursorPosition_left: Math.min( toBeMasked_noCenter.cursorPosition, x0) + property real cursorPosition_right: Math.max( toBeMasked_noCenter.cursorPosition, x0) + + //cursor background + Rectangle { + id: background + anchors.fill: parent + color: item.backgroundColor + } + + //filled slider + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: toBeMasked_centered.cursorPosition_left + width: toBeMasked_centered.cursorPosition_right - toBeMasked_centered.cursorPosition_left + height: parent.height + color: item.sliderColor + } + + //center + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: toBeMasked_centered.x0 + width: item.cursorWidth + height: parent.height + color: item.centerColor + } + + //cursor + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: toBeMasked_noCenter.cursorPosition + width: item.cursorWidth + height: parent.height + color: item.cursorColor + } + + visible: false + } + + Rectangle { + id: mask_noCenter + anchors.fill: parent + radius: item.radius + visible: false + } + + // OpacityMask { + // anchors.fill: parent + // maskSource: mask_noCenter + // source: item.centered ? toBeMasked_centered : toBeMasked_noCenter + // } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/StateBar.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/StateBar.qml new file mode 100755 index 000000000000..226c7cf1036f --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/StateBar.qml @@ -0,0 +1,34 @@ +import QtQuick 2.15 + +import '../Defines' as Defines + +// StateBar fits 'state count' elements into a bar of a given width and spacing. Take care that 'width > stateCount*spacing' +Item { + id: stateBarContainer + + property int spacing: 2 // default value. set from outside + property int stateCount: 5 // default value. set from outside + property int currentState: 2 // default value. set from outside + property color barColor: colors.colorIndicatorLevelOrange // default value. set from outside + property color barBgColor: colors.colorGrey24 // default value. set from outside + + property alias stateBarHeight: stateBarContainer.height + readonly property real stateBarWidth: width/stateCount - spacing + + Defines.Colors { id: colors} + + Row { + id: boxRow + anchors.fill: parent + anchors.leftMargin: 0.5*stateBarContainer.spacing + spacing: stateBarContainer.spacing + Repeater { + model: stateCount + Rectangle { + width: stateBarWidth + height: stateBarHeight + color: (index == currentState) ? barColor : barBgColor + } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/StemOverlay.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/StemOverlay.qml new file mode 100755 index 000000000000..37c8497e06e1 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/StemOverlay.qml @@ -0,0 +1,257 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.1 +import '../Views' + +import '../Defines' as Defines + +//---------------------------------------------------------------------------------------------------------------------- +// Remix Deck Overlay - for sample volume and filter value editing +//---------------------------------------------------------------------------------------------------------------------- + +Item { + id: display + + required property var deckInfo + + Dimensions {id: dimensions} + Defines.Colors { id: colors } + Defines.Durations { id: durations } + Defines.Settings {id: settings} + + // MODEL PROPERTIES // + property string showHideState: "hide" + property int bottomMargin: 0 + property int yPositionWhenHidden: 240 + property int yPositionWhenShown: (195 - bottomMargin) + + readonly property string name: display.deckInfo.stemSelectedName + + state: display.deckInfo.stemSelected ? "show" : "hide" + height: 40 + anchors.left: parent.left + anchors.right: parent.right + + // dark grey background + Rectangle { + id: bottomInfoDetailsPanelDarkBg + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: display.height + color: colors.colorFxHeaderBg + // light grey background + Rectangle { + id:bottomInfoDetailsPanelLightBg + anchors { + top: parent.top + left: parent.left + } + height: display.height + width: 105 + color: colors.colorFxHeaderLightBg + } + } + +// // dividers + Rectangle { + id: fxInfoDivider0 + width:1; + height:63; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 105 + } + + Rectangle { + id: fxInfoDivider2 + width:1; + color: colors.colorDivider + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 195 + height: display.height + } + + // Info Details + Rectangle { + id: bottomInfoDetailsPanel + + height: parent.height + clip: true + width: parent.width + color: "transparent" + + anchors.left: parent.left + anchors.leftMargin: 1 + + Row { + Item { + id: stemInfoDetailsPanel + + height: display.height + width: 110 + + // name + Text { + id: stemInfoName + font.capitalization: Font.AllUppercase + text: "NAME" + color: settings.accentColor + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + + // value + Text { + id: nameValue + font.capitalization: Font.AllUppercase + text: name + color: display.deckInfo.stemSelectedMidColor + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + anchors.left: parent.left + anchors.right: parent.right + font.pixelSize: fonts.scale(18) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + } + + Item { + id: volumeInfoDetailsPanel + + height: display.height + width: 85 + + // volume + Text { + id: volumeInfoName + font.capitalization: Font.AllUppercase + text: "VOLUME" + color: !display.deckInfo.stemSelectedMuted ? settings.accentColor : "grey" + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + + // value + ProgressBar { + id: volume + progressBarHeight: 9 + progressBarWidth: 76 + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.bottomMargin: 3 + + anchors.leftMargin: 5 + anchors.rightMargin: 20 + + value: display.deckInfo.stemSelectedVolume + + drawAsEnabled: true + progressBarColorIndicatorLevel: display.deckInfo.stemSelectedMuted ? "grey" : settings.accentColor + progressBarBackgroundColor: "black" + } + } + + Item { + id: fxInfoDetailsPanel + + height: display.height + width: 125 + + // fx name + Text { + id: fxInfoSampleName + + font.capitalization: Font.AllUppercase + text: display.deckInfo.stemSelectedQuickFXName + color: display.deckInfo.stemSelectedQuickFXOn ? settings.accentColor : "grey" + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + font.pixelSize: fonts.scale(13.5) + anchors.leftMargin: 4 + elide: Text.ElideRight + } + + // value + ProgressBar { + id: quickfx + progressBarHeight: 9 + progressBarWidth: 115 + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.rightMargin: 20 + anchors.bottomMargin: 3 + anchors.leftMargin: 5 + + value: display.deckInfo.stemSelectedQuickFXValue + visible: fxInfoSampleName.text !== "---" + + drawAsEnabled: true + progressBarColorIndicatorLevel: display.deckInfo.stemSelectedQuickFXOn ? settings.accentColor : "grey" + progressBarBackgroundColor: "black" + } + } + + // StemInfoDetails { + // id: bottomInfoDetails3 + // finalValue: (type == 0 ? "Cue" : type == 1 ? "Fade-In" : type == 2 ? "Fade-Out" : type == 3 ? "Load" : type == 4 ? "Grid" : type == 5 ? "Loop" : "-") + // finalLabel: "TYPE" + // width: 50 + // } + } + } + + // black border & shadow + Rectangle { + id: headerBlackLine + anchors.top: display.bottom + width: parent.width + color: colors.colorBlack + height: 2 + } + Rectangle { + id: headerShadow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerBlackLine.bottom + height: 6 + gradient: Gradient { + GradientStop { position: 1.0; color: colors.colorBlack0 } + GradientStop { position: 0.0; color: colors.colorBlack63 } + } + visible: false + } + + //------------------------------------------------------------------------------------------------------------------ + // STATES + //------------------------------------------------------------------------------------------------------------------ + + Behavior on y { PropertyAnimation { duration: durations.mainTransitionSpeed; easing.type: Easing.InOutQuad } } + + states: [ + State { + name: "show"; + PropertyChanges { target: display; y: yPositionWhenShown} + }, + State { + name: "hide"; + PropertyChanges { target: display; y: yPositionWhenHidden} + } + ] +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/TempoAdjust.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/TempoAdjust.qml new file mode 100755 index 000000000000..2fd23c0b29cf --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/TempoAdjust.qml @@ -0,0 +1,184 @@ +import QtQuick 2.15 + +import '../Defines' as Defines + +Item { + id: tempoAdjust + Defines.Margins {id: customMargins } + Defines.Settings {id: settings} + + readonly property bool shift: deckInfo.shift + + property int deckId: 0 + + function getHeader(headerID) { + switch(headerID) { + case 0: + return ""; + case 1: + return "Master BPM"; + case 2: + return "BPM"; + case 3: + return "Tempo"; + case 4: + return "BPM Offset"; + case 5: + return "Tempo Offset"; + case 6: + return "Master Deck"; + case 7: + return "Tempo Range"; + case 8: + return "Key"; + case 9: + return "Original BPM"; + } + } + + function getValue(valueID) { + switch(valueID) { + case 0: + return ""; + case 1: + return deckInfo.masterBPMShort; + case 2: + return deckInfo.bpmString; + case 3: + return deckInfo.tempoStringPer; + case 4: + return (deckInfo.masterDeck == tempoAdjust.deckId) ? "0.00" : deckInfo.bpmOffset; + case 5: + return (deckInfo.masterDeck == tempoAdjust.deckId) ? "0.00%" : deckInfo.tempoNeededString; + case 6: + return deckInfo.masterDeckLetter + case 7: + return deckInfo.tempoRange + case 8: + return deckInfo.hasKey && (deckInfo.keyAdjustString != "-0") && (deckInfo.keyAdjustString != "+0") ? (settings.camelotKey ? utils.camelotConvert(deckInfo.keyString) : deckInfo.keyString) + deckInfo.keyAdjustString : deckInfo.hasKey ? (settings.camelotKey ? utils.camelotConvert(deckInfo.keyString) : deckInfo.keyString) : "No key"; + case 9: + return deckInfo.songBPM + } + } + + function getColor(valueID) { + switch(valueID) { + case 0: + return "white"; + case 1: + return settings.enableMasterBpmTextColor ? ((deckInfo.masterDeck == deckInfo.deckId) ? colors.loopActiveColor : colors.lightOrange) : "white"; + case 2: + return settings.enableBpmTextColor ? ((deckInfo.masterDeck == tempoAdjust.deckId) || ((deckInfo.bpmOffset <= 0.05) && (deckInfo.bpmOffset >= - 0.05)) ? colors.loopActiveColor : colors.lightOrange) : "white"; + case 3: + return settings.enableTempoTextColor ? ((deckInfo.tempoString <= 0.05) && (deckInfo.tempoString >= - 0.05) ? colors.loopActiveColor : colors.lightOrange) : "white"; + case 4: + return settings.enableBpmOffsetTextColor ? ((deckInfo.masterDeck == tempoAdjust.deckId) || ((deckInfo.bpmOffset <= 0.05) && (deckInfo.bpmOffset >= - 0.05)) ? colors.loopActiveColor : colors.lightOrange) : "white"; + case 5: + return settings.enableTempoOffsetTextColor ? ((deckInfo.masterDeck == tempoAdjust.deckId) || ((deckInfo.tempoNeededVal <= 0.05) && (deckInfo.tempoNeededVal >= - 0.05)) ? colors.loopActiveColor : colors.lightOrange) : "white"; + case 6: + return settings.enableMasterDeckTextColor ? ((deckInfo.masterDeck == deckInfo.deckId) ? colors.loopActiveColor : colors.lightOrange) : "white"; + case 7: + return "white" + case 8: + return deckInfo.isKeyLockOn ? colors.musicalKeyColors[deckInfo.keyIndex] : "white" + case 9: + return "white" + } + } + + Rectangle { + id: tempoBackground + width: 320 + height: 38 + + color: colors.grayBackground + // headline + Text { + anchors.top: tempoBackground.top + anchors.topMargin: 0 + anchors.left: tempoBackground.left + anchors.leftMargin: 3 + font.pixelSize: 15 + color: settings.accentColor + text: shift ? getHeader(settings.tempoDisplayLeftShift) : getHeader(settings.tempoDisplayLeft) + } + + // value + Text { + anchors.bottom: tempoBackground.bottom + anchors.bottomMargin: 0 + anchors.left: tempoBackground.left + anchors.leftMargin: 3 + font.pixelSize: 20 + font.family: "Pragmatica" + color: shift ? getColor(settings.tempoDisplayLeftShift) : getColor(settings.tempoDisplayLeft) + text: shift ? getValue(settings.tempoDisplayLeftShift) : getValue(settings.tempoDisplayLeft) + } + + // headline + Text { + anchors.top: tempoBackground.top + anchors.topMargin: 0 + anchors.left: tempoBackground.left + anchors.leftMargin: 100 + font.pixelSize: 15 + color: settings.accentColor + text: shift ? getHeader(settings.tempoDisplayCenterShift) : getHeader(settings.tempoDisplayCenter) + } + + // value + Text { + + anchors.bottom: tempoBackground.bottom + anchors.bottomMargin: 0 + anchors.left: tempoBackground.left + anchors.leftMargin: 100 + font.pixelSize: 20 + font.family: "Pragmatica" + color: shift ? getColor(settings.tempoDisplayCenterShift) : getColor(settings.tempoDisplayCenter) + text: shift ? getValue(settings.tempoDisplayCenterShift) : getValue(settings.tempoDisplayCenter) + } + + // headline + Text { + anchors.top: tempoBackground.top + anchors.topMargin: 0 + anchors.left: tempoBackground.left + anchors.leftMargin: 216 + font.pixelSize: 15 + color: settings.accentColor + text: shift ? getHeader(settings.tempoDisplayRightShift) : getHeader(settings.tempoDisplayRight) + } + + // value + Text { + + anchors.bottom: tempoBackground.bottom + anchors.bottomMargin: 0 + anchors.left: tempoBackground.left + anchors.leftMargin: 216 + font.pixelSize: 20 + font.family: "Pragmatica" + color: shift ? getColor(settings.tempoDisplayRightShift) : getColor(settings.tempoDisplayRight) + text: shift ? getValue(settings.tempoDisplayRightShift) : getValue(settings.tempoDisplayRight) + } + } + + Rectangle { + width: 1 + height: 38 + color: "#88ffffff" + anchors.top: tempoBackground.top + anchors.left: tempoBackground.left + anchors.leftMargin: 97 + } + + Rectangle { + width: 1 + height: 38 + color: "#88ffffff" + anchors.top: tempoBackground.top + anchors.left: tempoBackground.left + anchors.leftMargin: 213 + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/TrackRating.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/TrackRating.qml new file mode 100755 index 000000000000..b3857271a216 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Widgets/TrackRating.qml @@ -0,0 +1,42 @@ +import QtQuick 2.15 + +Item { + id: trackRating + + property int rating: 0 + readonly property variant ratingMap: { '-1': 0, '0': 0, '51': 1, '1': 1, '64': 2, '102': 2, '153': 3, '196': 4, '204': 4, '252': 5, '255': 5 } + readonly property int nrRatings: 5 + + width: 20 + height: 133 + + //-------------------------------------------------------------------------------------------------------------------- + + Rectangle { + id: ratingStars + anchors.left: parent.left + height: 40 + width: 170 + color: "transparent" + visible: ratingMap[trackRating.rating] <= nrRatings + + Row { + id: rowSmall + anchors.left: parent.left + anchors.top: parent.top + height: parent.height + spacing: 2 + // Repeater { + // model: (5 -(nrRatings - ratingMap[trackRating.rating])) + // Image { + // id: star + // source: "../Images/star.png" + // clip: true + // cache: true + // height: 34 + // width: 34 + // } + // } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/BPMIndicator.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/BPMIndicator.qml new file mode 100755 index 000000000000..43260bfda898 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/BPMIndicator.qml @@ -0,0 +1,68 @@ +/* +This module is used to define the top right section, right under the label. +Currently this section is dedicated to BPM and tempo fader information. +*/ +import QtQuick 2.14 +import QtQuick.Controls 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Rectangle { + id: root + + required property string group + required property color borderColor + + property real value: 0 + + color: "transparent" + radius: 6 + border.color: smallBoxBorder + border.width: 2 + + Mixxx.ControlProxy { + id: bpm + group: root.group + key: "bpm" + } + + Mixxx.ControlProxy { + id: rateRange + group: root.group + key: "rateRange" + } + + Text { + id: indicator + text: bpm.value > 0 ? bpm.value.toFixed(2) : "-" + font.pixelSize: 17 + color: fontColor + anchors.centerIn: parent + } + + Text { + id: range + + text: rateRange.value > 0 ? `-/+ \n${(rateRange.value * 100).toFixed()}%` : '' + font.pixelSize: 9 + color: fontColor + + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: 5 + anchors.topMargin: 2 + + horizontalAlignment: Text.AlignHCenter + } + + states: State { + name: "compacted" + + PropertyChanges { + target: range + visible: false + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/HotcuePoint.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/HotcuePoint.qml new file mode 100644 index 000000000000..f39bdc1ca796 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/HotcuePoint.qml @@ -0,0 +1,199 @@ +/* +This module is used to define markers element as render over the the overview waveform. +When this is written, Mixxx QML doesn't have waveform overview marker ready to be used, so this +is an attempt to provide fully functional markers for the controller screen, while the Mixxx QML +interface is still being worked on. +Consider replacing this with native overview marker in the future. +*/ +import QtQuick 2.15 +import QtQuick.Shapes 1.4 +import QtQuick.Window 2.15 + +import QtQuick.Controls 2.15 + +import Mixxx 1.0 as Mixxx + +Item { + required property real position + required property int type + + property int number: 1 + property color color: 'blue' + + enum Type { + OneShot, + Loop, + IntroIn, + IntroOut, + OutroIn, + OutroOut, + LoopIn, + LoopOut + } + + property variant typeWithNumber: [ + HotcuePoint.Type.OneShot, + HotcuePoint.Type.Loop + ] + + x: position * (Window.width - 16) + width: 21 + + // One shot + Shape { + visible: type == HotcuePoint.Type.OneShot + anchors.fill: parent + antialiasing: true + + ShapePath { + strokeWidth: 1 + strokeColor: Qt.rgba(0, 0, 0, 0.5) + fillColor: color + strokeStyle: ShapePath.SolidLine + // dashPattern: [ 1, 4 ] + startX: 0; startY: 0 + + PathLine { x: 12; y: 0 } + PathLine { x: 18; y: 6 } + PathLine { x: 18; y: 7 } + PathLine { x: 12; y: 13 } + PathLine { x: 2; y: 13 } + PathLine { x: 2; y: 80 } + PathLine { x: 0; y: 80 } + PathLine { x: 0; y: 0 } + } + } + + // Intro/Outro entry marker + Shape { + visible: type == HotcuePoint.Type.IntroIn || type == HotcuePoint.Type.OutroIn + anchors.fill: parent + antialiasing: true + + ShapePath { + strokeWidth: 1 + strokeColor: Qt.rgba(0, 0, 0, 0.5) + fillColor: "#6e6e6e" + strokeStyle: ShapePath.SolidLine + // dashPattern: [ 1, 4 ] + startX: 0; startY: 0 + + PathLine { x: 11; y: 0 } + PathLine { x: 2; y: 13 } + PathLine { x: 2; y: 80 } + PathLine { x: 0; y: 80 } + PathLine { x: 0; y: 0 } + } + } + + // Intro/Outro exit marker + Shape { + visible: type == HotcuePoint.Type.IntroOut || type == HotcuePoint.Type.OutroOut + anchors.fill: parent + antialiasing: true + + ShapePath { + strokeWidth: 1 + strokeColor: Qt.rgba(0, 0, 0, 0.5) + fillColor: "#6e6e6e" + strokeStyle: ShapePath.SolidLine + // dashPattern: [ 1, 4 ] + startX: 2; startY: 0 + + PathLine { x: 0; y: 0 } + PathLine { x: 0; y: 67 } + PathLine { x: -9; y: 80 } + PathLine { x: 2; y: 80 } + PathLine { x: 2; y: 0 } + } + } + + // Loop + Shape { + visible: type == HotcuePoint.Type.Loop + anchors.fill: parent + antialiasing: true + + ShapePath { + strokeWidth: 1 + strokeColor: Qt.rgba(0, 0, 0, 0.5) + fillColor: "#6ef36e" + strokeStyle: ShapePath.SolidLine + // dashPattern: [ 1, 4 ] + startX: 13; startY: 0 + + PathArc { + x: 2; y: 13 + radiusX: 9; radiusY: 9 + direction: PathArc.Clockwise + } + PathLine { x: 2; y: 80 } + PathLine { x: 0; y: 80 } + PathLine { x: 0; y: 0 } + PathLine { x: 21; y: 0 } + } + } + + // Loop in + Shape { + visible: type == HotcuePoint.Type.LoopIn + anchors.fill: parent + antialiasing: true + + ShapePath { + strokeWidth: 1 + strokeColor: Qt.rgba(0, 0, 0, 0.5) + fillColor: "#6ef36e" + strokeStyle: ShapePath.SolidLine + // dashPattern: [ 1, 4 ] + startX: 0; startY: 0 + + PathLine { x: 8; y: 0 } + PathLine { x: 2; y: 10 } + PathLine { x: 2; y: 80 } + PathLine { x: 0; y: 80 } + PathLine { x: 0; y: 0 } + } + } + + // Loop out + Shape { + visible: type == HotcuePoint.Type.LoopOut + anchors.fill: parent + antialiasing: true + + ShapePath { + strokeWidth: 1 + strokeColor: Qt.rgba(0, 0, 0, 0.5) + fillColor: "#6ef36e" + strokeStyle: ShapePath.SolidLine + // dashPattern: [ 1, 4 ] + startX: 2; startY: 0 + + PathLine { x: -6; y: 0 } + PathLine { x: 0; y: 10 } + PathLine { x: 0; y: 80 } + PathLine { x: 2; y: 80 } + PathLine { x: 2; y: 0 } + } + } + + Shape { + visible: type in typeWithNumber + anchors.fill: parent + antialiasing: true + + ShapePath { + fillColor: "black" + strokeColor: "black" + PathText { + x: 4 + y: 3 + font.family: "Arial" + font.pixelSize: 11 + font.weight: Font.Medium + text: `${number}` + } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/KeyIndicator.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/KeyIndicator.qml new file mode 100755 index 000000000000..06006eb8abeb --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/KeyIndicator.qml @@ -0,0 +1,122 @@ +/* +This module is used to define the top left section, right under the label. +Currently this section is dedicated to key/pitch information. +*/ +import QtQuick 2.14 +import QtQuick.Controls 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Rectangle { + id: root + + required property string group + + enum Key { + NoKey, + OneD, + EightD, + ThreeD, + TenD, + FiveD, + TwelveD, + SevenD, + SecondD, + NineD, + FourD, + ElevenD, + SixD, + TenM, + FiveM, + TwelveM, + SevenM, + TwoM, + NineM, + FourM, + ElevenM, + SixM, + OneM, + EightM, + ThreeM + } + + property variant colorsMap: [ + "#b09840", // No key + "#b960a2",// 1d + "#9fc516", // 8d + "#527fc0", // 3d + "#f28b2e", // 10d + "#5bc1cf", // 5d + "#e84c4d", // 12d + "#73b629", // 7d + "#8269ab", // 2d + "#fdd615", // 9d + "#3cc0f0", // 4d + "#4cb686", // 11d + "#4cb686", // 6d + "#f5a158", // 10m + "#7bcdd9", // 5m + "#ed7171", // 12m + "#8fc555", // 7m + "#9b86be", // 2m + "#fcdf45", // 9m + "#63cdf4", // 4m + "#f1845f", // 11m + "#70c4a0", // 6m + "#c680b6", // 1m + "#b2d145", // 8m + "#7499cd" // 3m + ] + + property variant textMap: [ + "No key", + "1d", + "8d", + "3d", + "10d", + "5d", + "12d", + "7d", + "2d", + "9d", + "4d", + "11d", + "6d", + "10m", + "5m", + "12m", + "7m", + "2m", + "9m", + "4m", + "11m", + "6m", + "1m", + "8m", + "3m" + ] + + Mixxx.ControlProxy { + id: keyProxy + group: root.group + key: "key" + } + + required property color borderColor + + readonly property int key: keyProxy.value > 0 ? keyProxy.value : KeyIndicator.Key.NoKey + + radius: 6 + border.color: colorsMap[key] + border.width: 2 + + color: colorsMap[key] + + Text { + text: textMap[key] + font.pixelSize: 17 + color: fontColor + anchors.centerIn: parent + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/Keyboard.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/Keyboard.qml new file mode 100644 index 000000000000..cf5fa1d203c1 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/Keyboard.qml @@ -0,0 +1,133 @@ +/* +This module is used render the keyboard scale, originating from C major (do). +*/ +import QtQuick 2.15 +import QtQuick.Shapes 1.4 +import QtQuick.Layouts 1.3 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +import "." as S4MK3 + +Item { + id: root + + required property string group + + Mixxx.ControlProxy { + id: keyProxy + group: root.group + key: "key" + } + + readonly property int key: keyProxy.value + + RowLayout { + anchors.fill: parent + spacing: 0 + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Rectangle { anchors.fill: parent; color: "transparent" } + } + Repeater { + id: whiteKeys + + model: 7 + + property variant keyMap: [ + S4MK3.KeyIndicator.Key.OneD, + S4MK3.KeyIndicator.Key.ThreeD, + S4MK3.KeyIndicator.Key.FiveD, + S4MK3.KeyIndicator.Key.TwelveD, + S4MK3.KeyIndicator.Key.SecondD, + S4MK3.KeyIndicator.Key.FourD, + S4MK3.KeyIndicator.Key.SixD, + S4MK3.KeyIndicator.Key.TenM, + S4MK3.KeyIndicator.Key.TwelveM, + S4MK3.KeyIndicator.Key.TwoM, + S4MK3.KeyIndicator.Key.NineM, + S4MK3.KeyIndicator.Key.ElevenM, + S4MK3.KeyIndicator.Key.OneM, + S4MK3.KeyIndicator.Key.ThreeM + ] + + Rectangle { + Layout.preferredWidth: 21 + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + radius: 2 + border.width: 1 + border.color: root.key == whiteKeys.keyMap[index] || root.key == whiteKeys.keyMap[index + 7] ? "red" : "black" + color: root.key == whiteKeys.keyMap[index] || root.key == whiteKeys.keyMap[index + 7] ? "#aaaaaa" : "white" + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Rectangle { anchors.fill: parent; color: "transparent" } + } + } + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Rectangle { anchors.fill: parent; color: "transparent" } + } + Repeater { + id: blackKeys + + model: 5 + + property variant keyMap: [ + S4MK3.KeyIndicator.Key.EightD, + S4MK3.KeyIndicator.Key.TenD, + S4MK3.KeyIndicator.Key.SevenD, + S4MK3.KeyIndicator.Key.NineD, + S4MK3.KeyIndicator.Key.ElevenD, + S4MK3.KeyIndicator.Key.FiveM, + S4MK3.KeyIndicator.Key.SevenM, + S4MK3.KeyIndicator.Key.FourM, + S4MK3.KeyIndicator.Key.SixM, + S4MK3.KeyIndicator.Key.EightM, + ] + + Item { + Layout.fillHeight: true + Layout.preferredWidth: index == 1 ? 42 : index == 4 ? 12 : 21 + Rectangle { + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 12 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + color: "transparent" + ColumnLayout { + anchors.fill: parent + spacing: 0 + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + radius: 2 + border.width: 1 + color: root.key == blackKeys.keyMap[index] || root.key == blackKeys.keyMap[index + blackKeys.model] ? "#aaaaaa" : "black" + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Rectangle { anchors.fill: parent; color: "transparent" } + } + } + } + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Rectangle { anchors.fill: parent; color: "transparent" } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/LoopSizeIndicator.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/LoopSizeIndicator.qml new file mode 100755 index 000000000000..bd5938268693 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/LoopSizeIndicator.qml @@ -0,0 +1,63 @@ +/* +This module is used to define the center right section, above the waveform. +Currently this section is dedicated to display loop state information such as loop state, anchor mode or size. +*/ +import QtQuick 2.14 +import QtQuick.Controls 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Rectangle { + id: root + + required property string group + + property color loopReverseOffBoxColor: Qt.rgba(255/255,113/255,9/255, 1) + property color loopOffBoxColor: Qt.rgba(67/255,70/255,66/255, 1) + property color loopOffFontColor: "white" + property color loopOnBoxColor: Qt.rgba(125/255,246/255,64/255, 1) + property color loopOnFontColor: "black" + + Mixxx.ControlProxy { + id: beatloopSize + group: root.group + key: "beatloop_size" + } + + Mixxx.ControlProxy { + id: loopEnabled + group: root.group + key: "loop_enabled" + } + + Mixxx.ControlProxy { + id: loopAnchor + group: root.group + key: "loop_anchor" + } + + readonly property bool on: loopEnabled.value + + radius: 6 + border.width: 2 + border.color: (loopSizeIndicator.on ? loopOnBoxColor : (loopAnchor.value == 0 ? loopOffBoxColor : loopReverseOffBoxColor)) + color: (loopSizeIndicator.on ? loopOnBoxColor : (loopAnchor.value == 0 ? loopOffBoxColor : loopReverseOffBoxColor)) + + Text { + id: indicator + text: (beatloopSize.value < 1 ? `1/${1 / beatloopSize.value}` : `${beatloopSize.value}`); + anchors.centerIn: parent + font.pixelSize: 46 + color: (loopSizeIndicator.on ? loopOnFontColor : loopOffFontColor) + } + + states: State { + name: "compacted" + + PropertyChanges { + target: indicator + font.pixelSize: 17 + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/OnAirTrack.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/OnAirTrack.qml new file mode 100644 index 000000000000..8d4097ccad63 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/OnAirTrack.qml @@ -0,0 +1,82 @@ +/* +This module is used to define the top section o the screen. +Currently this section is dedicated to display title and artist of the track loaded on the deck. +*/ +import QtQuick 2.14 +import QtQuick.Controls 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Item { + id: root + + required property string group + property var deckPlayer: Mixxx.PlayerManager.getPlayer(root.group) + readonly property var currentTrack: deckPlayer.currentTrack + property bool scrolling: true + + property real speed: 1.7 + property real spacing: 30 + + Rectangle { + id: frame + anchors.top: root.top + anchors.bottom: root.bottom + width: parent.width + x: 6 + color: 'transparent' + + readonly property string fulltext: !trackLoadedControl.value || root.currentTrack?.title.trim().length + root.currentTrack?.artist.trim().length == 0 ? qsTr("No Track Loaded") : `${root.currentTrack?.title} - ${root.currentTrack?.artist}`.trim() + + Text { + id: text1 + text: frame.fulltext + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + Text { + id: text2 + visible: root.width < text1.implicitWidth + anchors.left: text1.right + anchors.leftMargin: spacing + text: frame.fulltext + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + } + + Mixxx.ControlProxy { + id: trackLoadedControl + + group: root.group + key: "track_loaded" + } + + Timer { + id: timer + + property int modifier: -root.speed + + repeat: true + interval: 15 + running: root.width < text1.implicitWidth && root.scrolling + + onTriggered: { + frame.x += modifier; + if (frame.x <= -text1.implicitWidth - spacing) { + frame.x = 0; + } + } + + onRunningChanged: { + if (!running) { + frame.x = 6; + } + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/Progression.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/Progression.qml new file mode 100755 index 000000000000..93bc6f2eb401 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/Progression.qml @@ -0,0 +1,43 @@ +/* +This module is used to draw an overlay on the waveform overview in order to highlight better the playback progression. +As the native Mixxx QML component involves, this component might become redundant and should be replaces with native modules. +*/ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Item { + id: root + + required property string group + + property real windowWidth: Window.width + + Mixxx.ControlProxy { + id: trackLoaded + group: root.group + key: "track_loaded" + } + + Mixxx.ControlProxy { + id: playposition + group: root.group + key: "playposition" + } + + width: Math.round(playposition.value * (320 - 12)) + visible: trackLoaded.value + clip: true + + Rectangle { + anchors.fill: parent + anchors.leftMargin: -border.width + anchors.topMargin: -border.width + anchors.bottomMargin: -border.width + border.width: 2 + border.color:"black" + color: Qt.rgba(0.39, 0.80, 0.96, 0.3) + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/SplashOff.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/SplashOff.qml new file mode 100644 index 000000000000..2954d704c83c --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/SplashOff.qml @@ -0,0 +1,15 @@ +import QtQuick 2.15 + +Rectangle { + id: root + anchors.fill: parent + color: "black" + + Image { + anchors.centerIn: parent + width: root.width*0.8 + height: root.height + fillMode: Image.PreserveAspectFit + source: engine.getSetting("idleBackground") || "../../../images/templates/logo_mixxx.png" + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/StockScreen.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/StockScreen.qml new file mode 100644 index 000000000000..f392a4548dd1 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/StockScreen.qml @@ -0,0 +1,634 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 + +import "../../../qml" as Skin +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +import S4MK3 as S4MK3 + +Rectangle { + id: root + + required property string group + required property string screenId + + anchors.fill: parent + color: "black" + + function onSharedDataUpdate(data) { + if (!root) return; + + console.log(`Received data on screen#${root.screenId} while currently bind to ${root.group}: ${JSON.stringify(data)}`); + if (typeof data === "object" && typeof data.group[root.screenId] === "string" && root.group !== data.group[root.screenId]) { + root.group = data.group[root.screenId] + waveformOverview.player = Mixxx.PlayerManager.getPlayer(root.group) + artwork.player = Mixxx.PlayerManager.getPlayer(root.group) + console.log(`Changed group for screen ${root.screenId} to ${root.group}`); + } + var shouldBeCompacted = false; + if (typeof data.padsMode === "object") { + scrollingWaveform.visible = data.padsMode[root.group] === 4 + artworkSpacer.visible = data.padsMode[root.group] === 1 + shouldBeCompacted |= scrollingWaveform.visible || artworkSpacer.visible + } + 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.group] ? S4MK3.TimeAndBeatloopIndicator.Mode.BeetjumpSize : S4MK3.TimeAndBeatloopIndicator.Mode.RemainingTime + timeIndicator.update() + } + } + + Mixxx.ControlProxy { + id: trackLoadedControl + + group: root.group + key: "track_loaded" + + onValueChanged: (value) => { + if (!value && deckInfo) { + deckInfo.state = "" + scrollingWaveform.visible = false + } + } + } + + Timer { + id: channelchange + + interval: 5000 + repeat: true + running: false + + onTriggered: { + root.onSharedDataUpdate({ + group: { + "leftdeck": screenId === "leftdeck" && trackLoadedControl.group === "[Channel1]" ? "[Channel3]" : "[Channel1]", + "rightdeck": screenId === "rightdeck" && trackLoadedControl.group === "[Channel2]" ? "[Channel4]" : "[Channel2]", + }, + scrollingWaveform: { + "[Channel1]": true, + "[Channel2]": true, + "[Channel3]": true, + "[Channel4]": true, + }, + keyboardMode: { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false, + }, + displayBeatloopSize: { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false, + }, + }); + } + } + + Component.onCompleted: { + if (typeof engine.makeSharedDataConnection !== "function") { + return + } + + engine.makeSharedDataConnection(root.onSharedDataUpdate) + + root.onSharedDataUpdate({ + group: { + "leftdeck": "[Channel1]", + "rightdeck": "[Channel2]", + }, + scrollingWaveform: { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false, + }, + keyboardMode: { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false, + }, + displayBeatloopSize: { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false, + }, + }); + } + + Rectangle { + anchors.fill: parent + color: "transparent" + + Image { + id: artwork + anchors.fill: parent + + property var player: Mixxx.PlayerManager.getPlayer(root.group) + + source: player.currentTrack?.coverArtUrl + height: 100 + width: 100 + fillMode: Image.PreserveAspectFit + + opacity: artworkSpacer.visible ? 1 : 0.2 + z: -1 + } + } + + ColumnLayout { + anchors.fill: parent + 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 + + scrolling: !scrollingWaveform.visible + } + } + } + + // Indicator + Rectangle { + id: deckInfo + + Layout.fillWidth: true + Layout.preferredHeight: 105 + Layout.leftMargin: 6 + Layout.rightMargin: 6 + color: "transparent" + + GridLayout { + id: gridLayout + anchors.fill: parent + columnSpacing: 6 + rowSpacing: 6 + columns: 2 + + // Section: Key + S4MK3.KeyIndicator { + id: keyIndicator + group: root.group + borderColor: smallBoxBorder + + Layout.fillWidth: true + Layout.fillHeight: true + } + + // Section: Bpm + S4MK3.BPMIndicator { + id: bpmIndicator + group: root.group + borderColor: smallBoxBorder + + Layout.fillWidth: true + Layout.fillHeight: true + } + + // Section: Key + S4MK3.TimeAndBeatloopIndicator { + id: timeIndicator + group: root.group + + Layout.fillWidth: true + Layout.preferredHeight: 72 + timeColor: smallBoxBorder + } + + // Section: Bpm + S4MK3.LoopSizeIndicator { + id: loopSizeIndicator + group: root.group + + Layout.fillWidth: true + Layout.preferredHeight: 72 + } + } + 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" + } + } + } + + Item { + id: scrollingWaveform + + Layout.fillWidth: true + Layout.minimumHeight: scrollingWaveform.visible ? 120 : 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + + visible: false + + Mixxx.ControlProxy { + id: zoomControl + + group: root.group + key: "waveform_zoom" + } + + MixxxControls.WaveformDisplay { + id: singleWaveform + group: root.group + x: 0 + width: 320 + height: 100 + + Behavior on height { PropertyAnimation { duration: 90} } + anchors.fill: parent + 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|right" + color: "#FF0000" + textColor: "#FFFFFF" + text: " %1 " + } + + untilMark.showTime: true + untilMark.showBeats: true + untilMark.align: Qt.AlignCenter + untilMark.textSize: 14 + + Mixxx.WaveformMark { + control: "cue_point" + text: 'C' + align: 'top|right' + color: 'red' + 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" + 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" + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + } + } + } + + Mixxx.ControlProxy { + id: deckScratching + + group: root.group + key: "scratch2_enable" + + onValueChanged: { + if (typeof engine.makeSharedDataConnection !== "function") { + return; + } + + if (value) { + waveformTimer.running = false; + scrollingWaveform.visible = true; + deckInfo.state = scrollingWaveform.visible ? "compacted" : "" + } else { + waveformTimer.running = true; + waveformTimer.restart() + } + } + } + + Timer { + id: waveformTimer + + interval: 4000 + repeat: false + running: false + + onTriggered: { + scrollingWaveform.visible = false; + deckInfo.state = scrollingWaveform.visible ? "compacted" : "" + } + } + + // Spacer + Item { + id: artworkSpacer + + Layout.fillWidth: true + Layout.minimumHeight: artworkSpacer.visible ? 120 : 0 + Layout.leftMargin: 6 + Layout.rightMargin: 6 + + visible: false + + Rectangle { + color: "transparent" + visible: parent.visible + anchors.top: parent.top + anchors.bottom: parent.bottom + x: 153 + width: 2 + } + } + + // Track progress + Item { + id: waveform + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 6 + Layout.rightMargin: 6 + layer.enabled: true + + S4MK3.Progression { + id: progression + group: root.group + + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + } + + Mixxx.WaveformOverview { + readonly property var player: Mixxx.PlayerManager.getPlayer(root.group) + id: waveformOverview + anchors.fill: parent + + track: player.currentTrack + } + + Mixxx.ControlProxy { + id: samplesControl + + group: root.group + key: "track_samples" + } + + // Hotcue + Repeater { + model: 16 + + S4MK3.HotcuePoint { + required property int index + + Mixxx.ControlProxy { + id: samplesControl + + group: root.group + key: "track_samples" + } + + Mixxx.ControlProxy { + id: hotcueEnabled + group: root.group + key: `hotcue_${index + 1}_status` + } + + Mixxx.ControlProxy { + id: hotcuePosition + group: root.group + key: `hotcue_${index + 1}_position` + } + + Mixxx.ControlProxy { + id: hotcueColor + group: root.group + key: `hotcue_${number}_color` + } + + anchors.top: parent.top + // anchors.left: parent.left + anchors.bottom: parent.bottom + visible: hotcueEnabled.value + + number: this.index + 1 + type: S4MK3.HotcuePoint.Type.OneShot + position: hotcuePosition.value / samplesControl.value + color: `#${(hotcueColor.value >> 16).toString(16).padStart(2, '0')}${((hotcueColor.value >> 8) & 255).toString(16).padStart(2, '0')}${(hotcueColor.value & 255).toString(16).padStart(2, '0')}` + } + } + + // Intro + S4MK3.HotcuePoint { + + Mixxx.ControlProxy { + id: introStartEnabled + group: root.group + key: `intro_start_enabled` + } + + Mixxx.ControlProxy { + id: introStartPosition + group: root.group + key: `intro_start_position` + } + + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: introStartEnabled.value + + type: S4MK3.HotcuePoint.Type.IntroIn + position: introStartPosition.value / samplesControl.value + } + + // Extro + S4MK3.HotcuePoint { + + Mixxx.ControlProxy { + id: introEndEnabled + group: root.group + key: `intro_end_enabled` + } + + Mixxx.ControlProxy { + id: introEndPosition + group: root.group + key: `intro_end_position` + } + + 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` + } + + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: loopStartPosition.value > 0 + + type: S4MK3.HotcuePoint.Type.LoopIn + position: loopStartPosition.value / samplesControl.value + } + + // Loop out + S4MK3.HotcuePoint { + Mixxx.ControlProxy { + id: loopEndPosition + group: root.group + key: `loop_end_position` + } + + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: loopEndPosition.value > 0 + + type: S4MK3.HotcuePoint.Type.LoopOut + position: loopEndPosition.value / samplesControl.value + } + } + + S4MK3.Keyboard { + id: keyboard + group: root.group + visible: false + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 6 + Layout.rightMargin: 6 + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/TimeAndBeatloopIndicator.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/TimeAndBeatloopIndicator.qml new file mode 100755 index 000000000000..be5d8cdf9a6d --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/TimeAndBeatloopIndicator.qml @@ -0,0 +1,95 @@ +/* +This module is used to define the center left section, above the waveform. +Currently this section is dedicated to show the remaining time as well as the beatloop when changing. +*/ +import QtQuick 2.14 +import QtQuick.Controls 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Rectangle { + id: root + + required property string group + + property color timeColor: Qt.rgba(67/255,70/255,66/255, 1) + property color beatjumpColor: 'yellow' + + enum Mode { + RemainingTime, + BeetjumpSize + } + + property int mode: TimeAndBeatloopIndicator.Mode.RemainingTime + + radius: 6 + border.color: timeColor + border.width: 2 + color: timeColor + + Text { + id: indicator + anchors.centerIn: parent + text: "0.00" + + font.pixelSize: 46 + color: fontColor + + Mixxx.ControlProxy { + id: progression + group: root.group + key: "playposition" + } + + Mixxx.ControlProxy { + id: duration + group: root.group + key: "duration" + } + + Mixxx.ControlProxy { + id: beatjump + group: root.group + key: "beatjump_size" + } + + Mixxx.ControlProxy { + id: endoftrack + group: root.group + key: "end_of_track" + onValueChanged: (value) => { + root.border.color = value ? 'red' : timeColor + root.color = value ? 'red' : timeColor + } + } + } + + Component.onCompleted: { + indicator.text = Qt.binding(function() { + let newValue = ""; + if (root.mode === TimeAndBeatloopIndicator.Mode.RemainingTime) { + var seconds = ((1.0 - progression.value) * duration.value); + newValue = `-${parseInt(seconds / 60).toString().padStart(2, '0')}:${parseInt(seconds % 60).toString().padStart(2, '0')}`; + } else { + newValue = (beatjump.value < 1 ? `1/${1 / beatjump.value}` : `${beatjump.value}`); + } + return newValue + }); + } + + states: State { + name: "compacted" + + PropertyChanges { + target: indicator + font.pixelSize: 17 + } + } + + onModeChanged: () => { + border.color = root.mode == TimeAndBeatloopIndicator.Mode.BeetjumpSize ? beatjumpColor : timeColor + color = root.mode == TimeAndBeatloopIndicator.Mode.BeetjumpSize ? beatjumpColor : timeColor + indicator.color = root.mode == TimeAndBeatloopIndicator.Mode.BeetjumpSize ? 'black' : 'white' + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/WaveformOverview.qml b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/WaveformOverview.qml new file mode 100755 index 000000000000..b77b3bc1cb67 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/WaveformOverview.qml @@ -0,0 +1,162 @@ +/* +This module is used to waveform overview, at the bottom of the screen. It is reusing component definition of `WaveformOverview.qml` but remove +the link to markers and provide hooks with screen update/redraw, needed for partial updates. +Currently this section is dedicated to BPM and tempo fader information. +*/ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +Item { + id: root + + required property string group + property var deckPlayer: Mixxx.PlayerManager.getPlayer(root.group) + property real scale: 0.2 + + visible: false + antialiasing: true + anchors.fill: parent + + Connections { + onGroupChanged: { + deckPlayer = Mixxx.PlayerManager.getPlayer(root.group) + console.log("Group changed!!") + } + } + + Rectangle { + color: "white" + anchors.top: parent.top + anchors.bottom: parent.bottom + x: 153 + width: 2 + } + Item { + id: waveformContainer + + property real duration: samplesControl.value / sampleRateControl.value + + anchors.fill: parent + clip: true + + Mixxx.ControlProxy { + id: samplesControl + + group: root.group + key: "track_samples" + } + + Mixxx.ControlProxy { + id: sampleRateControl + + 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 + + property real effectiveZoomFactor: (zoomControl.value * rateRatioControl.value / root.scale) * 6 + + width: waveformContainer.duration * effectiveZoomFactor + height: parent.height + x: 0.5 * waveformContainer.width - playPositionControl.value * width + visible: true + + Shape { + id: preroll + + 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 + + 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; + } + } + } + } + + 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 { + id: waveformOverview + // property real duration: samplesControl.value / sampleRateControl.onValueChanged + + player: root.player + anchors.fill: parent + channels: Mixxx.WaveformOverview.Channels.BothChannels + renderer: Mixxx.WaveformOverview.Renderer.RGB + colorHigh: 'white' + colorMid: 'blue' + colorLow: 'green' + } + } +} diff --git a/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/qmldir b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/qmldir new file mode 100644 index 000000000000..a330672059a4 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/qmldir @@ -0,0 +1,13 @@ +module S4MK3 +BPMIndicator 1.0 BPMIndicator.qml +HotcuePoint 1.0 HotcuePoint.qml +Keyboard 1.0 Keyboard.qml +KeyIndicator 1.0 KeyIndicator.qml +LoopSizeIndicator 1.0 LoopSizeIndicator.qml +OnAirTrack 1.0 OnAirTrack.qml +Progression 1.0 Progression.qml +TimeAndBeatloopIndicator 1.0 TimeAndBeatloopIndicator.qml +WaveformOverview 1.0 WaveformOverview.qml +SplashOff 1.0 SplashOff.qml +StockScreen 1.0 StockScreen.qml +AdvancedScreen 1.0 AdvancedScreen.qml diff --git a/src/controllers/bulk/bulksupported.h b/src/controllers/bulk/bulksupported.h index a7bf6c916d60..f10673e87ba4 100644 --- a/src/controllers/bulk/bulksupported.h +++ b/src/controllers/bulk/bulksupported.h @@ -28,4 +28,5 @@ constexpr static bulk_support_lookup bulk_supported[] = { {{0x06f8, 0xb107}, {0x83, 0x03, std::nullopt}}, // Hercules Mk4 {{0x06f8, 0xb100}, {0x86, 0x06, std::nullopt}}, // Hercules Mk2 {{0x06f8, 0xb120}, {0x82, 0x03, std::nullopt}}, // Hercules MP3 LE / Glow + {{0x17cc, 0x1720}, {0x00, 0x03, 0x04}}, // Traktor NI S4 Mk3 }; diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 2f162bdcd92b..c1a2767d5f77 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -1,5 +1,6 @@ #include "controllers/rendering/controllerrenderingengine.h" +#include #include #include #include @@ -10,17 +11,15 @@ #include #include #include +#include #include "controllers/controller.h" #include "controllers/controllerenginethreadcontrol.h" #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" -#include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" #include "moc_controllerrenderingengine.cpp" -#include "qml/qmlwaveformoverview.h" #include "util/cmdlineargs.h" #include "util/logger.h" #include "util/thread_affinity.h" -#include "util/time.h" #include "util/timer.h" // Used in the renderFrame method to properly abort the rendering and terminate the engine. @@ -179,7 +178,15 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { return; } QSurfaceFormat format; - format.setSamples(m_screenInfo.msaa); + // FIXME multi sampling appears to be unsupported when using offscreen + // rendering on Wayland QPA: + // warning [CtrlScreen_rightdeck] QWaylandGLContext::makeCurrent: + // eglError: 0x3009, this: 0x7ffd9c001770 warning [CtrlScreen_rightdeck] + // QRhiGles2: Failed to make context current. Expect bad things to happen. + // warning [CtrlScreen_rightdeck] Failed to create RHI (backend 2) + if (QGuiApplication::platformName() != QStringLiteral("wayland")) { + format.setSamples(m_screenInfo.msaa); + } format.setDepthBufferSize(16); format.setStencilBufferSize(8); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index f36405696830..93f1a1d92879 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -1,5 +1,8 @@ #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#include + +#include #include #ifdef MIXXX_USE_QML @@ -11,12 +14,9 @@ #include #endif -#include "control/controlobject.h" #include "controllers/controller.h" -#include "controllers/scripting/colormapperjsproxy.h" #include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" #include "errordialoghandler.h" -#include "mixer/playermanager.h" #include "moc_controllerscriptenginelegacy.cpp" #ifdef MIXXX_USE_QML #include "qml/qmlmixxxcontrollerscreen.h" @@ -358,7 +358,7 @@ bool ControllerScriptEngineLegacy::initialize() { watchFilePath(path); auto pQmlEngine = std::dynamic_pointer_cast(m_pJSEngine); pQmlEngine->addImportPath(path); - qCWarning(m_logger) << pQmlEngine->importPathList(); + qCDebug(m_logger) << "The QML import path is" << pQmlEngine->importPathList(); } } else if (!m_modules.isEmpty()) { qCWarning(m_logger) << "Controller mapping has QML library definitions but no " diff --git a/src/waveform/renderers/allshader/waveformrenderbeat.cpp b/src/waveform/renderers/allshader/waveformrenderbeat.cpp index 3056321e7a68..5d2340da4599 100644 --- a/src/waveform/renderers/allshader/waveformrenderbeat.cpp +++ b/src/waveform/renderers/allshader/waveformrenderbeat.cpp @@ -56,14 +56,20 @@ bool WaveformRenderBeat::preprocessInner() { return false; } +#ifndef __SCENEGRAPH__ int alpha = m_waveformRenderer->getBeatGridAlpha(); if (alpha == 0) { return false; } + m_color.setAlphaF(alpha / 100.0f); +#endif - const float devicePixelRatio = m_waveformRenderer->getDevicePixelRatio(); + if (!m_color.alpha()) { + // Don't render the beatgrid lines is there are fully transparent + return true; + } - m_color.setAlphaF(alpha / 100.0f); + const float devicePixelRatio = m_waveformRenderer->getDevicePixelRatio(); const double trackSamples = m_waveformRenderer->getTrackSamples(); if (trackSamples <= 0.0) { diff --git a/src/waveform/renderers/allshader/waveformrendererstem.cpp b/src/waveform/renderers/allshader/waveformrendererstem.cpp index 73ed176ed88d..93b056c12f55 100644 --- a/src/waveform/renderers/allshader/waveformrendererstem.cpp +++ b/src/waveform/renderers/allshader/waveformrendererstem.cpp @@ -225,7 +225,7 @@ bool WaveformRendererStem::preprocessInner() { } // Cast to float - float max = static_cast(u8max); + float max = static_cast(u8max) * allGain; // Apply the gains if (layerIdx) { diff --git a/tools/README b/tools/README index def2a3d4040b..3055fbed113c 100644 --- a/tools/README +++ b/tools/README @@ -20,3 +20,11 @@ cd build && gcc ../tools/dummy_hid_device.c -lhidapi-hidraw -o dummy_hid_device # Allow the created hidraw device to be accessed by the user. You may also set the write udev rules. Finally, you can also run Mixxx as root, but that's not recommended. sudo chown "$USER" "$(ls -1t /dev/hidraw* | head -n 1)" ``` + +## Traktor S4 Mk3 Screen drawing + +This small program can be used directly to draw arbitrary rectangles on the Traktor S4 Mk3 screens. It may also be useful for one to perform tests on top of the existing reversed engineered protocol. + +```sh +cd build && gcc ../tools/traktor_s4_mk3_screen_test.c `pkg-config --cflags --libs libusb-1.0` -o traktor_s4_mk3_screen_test && ./traktor_s4_mk3_screen_test +``` diff --git a/tools/clang_format.py b/tools/clang_format.py index 16076470c916..baeface76d29 100755 --- a/tools/clang_format.py +++ b/tools/clang_format.py @@ -49,7 +49,7 @@ def run_clang_format_on_lines(rootdir, file_to_format, stylepath=None): ", ".join("{}-{}".format(*x) for x in file_to_format.lines), ) - filename = os.path.join(rootdir, file_to_format.filename) + filename = os.path.join(rootdir, file_to_format.filename).strip() cmd = [ "clang-format", "--style=file", diff --git a/tools/traktor_s4_mk3_screen_test.c b/tools/traktor_s4_mk3_screen_test.c new file mode 100644 index 000000000000..cf14fd3833a2 --- /dev/null +++ b/tools/traktor_s4_mk3_screen_test.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define VENDOR_ID 0x17cc +#define PRODUCT_ID 0x1720 +#define IN_EPADDR 0x00 +#define OUT_EPADDR 0x03 + +static const uint8_t header_data[] = { + 0x84, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // Draw offset (y=0, x=0) + 0x1, + 0x40, + 0x0, + 0xf0, // Draw dimenssion (width=320, height=240) +}; +static const uint8_t footer_data[] = { + 0x40, 0x00, 0x00, 0x00}; + +int main(int argc, char** argv) { + libusb_context* context; + int transferred; + + if (argc != 7) { + fprintf(stderr, "Usage: %s \n", *argv); + return -1; + } + + libusb_init(&context); + + libusb_device_handle* handle = libusb_open_device_with_vid_pid( + context, VENDOR_ID, PRODUCT_ID); + + if (!handle) { + fprintf(stderr, "Unable to open USB Bulk device\n"); + return -1; + } + + uint8_t screen_idx = atoi(argv[1]); + + if (screen_idx != 0 && screen_idx != 1) { + fprintf(stderr, "Invalid screen ID %d\n", screen_idx); + return -1; + } + + uint16_t x = atoi(argv[2]); + uint16_t y = atoi(argv[3]); + uint16_t width = atoi(argv[4]); + uint16_t height = atoi(argv[5]); + uint16_t color = strtol(argv[6], NULL, 2); + + uint8_t* data = malloc(width * height * sizeof(uint16_t) + + sizeof(header_data) + sizeof(footer_data)); + uint8_t* header = data; + + memcpy(header, header_data, sizeof(header_data)); + + header[2] = screen_idx; + + 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; + + printf("draw x=%d,y=%d,width=%d,height=%d with color %x\n", x, y, width, height, color); + + size_t payload_size = width * height * sizeof(uint16_t) + + sizeof(header_data) + sizeof(footer_data); + uint8_t* payload = data + sizeof(header_data); + uint8_t* footer = payload + width * height * sizeof(uint16_t); + + for (int px = 0; px < width * height; px++) { + payload[px * sizeof(uint16_t)] = color >> 8; + payload[px * sizeof(uint16_t) + 1] = color & 0xff; + } + + memcpy(footer, footer_data, sizeof(footer_data)); + + footer[2] = screen_idx; + + clock_t start, end; + double cpu_time_used; + + start = clock(); + int ret = libusb_bulk_transfer(handle, OUT_EPADDR, data, payload_size, &transferred, 0); + end = clock(); + cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; + + if (ret < 0) { + fprintf(stderr, "Unable to send to USB Bulk device\n"); + + } else { + fprintf(stderr, "Sent %d bytes in %f ms\n", transferred, cpu_time_used); + } + + libusb_close(handle); + libusb_exit(context); + + return 0; +}