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 00000000000..6fc6d3d952a --- /dev/null +++ b/res/controllers/Traktor Kontrol S4 MK3.bulk.xml @@ -0,0 +1,683 @@ + + + + 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/Traktor Kontrol S4 MK3.hid.xml b/res/controllers/Traktor Kontrol S4 MK3.hid.xml index a254772e6c5..7d6883a2e23 100644 --- a/res/controllers/Traktor Kontrol S4 MK3.hid.xml +++ b/res/controllers/Traktor Kontrol S4 MK3.hid.xml @@ -10,6 +10,16 @@ + + + + + + + + + + + + + + + + + + - + diff --git a/res/controllers/Traktor-Kontrol-S4-MK3.js b/res/controllers/Traktor-Kontrol-S4-MK3.js index 960c2ac50c1..eba220bf4cb 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3.js +++ b/res/controllers/Traktor-Kontrol-S4-MK3.js @@ -21,6 +21,30 @@ const LedColors = { white: 68, }; +const LedColorMap = { + 0xCC0000: LedColors.red, + 0xCC5E00: LedColors.carrot, + 0xCC7800: LedColors.orange, + 0xCC9200: LedColors.honey, + + 0xCCCC00: LedColors.yellow, + 0x81CC00: LedColors.lime, + 0x00CC00: LedColors.green, + 0x00CC49: LedColors.aqua, + + 0x00CCCC: LedColors.celeste, + 0x0091CC: LedColors.sky, + 0x0000CC: LedColors.blue, + 0xCC00CC: LedColors.purple, + + 0xAD65FF: LedColors.fuscia, + 0xCC0079: LedColors.magenta, + 0xCC477E: LedColors.azalea, + 0xCC4761: LedColors.salmon, + + 0xCCCCCC: LedColors.white, +}; + // This define the sequence of color to use for pad button when in keyboard mode. This should make them look like an actual keyboard keyboard octave, except for C, which is green to help spotting it. const KeyboardColors = [ @@ -99,8 +123,7 @@ const MixerControlsMixAuxOnShift = !!engine.getSetting("mixerControlsMicAuxOnShi // Default: false const UseBeatloopRollInsteadOfSampler = !!engine.getSetting("useBeatloopRollInsteadOfSampler"); -// Predefined beatlooproll sizes. Note that if you use AddLoopHalveAndDoubleOnBeatloopRollTab, the first and -// last size will be ignored +// Predefined beatlooproll sizes. const BeatLoopRolls = [ engine.getSetting("beatLoopRollsSize1") || 1/8, engine.getSetting("beatLoopRollsSize2") || 1/4, @@ -112,6 +135,18 @@ const BeatLoopRolls = [ engine.getSetting("beatLoopRollsSize8") || "double" ]; +// Predefined beatjump. +const BeatJumps = [ + engine.getSetting("beatJumpSize1") || 1, + engine.getSetting("beatJumpSize2") || 2, + engine.getSetting("beatJumpSize3") || 4, + engine.getSetting("beatJumpSize4") || 8, + engine.getSetting("beatJumpSize5") || 16, + engine.getSetting("beatJumpSize6") || 32, + engine.getSetting("beatJumpSize7") || 64, + engine.getSetting("beatJumpSize8") || "beatjump" +]; + // Define the speed of the jogwheel. This will impact the speed of the LED playback indicator, the scratch, and the speed of // the motor if enable. Recommended value are 33 + 1/3 or 45. @@ -145,6 +180,8 @@ const SoftwareMixerHeadphone = !!engine.getSetting("softwareMixerHeadphone"); // Define custom default layout used by the pads, instead of intro/outro and first 4 hotcues. const DefaultPadLayout = engine.getSetting("defaultPadLayout"); +// Whether or not to use the ShareDataAPI, available in the PR 12199. +const UseSharedDataAPI = engine.getSetting("useSharedDataAPI"); // The LEDs only support 16 base colors. Adding 1 in addition to // the normal 2 for Button.prototype.brightnessOn changes the color @@ -189,6 +226,65 @@ const SamplerCrossfaderAssign = true; const MotorWindUpMilliseconds = 1200; const MotorWindDownMilliseconds = 900; +/* + * Kontrol S4 Mk3 hardware-specific constants + */ +const wheelRelativeMax = 2 ** 32 - 1; +const wheelAbsoluteMax = 2879; + +const wheelTimerMax = 2 ** 32 - 1; +const wheelTimerTicksPerSecond = 100000000; // One tick every 10ns + +const baseRevolutionsPerSecond = BaseRevolutionsPerMinute / 60; +const wheelTicksPerTimerTicksToRevolutionsPerSecond = wheelTimerTicksPerSecond / wheelAbsoluteMax; + +// The active tab ID. This is used when SharedDataAPI is active, to communicate with the screens which tab is currently selected. +const ActiveTabPadID = { + jump: 1, + hotcue: 2, + roll: 3, + samples: 4, + loop: 5, + mute: 7, + record: 8, + tone: 11, + fxbank1: 12, + fxbank2: 13, +}; + +const wheelLEDmodes = { + off: 0, + dimFlash: 1, + spot: 2, + ringFlash: 3, + dimSpot: 4, + individuallyAddressable: 5, // set byte 4 to 0 and set byes 8 - 40 to color values +}; + +// The mode available, which the wheel can be used for. +const wheelModes = { + jog: 0, + vinyl: 1, + motor: 2, + loopIn: 3, + loopOut: 4, +}; + +const moveModes = { + beat: 0, + bpm: 1, + grid: 2, + keyboard: 3, + hotcueColor: 4, +}; + +// tracks state across input reports +let wheelTimer = null; +// This is a global variable so the S4Mk3Deck Components have access +// to it and it is guaranteed to be calculated before processing +// input for the Components. +let wheelTimerDelta = 0; + /* * HID report parsing library */ @@ -328,13 +424,13 @@ class Component { this.send(value); } outConnect() { - if (this.outKey !== undefined && this.group !== undefined) { + if (this.outKey !== undefined && this.group !== undefined && this.outConnections.length === 0) { const connection = engine.makeConnection(this.group, this.outKey, this.output.bind(this)); // This is useful for case where effect would have been fully disabled in Mixxx. This appears to be the case during unit tests. if (connection) { - this.outConnections[0] = connection; + this.outConnections.push(connection); } else { - console.warn(`Unable to connect ${this.group}.${this.outKey}' to the controller output. The control appears to be unavailable.`); + console.warn(`Unable to connect '${this.group}.${this.outKey}' to the controller output. The control appears to be unavailable.`); } } } @@ -346,6 +442,7 @@ class Component { } outTrigger() { for (const connection of this.outConnections) { + if (!connection) { continue; } connection.trigger(); } } @@ -373,6 +470,9 @@ class ComponentContainer extends Component { } reconnectComponents(callback) { for (const component of this) { + if (typeof component.unshift === "function" && component.unshift.length === 0) { + component.unshift(); + } if (typeof component.outDisconnect === "function" && component.outDisconnect.length === 0) { component.outDisconnect(); } @@ -429,6 +529,13 @@ class Deck extends ComponentContainer { this.color = colors[0]; } this.secondDeckModes = null; + this.selectedHotcue = null; + if (UseSharedDataAPI) { + const data = engine.getSharedData() || {}; + if (!data.selectedHotcue) { return; } + data.selectedHotcue[this.group] = this.selectedHotcue; + engine.setSharedData(data); + } } toggleDeck() { if (this.decks === undefined) { @@ -441,14 +548,36 @@ class Deck extends ComponentContainer { newDeckIndex = 0; } - this.switchDeck(Deck.groupForNumber(this.decks[newDeckIndex])); + this.switchDeck(this.decks[newDeckIndex]); } - switchDeck(newGroup) { + switchDeck(newDeck) { + const newGroup = Deck.groupForNumber(newDeck); + + switch (this.moveMode) { + case moveModes.beat: + case moveModes.bpm: + case moveModes.grid: + case moveModes.hotcueColor: + this.moveMode = null; + this.selectedHotcue = null; + + if (UseSharedDataAPI) { + const data = engine.getSharedData() || {}; + if (data.selectedHotcue) { + data.selectedHotcue[this.group] = this.selectedHotcue; + engine.setSharedData(data); + } + } + break; + } + const currentModes = { wheelMode: this.wheelMode, moveMode: this.moveMode, }; + this.selectedStem.fill(false); + engine.setValue(this.group, "scratch2_enable", false); this.group = newGroup; this.color = this.groupsToColors[newGroup]; @@ -470,12 +599,22 @@ class Deck extends ComponentContainer { } else if (component.group.search(script.eqRegEx) !== -1) { component.group = `[EqualizerRack1_${newGroup}_Effect1]`; } else if (component.group.search(script.quickEffectRegEx) !== -1) { - component.group = `[QuickEffectRack1_${newGroup}]`; + component.group = quickFxChannel(newGroup); } component.color = this.groupsToColors[newGroup]; }); this.secondDeckModes = currentModes; + this.currentDeckNumber = newDeck; + + if (!UseSharedDataAPI) { + return; + } + + const data = engine.getSharedData() || {}; + if (!data.group) { return; } + data.group[this.decks[0] === 1 ? "leftdeck":"rightdeck"] = this.group; + engine.setSharedData(data); } static groupForNumber(deckNumber) { return `[Channel${deckNumber}]`; @@ -488,13 +627,19 @@ class Button extends Component { super(options); - if (this.input === undefined) { + if (this.input === undefined + || (typeof this.onLongPress === "function" && this.onLongPress.length === 0) + || (typeof this.onLongRelease === "function" && this.onLongRelease.length === 0) + || (typeof this.onShortPress === "function" && this.onShortPress.length === 0) + || (typeof this.onShortRelease === "function" && this.onShortRelease.length === 0) + || (typeof this.onPress === "function" && this.onPress.length === 0) + || (typeof this.onRelease === "function" && this.onRelease.length === 0)) { this.input = this.defaultInput; - if (typeof this.input === "function" - && this.inReport instanceof HIDInputReport - && this.input.length === 0) { - this.inConnect(); - } + } + if (typeof this.input === "function" + && this.inReport instanceof HIDInputReport + && this.input.length === 0) { + this.inConnect(); } if (this.longPressTimeOutMillis === undefined) { @@ -554,24 +699,32 @@ class Button extends Component { } } defaultInput(pressed) { + this.pressed = pressed; if (pressed) { + this.isShortPress = true; this.isLongPress = false; + if (typeof this.onPress === "function" && this.onPress.length === 0) { this.onPress(); } if (typeof this.onShortPress === "function" && this.onShortPress.length === 0) { this.onShortPress(); } if ((typeof this.onLongPress === "function" && this.onLongPress.length === 0) || (typeof this.onLongRelease === "function" && this.onLongRelease.length === 0)) { this.longPressTimer = engine.beginTimer(this.longPressTimeOutMillis, () => { this.isLongPress = true; + this.isShortPress = false; this.longPressTimer = 0; if (typeof this.onLongPress !== "function") { return; } this.onLongPress(this); }, true); } } else if (this.isLongPress) { + this.isLongPress = false; + if (typeof this.onRelease === "function" && this.onRelease.length === 0) { this.onRelease(); } if (typeof this.onLongRelease === "function" && this.onLongRelease.length === 0) { this.onLongRelease(); } } else { + this.isShortPress = false; if (this.longPressTimer !== 0) { engine.stopTimer(this.longPressTimer); this.longPressTimer = 0; } + if (typeof this.onRelease === "function" && this.onRelease.length === 0) { this.onRelease(); } if (typeof this.onShortRelease === "function" && this.onShortRelease.length === 0) { this.onShortRelease(); } } } @@ -610,8 +763,6 @@ class TriggerButton extends Button { class PowerWindowButton extends Button { constructor(options) { super(options); - this.isLongPressed = false; - this.longPressTimer = 0; } onShortPress() { script.toggleControl(this.group, this.inKey); @@ -717,8 +868,21 @@ class HotcueButton extends PushButton { this.inKey = `hotcue_${this.number}_clear`; } input(pressed) { - engine.setValue(this.group, "scratch2_enable", false); - engine.setValue(this.group, this.inKey, pressed); + if (this.deck.moveMode === moveModes.hotcueColor) { + this.deck.selectedHotcue = pressed ? this.number : null; + if (UseSharedDataAPI) { + const data = engine.getSharedData() || {}; + if (!data.selectedHotcue) { return; } + data.selectedHotcue[this.group] = this.deck.selectedHotcue; + engine.setSharedData(data); + } + + } else if (this.deck.libraryPlayButton.pressed) { + engine.setValue(this.deck.libraryPlayButton.group, this.inKey, pressed); + } else { + engine.setValue(this.group, "scratch2_enable", false); + engine.setValue(this.group, this.inKey, pressed); + } } output(value) { if (value) { @@ -728,10 +892,10 @@ class HotcueButton extends PushButton { } } outConnect() { - if (undefined !== this.group) { + if (undefined !== this.group && this.outConnections.length === 0) { const connection0 = engine.makeConnection(this.group, this.outKey, this.output.bind(this)); if (connection0) { - this.outConnections[0] = connection0; + this.outConnections.push(connection0); } else { console.warn(`Unable to connect ${this.group}.${this.outKey}' to the controller output. The control appears to be unavailable.`); } @@ -740,7 +904,7 @@ class HotcueButton extends PushButton { this.output(engine.getValue(this.group, this.outKey)); }); if (connection1) { - this.outConnections[1] = connection1; + this.outConnections.push(connection1); } else { console.warn(`Unable to connect ${this.group}.${this.colorKey}' to the controller output. The control appears to be unavailable.`); } @@ -796,17 +960,17 @@ class KeyboardButton extends PushButton { if (this.number + offset < 1 || this.number + offset > 24) { this.send(0); } else { - this.send(color + (value ? this.brightnessOn : this.brightnessOff)); + this.send(value ? LedColors.yellow : color); } } outConnect() { - if (undefined !== this.group) { + if (undefined !== this.group && this.outConnections.length === 0) { const connection = engine.makeConnection(this.group, "key", (key) => { const offset = this.deck.keyboardOffset - (this.shifted ? 8 : 0); this.output(key === this.number + offset); }); if (connection) { - this.outConnections[0] = connection; + this.outConnections.push(connection); } else { console.warn(`Unable to connect ${this.group}.key' to the controller output. The control appears to be unavailable.`); } @@ -814,6 +978,149 @@ class KeyboardButton extends PushButton { } } +/* + * Represent a pad button that acts as a stem controller. It will be used to mute or unmute a stem or select it for other operation such as volume or quick effect control + */ +class StemButton extends PushButton { + constructor(options) { + super(options); + if (this.number === undefined || !Number.isInteger(this.number) || this.number < 1 || this.number > 4) { + throw Error("StemButton must have a number property of an integer between 1 and 4"); + } + if (this.deck === undefined) { + throw Error("StemButton must have a deck attached to it"); + } + if (this.deck.mixer === undefined) { + throw Error("StemButton must have a deck with a mixer attached to it"); + } + this.color = 0; + this.muted = 0; + this.outConnect(); + } + unshift() { + this.outTrigger(); + } + shift() { + this.outTrigger(); + } + input(pressed) { + if (!this.enabled) { + return; + } + if (this.shifted && pressed) { + script.toggleControl(stemChannel(this.group, this.number - 1), "mute"); + } + if (!this.shifted) { + this.deck.selectedStem[this.number - 1] = pressed; + if (UseSharedDataAPI) { + const data = engine.getSharedData() || {}; + if (!data.selectedStems) { return; } + data.selectedStems[this.group] = this.deck.selectedStem; + engine.setSharedData(data); + } + } + if (!this.shifted && pressed && this.deck.mixer.firstPressedFxSelector !== null) { + const presetNumber = this.deck.mixer.calculatePresetNumber(); + this.color = QuickEffectPresetColors[presetNumber - 1]; + engine.setValue(quickFxChannel(stemChannel(this.group, this.number - 1)), "loaded_chain_preset", presetNumber + 1); + this.deck.mixer.firstPressedFxSelector = null; + this.deck.mixer.secondPressedFxSelector = null; + this.deck.mixer.resetFxSelectorColors(); + + if (!UseSharedDataAPI) { return; } + + const data = engine.getSharedData() || {}; + if (!data.selectedQuickFX === undefined) { return; } + data.selectedQuickFX = null; + engine.setSharedData(data); + } + } + output() { + if (!this.color || !this.enabled) { + this.send(0); + } else { + this.send(this.color + (this.muted ? this.brightnessOff : this.brightnessOn)); + } + } + outConnect() { + if (undefined !== this.group) { + const muteConnection = engine.makeConnection(stemChannel(this.group, this.number - 1), "mute", (mute) => { + this.muted = mute; + this.output(); + }); + if (muteConnection) { + this.outConnections[0] = muteConnection; + } else { + console.warn(`Unable to connect '${stemChannel(this.group, this.number)}.mute' to the controller output. The control appears to be unavailable.`); + } + const colorConnection = engine.makeConnection(stemChannel(this.group, this.number - 1), "color", (color) => { + this.color = this.colorMap.getValueForNearestColor(color); + this.output(); + }); + if (colorConnection) { + this.outConnections[1] = colorConnection; + } else { + console.warn(`Unable to connect '${stemChannel(this.group, this.number)}.color' to the controller output. The control appears to be unavailable.`); + } + const enabledConnection = engine.makeConnection(this.group, "stem_count", (count) => { + this.enabled = count >= this.number; + this.output(); + }); + if (enabledConnection) { + this.outConnections[2] = enabledConnection; + } else { + console.warn(`Unable to connect '${this.group}.stem_count' to the controller output. The control appears to be unavailable.`); + } + } + } +} + +class StemMuteButton extends PushButton { + constructor(options) { + if (options.number === undefined || !Number.isInteger(options.number) || options.number < 1 || options.number > 4) { + throw Error("StemMuteButton must have a number property of an integer between 1 and 4"); + } + super(options); + this.color = 0; + this.muted = 0; + this.outConnect(); + } + output() { + if (!this.enabled) { + this.send(0); + } else { + this.send(LedColors.white + (this.muted ? this.brightnessOff : this.brightnessOn)); + } + } + input(pressed) { + if (pressed) { + script.toggleControl(stemChannel(this.group, this.number - 1), "mute"); + } + } + outConnect() { + if (undefined !== this.group) { + const muteConnection = engine.makeConnection(stemChannel(this.group, this.number - 1), "mute", (mute) => { + this.muted = mute; + this.output(); + }); + if (muteConnection) { + this.outConnections[0] = muteConnection; + } else { + console.warn(`Unable to connect '${stemChannel(this.group, this.number)}.mute' to the controller output. The control appears to be unavailable.`); + } + const enabledConnection = engine.makeConnection(this.group, "stem_count", (count) => { + this.enabled = count >= this.number; + this.output(); + }); + if (enabledConnection) { + this.outConnections[1] = enabledConnection; + } else { + console.warn(`Unable to connect '${this.group}.stem_count' to the controller output. The control appears to be unavailable.`); + } + } + } +} + /* * Represent a pad button that will trigger a pre-defined beatloop size as set in BeatLoopRolls. */ @@ -833,16 +1140,24 @@ class BeatLoopRollButton extends TriggerButton { } options.key = `beatlooproll_${size}_activate`; options.onShortPress = function() { - if (!this.deck.beatloopSize) { - this.deck.beatloopSize = engine.getValue(this.group, "beatloop_size"); + if (!this.deck.beatloop) { + this.deck.beatloop = { + size: engine.getValue(this.group, "beatloop_size"), + start: engine.getValue(this.group, "loop_start_position"), + end: engine.getValue(this.group, "loop_end_position"), + enabled: engine.getValue(this.group, "loop_enabled"), + }; } engine.setValue(this.group, this.inKey, true); }; options.onShortRelease = function() { engine.setValue(this.group, this.inKey, false); - if (this.deck.beatloopSize) { - engine.setValue(this.group, "beatloop_size", this.deck.beatloopSize); - this.deck.beatloopSize = undefined; + if (this.deck.beatloop) { + engine.setValue(this.group, "loop_start_position", this.deck.beatloop.start); + engine.setValue(this.group, "loop_end_position", this.deck.beatloop.end); + engine.setValue(this.group, "beatloop_size", this.deck.beatloop.size); + engine.setValue(this.group, "loop_enabled", this.deck.beatloop.enabled); + this.deck.beatloop = undefined; } }; } @@ -862,6 +1177,61 @@ class BeatLoopRollButton extends TriggerButton { } } +/* + * Represent a pad button that will trigger a pre-defined beatjump as set in BeatJumps. + */ +class BeatJumpButton extends TriggerButton { + constructor(options) { + if (options.number === undefined || !Number.isInteger(options.number) || options.number < 0 || options.number > 7) { + throw Error("BeatJumpButton must have a number property of an integer between 0 and 7"); + } + if (BeatJumps[options.number] === "beatjump") { + options.key = "beatjump_forward"; + } else if (BeatJumps[options.number] === "half") { + options.key = "beatjump_size_halve"; + } else if (BeatJumps[options.number] === "double") { + options.key = "beatjump_size_double"; + } else { + const size = parseFloat(BeatJumps[options.number]); + if (isNaN(size)) { + throw Error(`BeatJumpButton ${options.number}'s size "${BeatJumps[options.number]}" is invalid. Must be a float, or the literal 'beatjump', 'half' or 'double'`); + } + options.key = `beatjump_${size}_forward`; + } + super(options); + if (this.deck === undefined) { + throw Error("BeatJumpButton must have a deck attached to it"); + } + + this.outConnect(); + } + shift() { + if (BeatJumps[this.number] === "beatjump") { + this.setKey("beatjump_backward"); + } else if (!isNaN(parseFloat(BeatJumps[this.number]))) { + const size = parseFloat(BeatJumps[this.number]); + this.setKey(`beatjump_${size}_backward`); + } + } + unshift() { + if (BeatJumps[this.number] === "beatjump") { + this.setKey("beatjump_forward"); + } else if (!isNaN(parseFloat(BeatJumps[this.number]))) { + const size = parseFloat(BeatJumps[this.number]); + this.setKey(`beatjump_${size}_forward`); + } + } + output(value) { + if (BeatJumps[this.number] === "beatjump") { + this.send(LedColors.salmon); + } else if (!isNaN(parseFloat(BeatJumps[this.number]))) { + this.send(this.color + (value ? this.brightnessOn : this.brightnessOff)); + } else { + this.send(LedColors.white); + } + } +} + /* * Represent a pad button that interact with a sampler (load, play/pause, cue, eject) */ @@ -877,7 +1247,12 @@ class SamplerButton extends Button { onShortPress() { if (!this.shifted) { if (engine.getValue(this.group, "track_loaded") === 0) { - engine.setValue(this.group, "LoadSelectedTrack", 1); + if (this.deck.samplerStemSelection !== null) { + engine.setValue(this.group, "load_selected_track_stems", this.deck.samplerStemSelection); + this.deck.samplerStemSelection = null; + } else { + engine.setValue(this.group, "LoadSelectedTrack", 1); + } } else { engine.setValue(this.group, "cue_gotoandplay", 1); } @@ -909,16 +1284,16 @@ class SamplerButton extends Button { } } outConnect() { - if (undefined !== this.group) { + if (undefined !== this.group && this.outConnections.length === 0) { const connection0 = engine.makeConnection(this.group, "play", this.output.bind(this)); if (connection0) { - this.outConnections[0] = connection0; + this.outConnections.push(connection0); } else { console.warn(`Unable to connect ${this.group}.play' to the controller output. The control appears to be unavailable.`); } const connection1 = engine.makeConnection(this.group, "track_loaded", this.output.bind(this)); if (connection1) { - this.outConnections[1] = connection1; + this.outConnections.push(connection1); } else { console.warn(`Unable to connect ${this.group}.track_loaded' to the controller output. The control appears to be unavailable.`); } @@ -1099,14 +1474,12 @@ class Mixer extends ComponentContainer { this.resetFxSelectorColors(); this.quantizeButton = new Button({ - input: function(pressed) { - if (pressed) { - this.globalQuantizeOn = !this.globalQuantizeOn; - for (let deckIdx = 1; deckIdx <= 4; deckIdx++) { - engine.setValue(`[Channel${deckIdx}]`, "quantize", this.globalQuantizeOn); - } - this.send(this.globalQuantizeOn ? 127 : 0); + onPress: function() { + this.globalQuantizeOn = !this.globalQuantizeOn; + for (let deckIdx = 1; deckIdx <= 4; deckIdx++) { + engine.setValue(`[Channel${deckIdx}]`, "quantize", this.globalQuantizeOn); } + this.send(this.globalQuantizeOn ? 127 : 0); }, globalQuantizeOn: false, inByte: 11, @@ -1241,8 +1614,22 @@ class FXSelect extends Button { } } this.outReport.send(); + + if (!UseSharedDataAPI) { return; } + + const data = engine.getSharedData() || {}; + if (!data.selectedQuickFX === undefined) { return; } + data.selectedQuickFX = this.mixer.calculatePresetNumber() + 1; + engine.setSharedData(data); } else { this.mixer.secondPressedFxSelector = this.number; + + if (!UseSharedDataAPI) { return; } + + const data = engine.getSharedData() || {}; + if (!data.selectedQuickFX === undefined) { return; } + data.selectedQuickFX = this.mixer.calculatePresetNumber() + 1; + engine.setSharedData(data); } } @@ -1262,7 +1649,7 @@ class FXSelect extends Button { if (this.mixer.firstPressedFxSelector !== null) { for (const deck of [1, 2, 3, 4]) { const presetNumber = this.mixer.calculatePresetNumber(); - engine.setValue(`[QuickEffectRack1_[Channel${deck}]]`, "loaded_chain_preset", presetNumber + 1); + engine.setValue(quickFxChannel(`[Channel${deck}]`), "loaded_chain_preset", presetNumber + 1); } } if (this.mixer.firstPressedFxSelector === this.number) { @@ -1273,6 +1660,13 @@ class FXSelect extends Button { this.mixer.comboSelected = true; } this.mixer.secondPressedFxSelector = null; + + if (!UseSharedDataAPI) { return; } + + const data = engine.getSharedData() || {}; + if (!data.selectedQuickFX === undefined) { return; } + data.selectedQuickFX = null; + engine.setSharedData(data); } } @@ -1287,7 +1681,7 @@ class QuickEffectButton extends Button { if (this.number === undefined || !Number.isInteger(this.number) || this.number < 1) { throw Error("number attribute must be an integer >= 1"); } - this.group = `[QuickEffectRack1_[Channel${this.number}]]`; + this.group = quickFxChannel(`[Channel${this.number}]`); this.outConnect(); } onShortPress() { @@ -1321,16 +1715,16 @@ class QuickEffectButton extends Button { this.outConnections[1].trigger(); } outConnect() { - if (this.group !== undefined) { + if (this.group !== undefined && this.outConnections.length === 0) { const connection0 = engine.makeConnection(this.group, "loaded_chain_preset", this.presetLoaded.bind(this)); if (connection0) { - this.outConnections[0] = connection0; + this.outConnections.push(connection0); } else { console.warn(`Unable to connect ${this.group}.loaded_chain_preset' to the controller output. The control appears to be unavailable.`); } const connection1 = engine.makeConnection(this.group, "enabled", this.output.bind(this)); if (connection1) { - this.outConnections[1] = connection1; + this.outConnections.push(connection1); } else { console.warn(`Unable to connect ${this.group}.enabled' to the controller output. The control appears to be unavailable.`); } @@ -1339,7 +1733,7 @@ class QuickEffectButton extends Button { } /* - * Kontrol S4 Mk3 hardware-specific constants + * Kontrol S4 Mk3 hardware-specific member constants */ Pot.prototype.max = 2 ** 12 - 1; @@ -1347,6 +1741,7 @@ Pot.prototype.inBit = 0; Pot.prototype.inBitLength = 16; Encoder.prototype.inBitLength = 4; +Encoder.prototype.tickDelta = 1 / (2 << Encoder.prototype.inBitLength); // valid range 0 - 3, but 3 makes some colors appear whitish Button.prototype.brightnessOff = 0; @@ -1358,71 +1753,20 @@ Button.prototype.uncoloredOutput = function(value) { const color = (value > 0) ? (this.color || LedColors.white) + this.brightnessOn : LedColors.off; this.send(color); }; -Button.prototype.colorMap = new ColorMapper({ - 0xCC0000: LedColors.red, - 0xCC5E00: LedColors.carrot, - 0xCC7800: LedColors.orange, - 0xCC9200: LedColors.honey, - - 0xCCCC00: LedColors.yellow, - 0x81CC00: LedColors.lime, - 0x00CC00: LedColors.green, - 0x00CC49: LedColors.aqua, - - 0x00CCCC: LedColors.celeste, - 0x0091CC: LedColors.sky, - 0x0000CC: LedColors.blue, - 0xCC00CC: LedColors.purple, +Button.prototype.colorMap = new ColorMapper(LedColorMap); - 0xCC0091: LedColors.fuscia, - 0xCC0079: LedColors.magenta, - 0xCC477E: LedColors.azalea, - 0xCC4761: LedColors.salmon, - - 0xCCCCCC: LedColors.white, -}); - -const wheelRelativeMax = 2 ** 32 - 1; -const wheelAbsoluteMax = 2879; - -const wheelTimerMax = 2 ** 32 - 1; -const wheelTimerTicksPerSecond = 100000000; // One tick every 10ns - -const baseRevolutionsPerSecond = BaseRevolutionsPerMinute / 60; -const wheelTicksPerTimerTicksToRevolutionsPerSecond = wheelTimerTicksPerSecond / wheelAbsoluteMax; - -const wheelLEDmodes = { - off: 0, - dimFlash: 1, - spot: 2, - ringFlash: 3, - dimSpot: 4, - individuallyAddressable: 5, // set byte 4 to 0 and set byes 8 - 40 to color values -}; +/* + * helper function + */ -// The mode available, which the wheel can be used for. -const wheelModes = { - jog: 0, - vinyl: 1, - motor: 2, - loopIn: 3, - loopOut: 4, +const quickFxChannel = (group) => { + return `[QuickEffectRack1_${group}]`; }; -const moveModes = { - beat: 0, - bpm: 1, - grid: 2, - keyboard: 3, +const stemChannel = (group, idx) => { + return `${group.substr(0, group.length - 1)}Stem${idx + 1}]`; }; -// tracks state across input reports -let wheelTimer = null; -// This is a global variable so the S4Mk3Deck Components have access -// to it and it is guaranteed to be calculated before processing -// input for the Components. -let wheelTimerDelta = 0; - /* * Kontrol S4 Mk3 hardware specific mapping logic */ @@ -1460,14 +1804,14 @@ class S4Mk3EffectUnit extends ComponentContainer { this.group = undefined; this.output(false); }, - input: function(pressed) { + onPress: function() { if (!this.shifted) { for (const index of [0, 1, 2]) { const effectGroup = `[EffectRack1_EffectUnit${unitNumber}_Effect${index + 1}]`; - engine.setValue(effectGroup, "enabled", pressed); + engine.setValue(effectGroup, "enabled", true); } - this.output(pressed); - } else if (pressed) { + this.output(true); + } else { if (this.unit.focusedEffect !== null) { this.unit.setFocusedEffect(null); } else { @@ -1475,6 +1819,15 @@ class S4Mk3EffectUnit extends ComponentContainer { this.shift(); } } + }, + onRelease: function() { + if (!this.shifted) { + for (const index of [0, 1, 2]) { + const effectGroup = `[EffectRack1_EffectUnit${unitNumber}_Effect${index + 1}]`; + engine.setValue(effectGroup, "enabled", false); + } + this.output(false); + } } }); @@ -1746,10 +2099,10 @@ class S4Mk3Deck extends Deck { this.setKey("loop_enabled"); }, outConnect: function() { - if (this.outKey !== undefined && this.group !== undefined) { + if (this.outKey !== undefined && this.group !== undefined && this.outConnections.length === 0) { const connection = engine.makeConnection(this.group, this.outKey, this.output.bind(this)); if (connection) { - this.outConnections[0] = connection; + this.outConnections.push(connection); } else { console.warn(`Unable to connect ${this.group}.${this.outKey}' to the controller output. The control appears to be unavailable.`); } @@ -1767,7 +2120,7 @@ class S4Mk3Deck extends Deck { this.indicator(false); const wheelOutput = new Uint8Array(40).fill(0); wheelOutput[0] = decks[0] - 1; - controller.sendOutputReport(wheelOutput.buffer, null, 50, true); + controller.sendOutputReport(50, wheelOutput.buffer, true); if (!skipRestore) { this.deck.wheelMode = this.previousWheelMode; } @@ -1824,14 +2177,11 @@ class S4Mk3Deck extends Deck { this.output(false); } : undefined, onShortPress: function() { - this.deck.libraryEncoder.gridButtonPressed = true; - if (this.shift) { engine.setValue(this.group, "bpm_tap", true); } }, onLongPress: function() { - this.deck.libraryEncoder.gridButtonPressed = true; this.previousMoveMode = this.deck.moveMode; if (this.shifted) { @@ -1843,7 +2193,6 @@ class S4Mk3Deck extends Deck { this.indicator(true); }, onLongRelease: function() { - this.deck.libraryEncoder.gridButtonPressed = false; if (this.previousMoveMode !== null) { this.deck.moveMode = this.previousMoveMode; this.previousMoveMode = null; @@ -1851,7 +2200,6 @@ class S4Mk3Deck extends Deck { this.indicator(false); }, onShortRelease: function() { - this.deck.libraryEncoder.gridButtonPressed = false; script.triggerControl(this.group, "beats_translate_curpos"); if (this.shift) { @@ -1864,7 +2212,7 @@ class S4Mk3Deck extends Deck { deck: this, input: function(value) { if (value) { - this.deck.switchDeck(Deck.groupForNumber(decks[0])); + this.deck.switchDeck(decks[0]); this.outReport.data[io.deckButtonOutputByteOffset] = colors[0] + this.brightnessOn; // turn off the other deck selection button's LED this.outReport.data[io.deckButtonOutputByteOffset + 1] = DeckSelectAlwaysBacklit ? colors[1] + this.brightnessOff : 0; @@ -1876,7 +2224,7 @@ class S4Mk3Deck extends Deck { deck: this, input: function(value) { if (value) { - this.deck.switchDeck(Deck.groupForNumber(decks[1])); + this.deck.switchDeck(decks[1]); // turn off the other deck selection button's LED this.outReport.data[io.deckButtonOutputByteOffset] = DeckSelectAlwaysBacklit ? colors[0] + this.brightnessOff : 0; this.outReport.data[io.deckButtonOutputByteOffset + 1] = colors[1] + this.brightnessOn; @@ -1899,18 +2247,52 @@ class S4Mk3Deck extends Deck { shift: function() { this.output(true); }, - input: function(pressed) { - if (pressed) { - this.deck.shift(); - } else { - this.deck.unshift(); + onPress: function() { + this.deck.shift.call(this.deck); + + if (!UseSharedDataAPI) { + return; } - } + + const data = engine.getSharedData() || {}; + if (!data.shift) { return; } + data.shift[decks[0] === 1 ? "leftdeck":"rightdeck"] = true; + engine.setSharedData(data); + }, + onRelease: function() { + this.deck.unshift.call(this.deck); + + if (!UseSharedDataAPI) { + return; + } + + const data = engine.getSharedData() || {}; + if (!data.shift) { return; } + data.shift[decks[0] === 1 ? "leftdeck":"rightdeck"] = false; + engine.setSharedData(data); + }, }); this.leftEncoder = new Encoder({ deck: this, onChange: function(right) { + if (this.deck.hasSelectedStem()) { + this.deck.selectedStem.forEach((selected, stemIdx) => { + if (!selected) { return; } + + engine.setValue(stemChannel(this.group, stemIdx), "volume", engine.getValue(stemChannel(this.group, stemIdx), "volume") + (right ? this.tickDelta : -this.tickDelta)); + }); + return; + } + + if (this.deck.hasSelectedStem()) { + this.deck.selectedStem.forEach((selected, stemIdx) => { + if (!selected) { return; } + + engine.setValue(stemChannel(this.group, stemIdx), "volume", engine.getValue(stemChannel(this.group, stemIdx), "volume") + (right ? this.tickDelta : -this.tickDelta)); + }); + return; + } switch (this.deck.moveMode) { case moveModes.grid: @@ -1918,18 +2300,34 @@ class S4Mk3Deck extends Deck { break; case moveModes.keyboard: if ( - this.deck.keyboard[0].offset === (right ? 16 : 0) + this.deck.pads[0].offset === (right ? 16 : 0) ) { return; } this.deck.keyboardOffset += (right ? 1 : -1); - this.deck.keyboard.forEach(function(pad) { + this.deck.pads.forEach(function(pad) { pad.outTrigger(); }); break; case moveModes.bpm: script.triggerControl(this.group, right ? "beats_translate_later" : "beats_translate_earlier"); break; + case moveModes.hotcueColor:{ + if (this.deck.selectedHotcue === null) { + return; + } + const currentColor = Button.prototype.colorMap.getValueForNearestColor(engine.getValue(this.deck.group, `hotcue_${this.deck.selectedHotcue}_color`)); + let currentColorIdx = Object.keys(LedColorMap).indexOf(Object.keys(LedColorMap).find(key => LedColorMap[key] === currentColor)); + currentColorIdx = Math.max( + Math.min( + Object.keys(LedColorMap).length - 2, // Last color is reserved for loop hotcue + currentColorIdx + (right ? 1:-1) + ), + 0 + ); + engine.setValue(this.deck.group, `hotcue_${this.deck.selectedHotcue}_color`, Object.keys(LedColorMap)[currentColorIdx]); + break; + } default: if (!this.shifted) { if (!this.deck.leftEncoderPress.pressed) { @@ -1959,17 +2357,50 @@ class S4Mk3Deck extends Deck { } }); this.leftEncoderPress = new PushButton({ - input: function(pressed) { - this.pressed = pressed; - if (pressed) { + deck: this, + onPress: function() { + if (this.deck.hasSelectedStem()) { + this.deck.selectedStem.forEach((selected, stemIdx) => { + if (!selected) { return; } + + engine.setValue(stemChannel(this.group, stemIdx), "volume", engine.getValue(stemChannel(this.group, stemIdx), "volume") === 1.0 ? 0 : 1); + }); + return; + } + if (this.shifted) { script.toggleControl(this.group, "pitch_adjust_set_default"); } + + if (!UseSharedDataAPI) { + return; + } + + + const data = engine.getSharedData() || {}; + if (!data.displayBeatloopSize) { return; } + data.displayBeatloopSize[this.group] = true; + engine.setSharedData(data); }, + onRelease: UseSharedDataAPI ? function() { + const data = engine.getSharedData() || {}; + if (!data.displayBeatloopSize) { return; } + data.displayBeatloopSize[this.group] = false; + engine.setSharedData(data); + } : undefined }); this.rightEncoder = new Encoder({ deck: this, onChange: function(right) { + if (this.deck.hasSelectedStem()) { + this.deck.selectedStem.forEach((selected, stemIdx) => { + if (!selected) { return; } + + engine.setValue(quickFxChannel(stemChannel(this.group, stemIdx)), "super1", engine.getValue(quickFxChannel(stemChannel(this.group, stemIdx)), "super1") + (right ? this.tickDelta : -this.tickDelta)); + }); + return; + } + if (this.deck.wheelMode === wheelModes.loopIn || this.deck.wheelMode === wheelModes.loopOut) { const moveFactor = this.shifted ? LoopEncoderShiftMoveFactor : LoopEncoderMoveFactor; const valueIn = engine.getValue(this.group, "loop_start_position") + (right ? moveFactor : -moveFactor); @@ -1984,8 +2415,14 @@ class S4Mk3Deck extends Deck { } }); this.rightEncoderPress = new PushButton({ - input: function(pressed) { - if (!pressed) { + deck: this, + onPress: function() { + if (this.deck.hasSelectedStem()) { + this.deck.selectedStem.forEach((selected, stemIdx) => { + if (!selected) { return; } + + script.toggleControl(quickFxChannel(stemChannel(this.group, stemIdx)), "enabled"); + }); return; } const loopEnabled = engine.getValue(this.group, "loop_enabled"); @@ -1998,26 +2435,34 @@ class S4Mk3Deck extends Deck { }); this.libraryEncoder = new Encoder({ - libraryPlayButtonPressed: false, - gridButtonPressed: false, - starButtonPressed: false, - libraryViewButtonPressed: false, - libraryPlaylistButtonPressed: false, + deck: this, currentSortedColumnIdx: -1, onChange: function(right) { - if (this.libraryViewButtonPressed) { + let fxChanged = false; + for (const fxButton of this.deck.effectUnit.buttons) { + if (fxButton.pressed) { + script.triggerControl(fxButton.group, right ? "next_effect" : "prev_effect"); + fxChanged = true; + } + } + + if (fxChanged) { + return; + } + + if (this.deck.libraryViewButton.pressed) { this.currentSortedColumnIdx = (LibrarySortableColumns.length + this.currentSortedColumnIdx + (right ? 1 : -1)) % LibrarySortableColumns.length; engine.setValue("[Library]", "sort_column", LibrarySortableColumns[this.currentSortedColumnIdx]); - } else if (this.starButtonPressed) { + } else if (this.deck.libraryStarButton.pressed) { if (this.shifted) { // FIXME doesn't exist, feature request needed script.triggerControl(this.group, right ? "track_color_prev" : "track_color_next"); } else { script.triggerControl(this.group, right ? "stars_up" : "stars_down"); } - } else if (this.gridButtonPressed) { + } else if (this.deck.gridButton.pressed) { script.triggerControl(this.group, right ? "waveform_zoom_up" : "waveform_zoom_down"); - } else if (this.libraryPlayButtonPressed) { + } else if (this.deck.libraryPlayButton.pressed) { script.triggerControl("[PreviewDeck1]", right ? "beatjump_16_forward" : "beatjump_16_backward"); } else { // FIXME there is a bug where this action has no effect when the Mixxx window has no focused. https://github.com/mixxxdj/mixxx/issues/11285 @@ -2031,15 +2476,15 @@ class S4Mk3Deck extends Deck { } } else { engine.setValue("[Library]", "focused_widget", this.shifted ? 2 : 3); - engine.setValue("[Library]", "MoveVertical", right ? 1 : -1); + engine.setValue("[Library]", this.deck.turntableButton.pressed ? "ScrollVertical" : "MoveVertical", right ? 1 : -1); } } } }); this.libraryEncoderPress = new Button({ - libraryViewButtonPressed: false, + deck: this, onShortPress: function() { - if (this.libraryViewButtonPressed) { + if (this.deck.libraryViewButton.pressed) { script.toggleControl("[Library]", "sort_order"); } else { const currentlyFocusWidget = engine.getValue("[Library]", "focused_widget"); @@ -2047,7 +2492,11 @@ class S4Mk3Deck extends Deck { if (this.shifted && currentlyFocusWidget === 0) { script.triggerControl("[Playlist]", "ToggleSelectedSidebarItem"); } else if (currentlyFocusWidget === 3 || currentlyFocusWidget === 0) { - script.triggerControl(this.group, "LoadSelectedTrack"); + if (this.deck.hasSelectedStem()) { + engine.setValue(this.group, "load_selected_track_stems", this.deck.stemSelection()); + } else { + script.triggerControl(this.group, "LoadSelectedTrack"); + } } else { script.triggerControl("[Library]", "GoToItem"); } @@ -2060,15 +2509,17 @@ class S4Mk3Deck extends Deck { }); this.libraryPlayButton = new PushButton({ group: "[PreviewDeck1]", - libraryEncoder: this.libraryEncoder, - input: function(pressed) { - if (pressed) { - script.triggerControl(this.group, "LoadSelectedTrackAndPlay"); + deck: this, + onPress: function() { + if (this.shifted) { + engine.setValue(this.group, "CloneFromDeck", this.deck.currentDeckNumber); } else { - engine.setValue(this.group, "play", 0); - script.triggerControl(this.group, "eject"); + script.triggerControl(this.group, "LoadSelectedTrackAndPlay"); } - this.libraryEncoder.libraryPlayButtonPressed = pressed; + }, + onRelease: function() { + engine.setValue(this.group, "play", 0); + script.triggerControl(this.group, "eject"); }, outKey: "play", }); @@ -2078,12 +2529,6 @@ class S4Mk3Deck extends Deck { onShortRelease: function() { script.triggerControl(this.group, this.shifted ? "track_color_prev" : "track_color_next"); }, - onLongPress: function() { - this.libraryEncoder.starButtonPressed = true; - }, - onLongRelease: function() { - this.libraryEncoder.starButtonPressed = false; - }, }); // FIXME there is no feature about playlist at the moment, so we use this button to control the context menu, which has playlist control this.libraryPlaylistButton = new Button({ @@ -2096,7 +2541,7 @@ class S4Mk3Deck extends Deck { }); // This is useful for case where effect would have been fully disabled in Mixxx. This appears to be the case during unit tests. if (connection) { - this.outConnections[0] = connection; + this.outConnections.push(connection); } else { console.warn(`Unable to connect ${this.group}.focused_widget' to the controller output. The control appears to be unavailable.`); } @@ -2109,18 +2554,11 @@ class S4Mk3Deck extends Deck { return; } script.toggleControl("[Library]", "show_track_menu"); - this.libraryEncoder.libraryPlayButtonPressed = false; if (currentlyFocusWidget === 4) { engine.setValue("[Library]", "focused_widget", 3); } }, - onShortPress: function() { - this.libraryEncoder.libraryPlayButtonPressed = true; - }, - onLongRelease: function() { - this.libraryEncoder.libraryPlayButtonPressed = false; - }, onLongPress: function() { engine.setValue("[Library]", "clear_search", 1); } @@ -2133,14 +2571,7 @@ class S4Mk3Deck extends Deck { onShortRelease: function() { script.toggleControl(this.group, this.inKey, true); }, - onLongPress: function() { - this.libraryEncoder.libraryViewButtonPressed = true; - this.libraryEncoderPress.libraryViewButtonPressed = true; - }, - onLongRelease: function() { - this.libraryEncoder.libraryViewButtonPressed = false; - this.libraryEncoderPress.libraryViewButtonPressed = false; - } + onLongPress: function() {}, // This is needed to make difference between a shot and long press }); this.keyboardPlayMode = null; @@ -2161,28 +2592,63 @@ class S4Mk3Deck extends Deck { cueBaseName: "outro_end", }), new HotcueButton({ - number: 1 + number: 1, deck: this }), new HotcueButton({ - number: 2 + number: 2, deck: this }), new HotcueButton({ - number: 3 + number: 3, deck: this }), new HotcueButton({ - number: 4 + number: 4, deck: this }) ]; const hotcuePage2 = Array(8).fill({}); const hotcuePage3 = Array(8).fill({}); + const beatJumpPage = Array(8).fill({}); const samplerOrBeatloopRollPage = Array(8).fill({}); - this.keyboard = Array(8).fill({}); + const keyboard = Array(8).fill({}); + const stem = [ + new StemButton({ + number: 1, + deck: this, + }), + new StemButton({ + number: 2, + deck: this, + }), + new StemButton({ + number: 3, + deck: this, + }), + new StemButton({ + number: 4, + deck: this, + }), + new StemMuteButton({ + number: 1, + }), + new StemMuteButton({ + number: 2, + }), + new StemMuteButton({ + number: 3, + }), + new StemMuteButton({ + number: 4, + }), + ]; let i = 0; /* eslint no-unused-vars: "off" */ for (const pad of hotcuePage2) { // start with hotcue 5; hotcues 1-4 are in defaultPadLayer - hotcuePage2[i] = new HotcueButton({number: i + 1}); - hotcuePage3[i] = new HotcueButton({number: i + 13}); + hotcuePage2[i] = new HotcueButton({number: i + 1, deck: this}); + hotcuePage3[i] = new HotcueButton({number: i + 13, deck: this}); + beatJumpPage[i] = new BeatJumpButton({ + number: i, + deck: this, + }); if (UseBeatloopRollInsteadOfSampler) { samplerOrBeatloopRollPage[i] = new BeatLoopRollButton({ number: i, @@ -2199,6 +2665,7 @@ class S4Mk3Deck extends Deck { } samplerOrBeatloopRollPage[i] = new SamplerButton({ number: samplerNumber, + deck: this, }); if (SamplerCrossfaderAssign) { engine.setValue( @@ -2208,7 +2675,7 @@ class S4Mk3Deck extends Deck { ); } } - this.keyboard[i] = new KeyboardButton({ + keyboard[i] = new KeyboardButton({ number: i + 1, deck: this, }); @@ -2217,9 +2684,13 @@ class S4Mk3Deck extends Deck { const switchPadLayer = (deck, newLayer) => { let index = 0; + if (newLayer === samplerOrBeatloopRollPage && deck.hasSelectedStem()) { + deck.samplerStemSelection = deck.stemSelection(); + } for (let pad of deck.pads) { pad.outDisconnect(); pad.inDisconnect(); + const shifted = pad.shifted; pad = newLayer[index]; Object.assign(pad, io.pads[index]); @@ -2233,6 +2704,11 @@ class S4Mk3Deck extends Deck { if (pad.inReport === undefined) { pad.inReport = inReports[1]; } + if (shifted && typeof pad.shift === "function" && pad.shift.length === 0) { + pad.shift(); + } else if (typeof pad.unshift === "function" && pad.unshift.length === 0) { + pad.unshift(); + } pad.outReport = outReport; pad.inConnect(); pad.outConnect(); @@ -2248,18 +2724,24 @@ class S4Mk3Deck extends Deck { hotcuePage3: 2, samplerPage: 3, keyboard: 5, + stem: 6, + beatJump: 6, }; switch (DefaultPadLayout) { case DefaultPadLayoutHotcue: switchPadLayer(this, hotcuePage2); this.currentPadLayer = this.padLayers.hotcuePage2; break; + case DefaultPadLayoutSamplerBeatloop: + switchPadLayer(this, beatJumpPage); + this.currentPadLayer = this.padLayers.beatJump; + break; case DefaultPadLayoutSamplerBeatloop: switchPadLayer(this, samplerOrBeatloopRollPage); this.currentPadLayer = this.padLayers.samplerPage; break; case DefaultPadLayoutKeyboard: - switchPadLayer(this, this.keyboard); + switchPadLayer(this, keyboard); this.currentPadLayer = this.padLayers.keyboard; break; default: @@ -2270,7 +2752,7 @@ class S4Mk3Deck extends Deck { this.hotcuePadModeButton = new Button({ deck: this, - onShortPress: function() { + onShortRelease: function() { if (!this.shifted) { if (this.deck.currentPadLayer !== this.deck.padLayers.hotcuePage2) { switchPadLayer(this.deck, hotcuePage2); @@ -2287,34 +2769,89 @@ class S4Mk3Deck extends Deck { } }, + onShortPress: UseSharedDataAPI ? function() { + const data = engine.getSharedData() || {}; + if (!data.padsMode) { return; } + data.padsMode[this.deck.group] = ActiveTabPadID.hotcue; + engine.setSharedData(data); + } : undefined, + onLongPress: function() { + this.previousMoveMode = this.deck.moveMode; + this.deck.moveMode = moveModes.hotcueColor; + + }, + onLongRelease: function() { + this.deck.moveMode = this.previousMoveMode; + this.previousMoveMode = null; + }, // hack to switch the LED color when changing decks outTrigger: function() { this.deck.lightPadMode(); } }); // The record button doesn't have a mapping by default, but you can add yours here - // this.recordPadModeButton = new Button({ - // ... - // }); + this.recordPadModeButton = new Button({ + deck: this, + onShortPress: UseSharedDataAPI ? function() { + switchPadLayer(this.deck, beatJumpPage); + this.deck.lightPadMode(); + + const data = engine.getSharedData() || {}; + if (!data.padsMode) { return; } + data.padsMode[this.deck.group] = ActiveTabPadID.jump; + engine.setSharedData(data); + this.output(data.scrollingWavefom[this.deck.group]); + } : undefined, + // hack to switch the LED color when changing decks + outTrigger: function() { + this.deck.lightPadMode(); + } + }); this.samplesPadModeButton = new Button({ + pressed: false, deck: this, onShortPress: function() { + engine.setValue(this.deck.group, "loop_anchor", 1); + + if (!UseSharedDataAPI) { + return; + } + const data = engine.getSharedData() || {}; + if (!data.padsMode) { return; } + data.padsMode[this.deck.group] = UseBeatloopRollInsteadOfSampler ? ActiveTabPadID.roll : ActiveTabPadID.samples; + engine.setSharedData(data); + }, + onShortRelease: function() { if (this.deck.currentPadLayer !== this.deck.padLayers.samplerPage) { switchPadLayer(this.deck, samplerOrBeatloopRollPage); - engine.setValue("[Samplers]", "show_samplers", true); + engine.setValue("[Skin]", "show_samplers", true); this.deck.currentPadLayer = this.deck.padLayers.samplerPage; } else { switchPadLayer(this.deck, defaultPadLayer); - engine.setValue("[Samplers]", "show_samplers", false); + engine.setValue("[Skin]", "show_samplers", false); this.deck.currentPadLayer = this.deck.padLayers.defaultLayer; } this.deck.lightPadMode(); + engine.setValue(this.deck.group, "loop_anchor", 0); }, + onLongRelease: function() { + engine.setValue(this.deck.group, "loop_anchor", 0); + } }); // The mute button doesn't have a mapping by default, but you can add yours here - // this.mutePadModeButton = new Button({ - // ... - // }); + this.mutePadModeButton = new Button({ + deck: this, + onShortPress: UseSharedDataAPI ? function() { + const data = engine.getSharedData() || {}; + if (!data.padsMode) { return; } + data.padsMode[this.deck.group] = ActiveTabPadID.mute; + engine.setSharedData(data); + } : undefined, + // hack to switch the LED color when changing decks + outTrigger: function() { + this.deck.lightPadMode(); + } + }); this.stemsPadModeButton = new Button({ deck: this, @@ -2326,6 +2863,12 @@ class S4Mk3Deck extends Deck { } }, onShortPress: function() { + if (UseSharedDataAPI) { + const data = engine.getSharedData() || {}; + if (!data.padsMode) { return; } + data.padsMode[this.deck.group] = ActiveTabPadID.stems; + engine.setSharedData(data); + } if (this.previousMoveMode === null) { this.previousMoveMode = this.deck.moveMode; this.deck.moveMode = moveModes.keyboard; @@ -2336,12 +2879,19 @@ class S4Mk3Deck extends Deck { this.deck.moveMode = this.previousMoveMode; this.previousMoveMode = null; } - if (this.deck.currentPadLayer === this.deck.padLayers.keyboard) { + let targetLayer = this.deck.padLayers.stem; + if (this.shifted) { + targetLayer = this.deck.padLayers.keyboard; + } + if (this.deck.currentPadLayer === targetLayer) { switchPadLayer(this.deck, defaultPadLayer); this.deck.currentPadLayer = this.deck.padLayers.defaultLayer; - } else if (this.deck.currentPadLayer !== this.deck.padLayers.keyboard) { - switchPadLayer(this.deck, this.deck.keyboard); - this.deck.currentPadLayer = this.deck.padLayers.keyboard; + } else if (targetLayer === this.deck.padLayers.stem) { + switchPadLayer(this.deck, stem); + this.deck.currentPadLayer = targetLayer; + } else if (targetLayer === this.deck.padLayers.keyboard) { + switchPadLayer(this.deck, keyboard); + this.deck.currentPadLayer = targetLayer; } this.deck.lightPadMode(); }, @@ -2358,43 +2908,46 @@ class S4Mk3Deck extends Deck { }); this.wheelMode = wheelModes.vinyl; - this.turntableButton = UseMotors ? new Button({ + this.turntableButton = new Button({ deck: this, - input: function(press) { - if (press) { - this.deck.reverseButton.loopModeOff(true); - this.deck.fluxButton.loopModeOff(true); - if (this.deck.wheelMode === wheelModes.motor) { - this.deck.wheelMode = wheelModes.vinyl; - engine.setValue(this.group, "scratch2_enable", false); - } else { - this.deck.wheelMode = wheelModes.motor; - const group = this.group; - } - this.outTrigger(); - } + onShortPress: function() { }, - outTrigger: function() { + onLongRelease: function() { + }, + onShortRelease: UseMotors ? function() { + this.deck.reverseButton.loopModeOff(true); + this.deck.fluxButton.loopModeOff(true); + if (this.deck.wheelMode === wheelModes.motor) { + this.deck.wheelMode = wheelModes.vinyl; + engine.setValue(this.group, "scratch2_enable", false); + } else { + this.deck.wheelMode = wheelModes.motor; + const group = this.group; + engine.beginTimer(MotorWindUpMilliseconds, () => { + engine.setValue(group, "scratch2_enable", true); + }, true); + } + this.outTrigger(); + } : undefined, + outTrigger: UseMotors ? function() { const motorOn = this.deck.wheelMode === wheelModes.motor; this.send(this.color + (motorOn ? this.brightnessOn : this.brightnessOff)); const vinylModeOn = this.deck.wheelMode === wheelModes.vinyl; this.deck.jogButton.send(this.color + (vinylModeOn ? this.brightnessOn : this.brightnessOff)); - }, - }) : undefined; + } : undefined, + }); this.jogButton = new Button({ deck: this, - input: function(press) { - if (press) { - this.deck.reverseButton.loopModeOff(true); - this.deck.fluxButton.loopModeOff(true); - if (this.deck.wheelMode === wheelModes.vinyl) { - this.deck.wheelMode = wheelModes.jog; - } else { - this.deck.wheelMode = wheelModes.vinyl; - } - engine.setValue(this.group, "scratch2_enable", false); - this.outTrigger(); + onPress: function() { + this.deck.reverseButton.loopModeOff(true); + this.deck.fluxButton.loopModeOff(true); + if (this.deck.wheelMode === wheelModes.vinyl) { + this.deck.wheelMode = wheelModes.jog; + } else { + this.deck.wheelMode = wheelModes.vinyl; } + engine.setValue(this.group, "scratch2_enable", false); + this.outTrigger(); }, outTrigger: function() { const vinylOn = this.deck.wheelMode === wheelModes.vinyl; @@ -2512,6 +3065,7 @@ class S4Mk3Deck extends Deck { engine.setValue(this.group, "scratch2", this.speed); } else { engine.setValue(this.group, "jog", this.speed); + console.log(this.speed) } break; default: @@ -2582,6 +3136,9 @@ class S4Mk3Deck extends Deck { } }); + this.selectedStem = new Array(4).fill(false); + this.samplerStemSelection = null; + for (const property in this) { if (Object.prototype.hasOwnProperty.call(this, property)) { const component = this[property]; @@ -2611,6 +3168,17 @@ class S4Mk3Deck extends Deck { } } + hasSelectedStem() { + return this.selectedStem.some((stemSelected) => stemSelected); + } + + stemSelection() { + return [...this.selectedStem].reverse().reduce( + (acc, curr) => (curr + acc * 2), + 0, + ); + } + assignKeyboardPlayMode(group, action) { this.keyboardPlayMode = { group: group, @@ -2628,21 +3196,26 @@ class S4Mk3Deck extends Deck { this.hotcuePadModeButton.send(this.hotcuePadModeButton.color + this.hotcuePadModeButton.brightnessOff); } + const data = (UseSharedDataAPI ? engine.getSharedData() : false) || {}; + // unfortunately the other pad mode buttons only have one LED color // const recordPadModeLEDOn = this.currentPadLayer === this.padLayers.hotcuePage3; - // this.recordPadModeButton.send(recordPadModeLEDOn ? 127 : 0); + this.recordPadModeButton.output(data.scrollingWavefom && data.scrollingWavefom[this.group]); const samplesPadModeLEDOn = this.currentPadLayer === this.padLayers.samplerPage; this.samplesPadModeButton.send(samplesPadModeLEDOn ? 127 : 0); // this.mutePadModeButtonLEDOn = this.currentPadLayer === this.padLayers.samplerPage2; - // const mutedModeButton.send(mutePadModeButtonLEDOn ? 127 : 0); + this.mutePadModeButton.output(data.viewArtwork && data.viewArtwork[this.group]); if (this.keyboardPlayMode !== null) { this.stemsPadModeButton.send(LedColors.green + this.stemsPadModeButton.brightnessOn); } else { - const keyboardPadModeLEDOn = this.currentPadLayer === this.padLayers.keyboard; + const keyboardPadModeLEDOn = this.currentPadLayer === this.padLayers.keyboard || this.currentPadLayer === this.padLayers.stem; this.stemsPadModeButton.send(this.stemsPadModeButton.color + (keyboardPadModeLEDOn ? this.stemsPadModeButton.brightnessOn : this.stemsPadModeButton.brightnessOff)); } + if (!UseSharedDataAPI || !data.keyboardMode) { return; } + data.keyboardMode[this.group] = this.currentPadLayer === this.padLayers.keyboard; + engine.setSharedData(data); } } @@ -2750,7 +3323,7 @@ class S4Mk3MixerColumn extends ComponentContainer { inKey: "parameter1", }); this.quickEffectKnob = new Pot({ - group: `[QuickEffectRack1_${this.group}]`, + group: quickFxChannel(this.group), inKey: "super1", }); this.volume = new Pot({ @@ -3121,6 +3694,13 @@ class S4MK3 { wheelLEDinitReport[0] = 1; controller.sendOutputReport(48, wheelLEDinitReport.buffer); + const motorData = new Uint8Array([ + 1, 0x20, 1, 0, 0, + 1, 0x20, 1, 0, 0, + + ]); + controller.sendOutputReport(49, motorData.buffer); + // Init wheel timer data wheelTimer = null; wheelTimerDelta = 0; @@ -3129,6 +3709,72 @@ class S4MK3 { for (const repordId of [0x01, 0x02]) { this.inReports[repordId].handleInput(controller.getInputReport(repordId)); } + + if (!UseSharedDataAPI) { + return; + } + + engine.setSharedData({ + group: { + "leftdeck": "[Channel1]", + "rightdeck": "[Channel2]", + }, + shift: { + "leftdeck": false, + "rightdeck": false, + }, + scrollingWavefom: { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false, + }, + deckColor: { + "[Channel1]": Object.keys(LedColors).indexOf(Object.keys(LedColors).find(key => LedColors[key] === DeckColors[0])) - 1, + "[Channel2]": Object.keys(LedColors).indexOf(Object.keys(LedColors).find(key => LedColors[key] === DeckColors[1])) - 1, + "[Channel3]": Object.keys(LedColors).indexOf(Object.keys(LedColors).find(key => LedColors[key] === DeckColors[2])) - 1, + "[Channel4]": Object.keys(LedColors).indexOf(Object.keys(LedColors).find(key => LedColors[key] === DeckColors[3])) - 1, + }, + rollpadSize: BeatLoopRolls, + beatjumpSize: BeatJumps, + selectedQuickFX: null, + selectedHotcue: { + "[Channel1]": null, + "[Channel2]": null, + "[Channel3]": null, + "[Channel4]": null, + }, + selectedStems: { + "[Channel1]": [0, 0, 0, 0], + "[Channel2]": [0, 0, 0, 0], + "[Channel3]": [0, 0, 0, 0], + "[Channel4]": [0, 0, 0, 0], + }, + viewArtwork: { + "[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, + }, + padsMode: { + "[Channel1]": 0, + "[Channel2]": 0, + "[Channel3]": 0, + "[Channel4]": 0, + }, + }); } shutdown() { // button LEDs diff --git a/res/controllers/TraktorKontrolS4MK3Screens.qml b/res/controllers/TraktorKontrolS4MK3Screens.qml new file mode 100644 index 00000000000..10a2df52539 --- /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 00000000000..b8d6daa2dff --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen.qml @@ -0,0 +1,63 @@ +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 (engine.getSetting("useSharedDataAPI")) { + engine.makeSharedDataConnection(screen.onSharedDataUpdate) + } + } + + 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 00000000000..edd2dea4392 --- /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 00000000000..5e258cd4995 --- /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 00000000000..54e8525fa5e --- /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 00000000000..28dadb00bdb --- /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 00000000000..f6c0213cb83 --- /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 00000000000..7ec1eb1a2f6 --- /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 00000000000..97cbe25ab4d --- /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 00000000000..de1bf4b4739 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/DeckScreen.qml @@ -0,0 +1,184 @@ +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 (engine.getSetting("useSharedDataAPI")) { + engine.makeSharedDataConnection(deckscreen.onSharedDataUpdate) + } + } + + 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 00000000000..fc295f9be47 --- /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 cueColours: [ + red, + darkOrange, + lightOrange, + colorWhite, + yellow, + lime, + green, + mint, + cyan, + turquoise, + blue, + plum, + violet, + purple, + magenta, + fuchsia, + warmYellow + ] + + property variant cueColoursDark: [ + 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 00000000000..d91643a23e3 --- /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 00000000000..833870d29e2 --- /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 00000000000..6bedda675a5 --- /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 00000000000..af590183d93 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Defines/Settings.qml @@ -0,0 +1,288 @@ +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: parseInt(engine.getSetting('bpmOverlayTimer')) || 5000 + + //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 colour 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') || 10000 + + //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 int beatgridVisibility: engine.getSetting('beatgridVisibility') || 25 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////// + //BROWSER SETTINGS// + //////////////////// + + //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 colouring and return to all keys coloured + 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 colour 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 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /////////////////// + //STRIPE SETTINGS// + /////////////////// + + //change to true to hide stripe + readonly property bool hideStripe: engine.getSetting('hideStripe') || false + + //change to false to disable the switching of stripes when holding shift + readonly property bool shiftStripe: engine.getSetting('shiftStripe') || false + + //change to true to show the master deck's stripe when holding shift (shiftStripe must be set to true) + readonly property bool shiftStripeMaster: engine.getSetting('shiftStripeMaster') || 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 timeTextColourChange: engine.getSetting('timeTextColourChange') || 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 phaseAColour: parseInt(engine.getSetting('phaseAColour')) || 3 + readonly property int phaseBColour: parseInt(engine.getSetting('phaseBColour')) || 3 + readonly property int phaseCColour: parseInt(engine.getSetting('phaseCColour')) || 3 + readonly property int phaseDColour: parseInt(engine.getSetting('phaseDColour')) || 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 bool rateAdjustTimer: engine.getSetting('rateAdjustTimer') || 2000 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /////////////////// + //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 colours. + //This will change the cue markers and also the loop indicator. + readonly property int cueCueColour: parseInt(engine.getSetting('cueCueColour')) || 10 + readonly property int cueLoopColour: parseInt(engine.getSetting('cueLoopColour')) || 6 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////// + //EFFECTS SETTINGS// + //////////////////// + + //change to false to disable FX overlays + readonly property bool fxOverlays: engine.getSetting('fxOverlays') || true + + //amount of time the fx overlay will stay on the screen in ms. 1000 = 1 second. + readonly property int fxOverlayTimer: parseInt(engine.getSetting('fxOverlayTimer')) || 2000 + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////// + //EFFECTS PAD 1 SETTINGS// + ////////////////////////// + + //set to true to disable the effects pads 1 overlay appearing + readonly property bool hideEffectsOverlay1: engine.getSetting('hideEffectsOverlay1') || false + + //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('hideEffectsOverlay2') || false + + //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 00000000000..b5f59ac7649 --- /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 00000000000..0104aca5662 --- /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 00000000000..026bca41f4f --- /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 00000000000..dbc47d84145 --- /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 00000000000..5f0d2b10283 --- /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 00000000000..70b52426b30 --- /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 00000000000..0da2209b2ed --- /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 00000000000..4d54ee03c28 --- /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 00000000000..57c4e97d8ad --- /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 00000000000..6b220097ade --- /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 00000000000..ea9f5c29fe7 --- /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 00000000000..12ab2ff7c03 --- /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 00000000000..930ce608e7c --- /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 00000000000..0b85f5b4e98 --- /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 00000000000..0ecebfade34 --- /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 00000000000..0e39735a4f9 --- /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 00000000000..6c001523d69 --- /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: 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: 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: 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: 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 00000000000..92ea2991b01 --- /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 00000000000..7df5c69b37e --- /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 00000000000..f47dc4077cb --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/ViewModels/DeckInfo.qml @@ -0,0 +1,1563 @@ +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 string screenName: isLeftScreen(viewModel.deckId) ? "leftdeck" : "rightdeck" + + function onSharedDataUpdate(data) { + if (typeof data === "object" && typeof data.group[screenName] === "string") { + viewModel.group = data.group[screenName] + console.log(`Changed group for screen ${screenName} to ${viewModel.group}`); + } + if (typeof data === "object" && 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.deckPlayer.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 (engine.getSetting("useSharedDataAPI")) { + engine.makeSharedDataConnection(viewModel.onSharedDataUpdate) + } + } + + 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 deckAColour: propDeckColors.a + readonly property int deckBColour: propDeckColors.b + readonly property int deckCColour: propDeckColors.c + readonly property int deckDColour: 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.deckPlayer ? viewModel.deckPlayer.title : null + } + QtObject { + id: propArtist + property var value: viewModel.deckPlayer ? viewModel.deckPlayer.artist : null + } + 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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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.deckPlayer.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 00000000000..005df81c6fe --- /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 00000000000..3961fc23623 --- /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 00000000000..6436f415333 --- /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 00000000000..bedc4e59451 --- /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 00000000000..5ceca5ff3d5 --- /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 00000000000..e0a26072142 --- /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 00000000000..492312a4200 --- /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.timeTextColourChange && 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.timeTextColourChange && 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.hideStripe ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38) : (!deckInfo.showBPMInfo ? (settings.hideStripe ? content.waveformHeight + display.secondRowHeight-13 : content.waveformHeight) : (settings.hideStripe ? 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.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) || (deckInfo.padsModeFx1 && (settings.fx1unit == 1)) || (deckInfo.padsModeFx2 && (settings.fx2unit == 1)) ? "show" : "hide" + } + + Overlays.TopControls { + id: fx2 + fxUnit: 1 + showHideState: deckInfo.showFx2 && settings.fxOverlays || (deckInfo.padsModeFx1 && (settings.fx1unit == 2)) || (deckInfo.padsModeFx2 && (settings.fx2unit == 2)) ? "show" : "hide" + } + + Overlays.TopControls { + id: fx3 + fxUnit: 2 + showHideState: deckInfo.showFx3 && settings.fxOverlays || (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 || (deckInfo.padsModeFx1 && (!settings.fx1unit == 4)) ||(deckInfo.padsModeFx2 && (settings.fx2unit == 4))) ? "show" : "hide" + } + + Widgets.TempoAdjust { + id: tempoInfo + deckId: deckInfo.deckId + height: 38 + y: settings.hideStripe ? 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 00000000000..89034278cd2 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/StemColorIndicators.qml @@ -0,0 +1,81 @@ +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}]`) + + 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 00000000000..5b7dc361f40 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformContainer.qml @@ -0,0 +1,85 @@ +import QtQuick 2.15 + +import '../Defines' +import '../Widgets' as Widgets +import '../Overlays' as Overlays +import '../ViewModels' as ViewModels + +import "../../../../../qml/" as Skin + +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 + //------------------------------------------------------------------------------------------------------------------ + + Skin.WaveformRow { + id: singleWaveform + group: `[Channel${view.deckId}]` + x: 0 + width: 316 + // height: (settings.alwaysShowTempoInfo || deckInfo.adjustEnabled ? (settings.hideStripe ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38) : (!deckInfo.showBPMInfo ? (settings.hideStripe ? content.waveformHeight + display.secondRowHeight-13 : content.waveformHeight) : (settings.hideStripe ? content.waveformHeight + display.secondRowHeight-51 : content.waveformHeight-38))) + (settings.hidePhase && settings.hidePhrase ? 16 : 0) + (!settings.hidePhase && !settings.hidePhrase ? -16 : 0) + height: view.height + + shader.axesColor: 'transparent' + + zoomControlRatio: 100 + Behavior on height { PropertyAnimation { duration: 90} } + // anchors.left: parent.left + // anchors.top: phase.bottom + } + + //-------------------------------------------------------------------------------------------------------------------- + // 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 00000000000..551f26bf81b --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/AdvancedScreen/Waveform/WaveformOverview.qml @@ -0,0 +1,226 @@ +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 { + id: waveformOverview + anchors.fill: parent + anchors.topMargin: 6 + player: Mixxx.PlayerManager.getPlayer(waveform.group) + } + + 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 00000000000..8ab0a41e58b --- /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 00000000000..da7b88b67c7 --- /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.deckAColour + property int deckB: deckInfo.deckBColour + property int deckC: deckInfo.deckCColour + property int deckD: deckInfo.deckDColour + + 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 00000000000..fd3f3ac53fd --- /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 00000000000..9f5895c3e07 --- /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 loopActiveColour: colors.cueColours[settings.cueLoopColour] + property color loopDimmedColour: colors.cueColoursDark[settings.cueLoopColour] + + Rectangle { + id: loopSizeBackground + width: 40 + height: width + radius: width * 0.5 + opacity: loopActiveBlinkTimer.blink ? 0.25 : 1 + color: deckInfo.loopActive ? (loopActiveBlinkTimer.blink ? loopActiveColour : (settings.loopActiveRedFlash ? colors.colorRed : loopDimmedColour)) + : deckInfo.loopActive ? (deckInfo.shift ? loopDimmedColour : loopActiveColour) + : 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: loopActiveColour + 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 00000000000..e078bd949af --- /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 phaseAColour: settings.phaseAColour + property int phaseBColour: settings.phaseBColour + property int phaseCColour: settings.phaseCColour + property int phaseDColour: settings.phaseDColour + property int deckId: deckInfo.deckId + + property color phaseColor: colorForPhase(deckId == 1 ? phaseAColour : deckId == 2 ? phaseBColour : deckId == 3 ? phaseCColour : phaseDColour) + 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 00000000000..6c8c25dcd4c --- /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 00000000000..23206eeb43c --- /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 00000000000..226c7cf1036 --- /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 00000000000..37c8497e06e --- /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 00000000000..2fd23c0b29c --- /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 00000000000..b3857271a21 --- /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 00000000000..43260bfda89 --- /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 00000000000..f39bdc1ca79 --- /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 00000000000..06006eb8abe --- /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 00000000000..cf5fa1d203c --- /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 00000000000..bd593826869 --- /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 00000000000..0d034066a6e --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/OnAirTrack.qml @@ -0,0 +1,81 @@ +/* +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) + 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.deckPlayer.title.trim().length + root.deckPlayer.artist.trim().length == 0 ? qsTr("No Track Loaded") : `${root.deckPlayer.title} - ${root.deckPlayer.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 00000000000..93bc6f2eb40 --- /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 00000000000..2954d704c83 --- /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 00000000000..91b3820ecc4 --- /dev/null +++ b/res/controllers/TraktorKontrolS4MK3Screens/S4MK3/StockScreen.qml @@ -0,0 +1,498 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 + +import "../../../qml" as Skin +import Mixxx 1.0 as Mixxx + +import S4MK3 as S4MK3 + +Rectangle { + id: root + + required property string group + required property string screenId + + readonly property bool useSharedApi: engine.getSetting("useSharedDataAPI") || false + + 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 (!root.useSharedApi) { + 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.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 + + Skin.WaveformRow { + group: root.group + x: 0 + width: 320 + height: 100 + zoomControlRatio: 200 + } + } + + Mixxx.ControlProxy { + id: deckScratching + + group: root.group + key: "scratch2_enable" + + onValueChanged: { + if (root.useSharedApi) { + 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 { + id: waveformOverview + anchors.fill: parent + player: Mixxx.PlayerManager.getPlayer(root.group) + } + + 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 00000000000..be5d8cdf9a6 --- /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 00000000000..b77b3bc1cb6 --- /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 00000000000..a330672059a --- /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/res/qml/WaveformRow.qml b/res/qml/WaveformRow.qml index be129fc42bf..82a8eb43d14 100644 --- a/res/qml/WaveformRow.qml +++ b/res/qml/WaveformRow.qml @@ -15,6 +15,8 @@ Item { property string group // required property var deckPlayer: Mixxx.PlayerManager.getPlayer(group) + property int zoomControlRatio: 100 + property alias shader: shader Item { id: waveformContainer @@ -118,7 +120,7 @@ Item { Item { id: waveform - property real effectiveZoomFactor: (1 / rateRatioControl.value) * (100 / zoomControl.value) + property real effectiveZoomFactor: (1 / rateRatioControl.value) * (root.zoomControlRatio / zoomControl.value) width: waveformContainer.duration * effectiveZoomFactor height: parent.height @@ -126,6 +128,7 @@ Item { visible: root.deckPlayer.isLoaded WaveformShader { + id: shader group: root.group anchors.fill: parent } diff --git a/src/controllers/bulk/bulksupported.h b/src/controllers/bulk/bulksupported.h index a7bf6c916d6..f10673e87ba 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/tools/README b/tools/README index def2a3d4040..3055fbed113 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 16076470c91..baeface76d2 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 00000000000..cf14fd3833a --- /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; +}