From 53ef29ebf1f9112de498fee7622966591f985ced Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 23 Mar 2024 19:21:03 +0000 Subject: [PATCH 1/2] feat: add setting definition for Traktor S4 MK3 --- .../Traktor Kontrol S4 MK3.hid.xml | 614 ++++++++++++++++++ res/controllers/Traktor-Kontrol-S4-MK3.js | 113 ++-- src/controllers/legacycontrollersettings.cpp | 20 +- 3 files changed, 694 insertions(+), 53 deletions(-) diff --git a/res/controllers/Traktor Kontrol S4 MK3.hid.xml b/res/controllers/Traktor Kontrol S4 MK3.hid.xml index 02b9b5b9363..881d44849aa 100644 --- a/res/controllers/Traktor Kontrol S4 MK3.hid.xml +++ b/res/controllers/Traktor Kontrol S4 MK3.hid.xml @@ -9,6 +9,620 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/controllers/Traktor-Kontrol-S4-MK3.js b/res/controllers/Traktor-Kontrol-S4-MK3.js index 857ff9ff35d..23ecf83b0da 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3.js +++ b/res/controllers/Traktor-Kontrol-S4-MK3.js @@ -40,95 +40,102 @@ const KeyboardColors = [ /* * USER CONFIGURABLE SETTINGS - * Adjust these to your liking + * Change settings in the preferences (Mixxx >= 2.5) */ const DeckColors = [ - LedColors.red, - LedColors.blue, - LedColors.yellow, - LedColors.purple, + LedColors[engine.getSetting("deckA")] || LedColors.red, + LedColors[engine.getSetting("deckB")] || LedColors.blue, + LedColors[engine.getSetting("deckC")] || LedColors.yellow, + LedColors[engine.getSetting("deckD")] || LedColors.purple, ]; const LibrarySortableColumns = [ - script.LIBRARY_COLUMNS.ARTIST, - script.LIBRARY_COLUMNS.TITLE, - script.LIBRARY_COLUMNS.BPM, - script.LIBRARY_COLUMNS.KEY, - script.LIBRARY_COLUMNS.DATETIME_ADDED, -]; + engine.getSetting("librarySortableColumns1Value"), + engine.getSetting("librarySortableColumns2Value"), + engine.getSetting("librarySortableColumns3Value"), + engine.getSetting("librarySortableColumns4Value"), + engine.getSetting("librarySortableColumns5Value"), + engine.getSetting("librarySortableColumns6Value"), +].map(c => parseInt(c)).filter(c => c); // Filter '0' column, equivalent to '---' value in the UI or disabled -const LoopWheelMoveFactor = 50; -const LoopEncoderMoveFactor = 500; -const LoopEncoderShiftmoveFactor = 2500; +const LoopWheelMoveFactor = engine.getSetting("loopWheelMoveFactor") || 50; +const LoopEncoderMoveFactor = engine.getSetting("loopEncoderMoveFactor") || 500; +const LoopEncoderShiftMoveFactor = engine.getSetting("loopEncoderShiftMoveFactor") || 2500; -const TempoFaderSoftTakeoverColorLow = LedColors.white; -const TempoFaderSoftTakeoverColorHigh = LedColors.green; +const TempoFaderSoftTakeoverColorLow = LedColors[engine.getSetting("tempoFaderSoftTakeoverColorLow")] || LedColors.white; +const TempoFaderSoftTakeoverColorHigh = LedColors[engine.getSetting("tempoFaderSoftTakeoverColorHigh")] || LedColors.green; // Define whether or not to keep LED that have only one color (reverse, flux, play, shift) dimmed if they are inactive. // 'true' will keep them dimmed, 'false' will turn them off. Default: true -const KeepLEDWithOneColorDimedWhenInactive = true; +const InactiveLightsAlwaysBacklit = !!engine.getSetting("inactiveLightsAlwaysBacklit"); // Keep both deck select buttons backlit and do not fully turn off the inactive deck button. -// 'true' will keep the unseclected deck dimmed, 'false' to fully turn it off. Default: true -const KeepDeckSelectDimmed = true; +// 'true' will keep the unselected deck dimmed, 'false' to fully turn it off. Default: true +const DeckSelectAlwaysBacklit = !!engine.getSetting("deckSelectAlwaysBacklit"); // Define whether the keylock is mapped when doing "shift+master" (on press) or "shift+sync" (on release since long push copies the key)". // 'true' will use "sync+master", 'false' will use "shift+sync". Default: false -const UseKeylockOnMaster = false; +const UseKeylockOnMaster = !!engine.getSetting("useKeylockOnMaster"); -// Define whether the grid button would blink when the playback is going over a detcted beat. Can help to adjust beat grid. +// Define whether the grid button would blink when the playback is going over a detected beat. Can help to adjust beat grid. // Default: false -const GridButtonBlinkOverBeat = false; +const GridButtonBlinkOverBeat = !!engine.getSetting("gridButtonBlinkOverBeat"); // Wheel led blinking if reaching the end of track warning (default 30 seconds, can be changed in the settings, under "Waveforms" > "End of track warning"). // Default: true -const WheelLedBlinkOnTrackEnd = true; +const WheelLedBlinkOnTrackEnd = !!engine.getSetting("wheelLedBlinkOnTrackEnd"); // When shifting either decks, the mixer will control microphones or auxiliary lines. If there is both a mic and an configure on the same channel, the mixer will control the auxiliary. // Default: false -const MixerControlsMixAuxOnShift = false; +const MixerControlsMixAuxOnShift = !!engine.getSetting("mixerControlsMixAuxOnShift"); // Define how many wheel moves are sampled to compute the speed. The more you have, the more the speed is accurate, but the // less responsive it gets in Mixxx. Default: 5 -const WheelSpeedSample = 3; +const WheelSpeedSample = engine.getSetting("wheelSpeedSample") || 5; // Make the sampler tab a beatlooproll tab instead // Default: false -const UseBeatloopRollInsteadOfSampler = false; +const UseBeatloopRollInsteadOfSampler = !!engine.getSetting("useBeatloopRollInsteadOfSampler"); // Predefined beatlooproll sizes. Note that if you use AddLoopHalveAndDoubleOnBeatloopRollTab, the first and // last size will be ignored -const BeatLoopRolls = [1/16, 1/8, 1/4, 1/2, 1, 2, 4, 8]; +const BeatLoopRolls = [ + engine.getSetting("beatLoopRollsSize1") || 1/8, + engine.getSetting("beatLoopRollsSize2") || 1/4, + engine.getSetting("beatLoopRollsSize3") || 1/2, + engine.getSetting("beatLoopRollsSize4") || 1, + engine.getSetting("beatLoopRollsSize5") || 2, + engine.getSetting("beatLoopRollsSize6") || 4, + engine.getSetting("beatLoopRollsSize7") || "half", + engine.getSetting("beatLoopRollsSize8") || "double" +]; -// Make the two last button on the beatlooproll pad halve or double the loop size. This will take away the 1/16 and 8 loop size. -// Default: true -const AddLoopHalveAndDoubleOnBeatloopRollTab = true; // Define the speed of the jogwheel. This will impact the speed of the LED playback indicator, the sratch, and the speed of // the motor if enable. Recommended value are 33 + 1/3 or 45. // Default: 33 + 1/3 -const BaseRevolutionsPerMinute = 33 + 1/3; +const BaseRevolutionsPerMinute = engine.getSetting("baseRevolutionsPerMinute") || 33 + 1/3; // Define whether or not to use motors. // This is a BETA feature! Please use at your own risk. Setting this off means that below settings are inactive // Default: false -const UseMotors = false; +const UseMotors = !!engine.getSetting("useMotors"); // Define how many wheel moves are sampled to compute the speed when using the motor. This is helpful to mitigate delay that // occurs in communication as well as Mixxx limitation to 20ms latency. // The more you have, the more the speed is accurate. // less responsive it gets in Mixxx. Default: 20 -const TurnTableSpeedSample = 20; +const TurnTableSpeedSample = engine.getSetting("turnTableSpeedSample") || 20; // Define how much the wheel will resist. It is a similar setting that the Grid+Wheel in Tracktor // Value must defined between 0 to 1. 0 is very tight, 1 is very loose. // Default: 0.5 -const TightnessFactor = 0.5; +const TightnessFactor = engine.getSetting("tightnessFactor") || 0.5; // Define how much force can the motor use. This defines how much the wheel will "fight" you when you block it in TT mode // This will also affect how quick the wheel starts spinning when enabling motor mode, or starting a deck with motor mode on -const MaxWheelForce = 25000; // Traktor seems to cap the max value at 60000, which just sounds insane +const MaxWheelForce = engine.getSetting("maxWheelForce") || 25000; // Traktor seems to cap the max value at 60000, which just sounds insane @@ -699,7 +706,7 @@ class HotcueButton extends PushButton { if (this.number === undefined || !Number.isInteger(this.number) || this.number < 1 || this.number > 32) { throw Error("HotcueButton must have a number property of an integer between 1 and 32"); } - this.outKey = `hotcue_${this.number}_enabled`; + this.outKey = `hotcue_${this.number}_status`; this.colorKey = `hotcue_${this.number}_color`; this.outConnect(); } @@ -815,8 +822,16 @@ class BeatLoopRollButton extends TriggerButton { if (options.number === undefined || !Number.isInteger(options.number) || options.number < 0 || options.number > 7) { throw Error("BeatLoopRollButton must have a number property of an integer between 0 and 7"); } - if (options.number <= 5 || !AddLoopHalveAndDoubleOnBeatloopRollTab) { - options.key = "beatlooproll_"+BeatLoopRolls[AddLoopHalveAndDoubleOnBeatloopRollTab ? options.number + 1 : options.number]+"_activate"; + if (BeatLoopRolls[options.number] === "half") { + options.key = "loop_halve"; + } else if (BeatLoopRolls[options.number] === "double") { + options.key = "loop_double"; + } else { + const size = parseFloat(BeatLoopRolls[options.number]); + if (isNaN(size)) { + throw Error(`BeatLoopRollButton ${options.number}'s size "${BeatLoopRolls[options.number]}" is invalid. Must be a float, or the literal 'half' or 'double'`); + } + options.key = `beatlooproll_${size}_activate`; options.onShortPress = function() { if (!this.deck.beatloopSize) { this.deck.beatloopSize = engine.getValue(this.group, "beatloop_size"); @@ -830,10 +845,6 @@ class BeatLoopRollButton extends TriggerButton { this.deck.beatloopSize = undefined; } }; - } else if (options.number === 6) { - options.key = "loop_halve"; - } else { - options.key = "loop_double"; } super(options); if (this.deck === undefined) { @@ -843,7 +854,7 @@ class BeatLoopRollButton extends TriggerButton { this.outConnect(); } output(value) { - if (this.number <= 5 || !AddLoopHalveAndDoubleOnBeatloopRollTab) { + if (this.key.startsWith("beatlooproll_")) { this.send(LedColors.white + (value ? this.brightnessOn : this.brightnessOff)); } else { this.send(this.color); @@ -1522,7 +1533,7 @@ class S4Mk3Deck extends Deck { super(decks, colors); this.playButton = new PlayButton({ - output: KeepLEDWithOneColorDimedWhenInactive ? undefined : Button.prototype.uncoloredOutput + output: InactiveLightsAlwaysBacklit ? undefined : Button.prototype.uncoloredOutput }); this.cueButton = new CueButton({ @@ -1624,7 +1635,7 @@ class S4Mk3Deck extends Deck { shift: function() { this.setKey("loop_enabled"); }, - output: KeepLEDWithOneColorDimedWhenInactive ? undefined : Button.prototype.uncoloredOutput, + output: InactiveLightsAlwaysBacklit ? undefined : Button.prototype.uncoloredOutput, onShortRelease: function() { if (!this.shifted) { engine.setValue(this.group, this.key, false); @@ -1708,7 +1719,7 @@ class S4Mk3Deck extends Deck { } } }, - output: KeepLEDWithOneColorDimedWhenInactive ? undefined : Button.prototype.uncoloredOutput, + output: InactiveLightsAlwaysBacklit ? undefined : Button.prototype.uncoloredOutput, onShortRelease: function() { if (!this.shifted) { engine.setValue(this.group, this.key, false); @@ -1820,7 +1831,7 @@ class S4Mk3Deck extends Deck { this.deck.switchDeck(Deck.groupForNumber(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] = KeepDeckSelectDimmed ? colors[1] + this.brightnessOff : 0; + this.outReport.data[io.deckButtonOutputByteOffset + 1] = DeckSelectAlwaysBacklit ? colors[1] + this.brightnessOff : 0; this.outReport.send(); } }, @@ -1831,7 +1842,7 @@ class S4Mk3Deck extends Deck { if (value) { this.deck.switchDeck(Deck.groupForNumber(decks[1])); // turn off the other deck selection button's LED - this.outReport.data[io.deckButtonOutputByteOffset] = KeepDeckSelectDimmed ? colors[0] + this.brightnessOff : 0; + this.outReport.data[io.deckButtonOutputByteOffset] = DeckSelectAlwaysBacklit ? colors[0] + this.brightnessOff : 0; this.outReport.data[io.deckButtonOutputByteOffset + 1] = colors[1] + this.brightnessOn; this.outReport.send(); } @@ -1840,12 +1851,12 @@ class S4Mk3Deck extends Deck { // set deck selection button LEDs outReport.data[io.deckButtonOutputByteOffset] = colors[0] + Button.prototype.brightnessOn; - outReport.data[io.deckButtonOutputByteOffset + 1] = KeepDeckSelectDimmed ? colors[1] + Button.prototype.brightnessOff : 0; + outReport.data[io.deckButtonOutputByteOffset + 1] = DeckSelectAlwaysBacklit ? colors[1] + Button.prototype.brightnessOff : 0; outReport.send(); this.shiftButton = new PushButton({ deck: this, - output: KeepLEDWithOneColorDimedWhenInactive ? undefined : Button.prototype.uncoloredOutput, + output: InactiveLightsAlwaysBacklit ? undefined : Button.prototype.uncoloredOutput, unshift: function() { this.output(false); }, @@ -1924,7 +1935,7 @@ class S4Mk3Deck extends Deck { deck: this, onChange: function(right) { if (this.deck.wheelMode === wheelModes.loopIn || this.deck.wheelMode === wheelModes.loopOut) { - const moveFactor = this.shifted ? LoopEncoderShiftmoveFactor : LoopEncoderMoveFactor; + const moveFactor = this.shifted ? LoopEncoderShiftMoveFactor : LoopEncoderMoveFactor; const valueIn = engine.getValue(this.group, "loop_start_position") + (right ? moveFactor : -moveFactor); const valueOut = engine.getValue(this.group, "loop_end_position") + (right ? moveFactor : -moveFactor); engine.setValue(this.group, "loop_start_position", valueIn); diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp index 56f41fcd1f9..bf449a7a849 100644 --- a/src/controllers/legacycontrollersettings.cpp +++ b/src/controllers/legacycontrollersettings.cpp @@ -99,7 +99,9 @@ QWidget* LegacyControllerBooleanSetting::buildWidget( } QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* pParent) { - auto* pCheckBox = new QCheckBox(label(), pParent); + auto pWidget = make_parented(pParent); + + auto* pCheckBox = new QCheckBox(pWidget); pCheckBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); if (m_editedValue) { pCheckBox->setCheckState(Qt::Checked); @@ -118,7 +120,21 @@ QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* pParent) { emit changed(); }); - return pCheckBox; + auto pLabelWidget = make_parented(pWidget); + pLabelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + pLabelWidget->setText(label()); + + QBoxLayout* pLayout = new QHBoxLayout(); + + pLayout->addWidget(pCheckBox); + pLayout->addWidget(pLabelWidget); + + pLayout->setStretch(0, 3); + pLayout->setStretch(1, 1); + + pWidget->setLayout(pLayout); + + return pWidget; } bool LegacyControllerBooleanSetting::match(const QDomElement& element) { From a89e141bed67bc42e2898fa65e57c3ca263ea9b2 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 30 Mar 2024 15:57:36 +0000 Subject: [PATCH 2/2] Add a testcase and addressing couple of nits --- CMakeLists.txt | 1 + .../Traktor Kontrol S4 MK3.hid.xml | 54 ++++++------- res/controllers/Traktor-Kontrol-S4-MK3.js | 4 +- src/controllers/legacycontrollersettings.h | 5 ++ .../controller_columnid_regression_test.cpp | 79 +++++++++++++++++++ 5 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 src/test/controllers/controller_columnid_regression_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 70d4954448f..2530efeff70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2075,6 +2075,7 @@ add_executable(mixxx-test src/test/configobject_test.cpp src/test/controller_mapping_validation_test.cpp src/test/controller_mapping_settings_test.cpp + src/test/controllers/controller_columnid_regression_test.cpp src/test/controllerscriptenginelegacy_test.cpp src/test/controlobjecttest.cpp src/test/controlobjectaliastest.cpp diff --git a/res/controllers/Traktor Kontrol S4 MK3.hid.xml b/res/controllers/Traktor Kontrol S4 MK3.hid.xml index 881d44849aa..0c6f835c972 100644 --- a/res/controllers/Traktor Kontrol S4 MK3.hid.xml +++ b/res/controllers/Traktor Kontrol S4 MK3.hid.xml @@ -191,9 +191,9 @@