From b36c378bc80107d8995afc705272650549a92c95 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 4 Jun 2023 17:56:21 +0200 Subject: [PATCH 01/16] Changed to ES2015 classes, const functions and used JSDoc deprecate syntax Silenced eslint warnings or unused vars --- res/controllers/common-controller-scripts.js | 965 ++++++++++--------- 1 file changed, 495 insertions(+), 470 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index f0d19253ee7b..ec02d4e962e8 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -37,17 +37,18 @@ String.prototype.toInt = function() { * @deprecated Use console.log()/console.warn()/console.debug() instead. */ -var print = function(message) { +// eslint-disable-next-line no-unused-vars +const print = function(message) { console.log(message); }; // eslint-disable-next-line no-unused-vars -var printObject = function(obj, maxdepth) { - print(stringifyObject(obj, maxdepth)); +const printObject = function(obj, maxdepth) { + console.log(stringifyObject(obj, maxdepth)); }; -var stringifyObject = function(obj, maxdepth, checked, prefix) { +const stringifyObject = function(obj, maxdepth, checked, prefix) { if (!maxdepth) { maxdepth = 2; } try { return JSON.stringify(obj, null, maxdepth); @@ -72,7 +73,7 @@ var stringifyObject = function(obj, maxdepth, checked, prefix) { }; -var arrayContains = function(array, elem) { +const arrayContains = function(array, elem) { for (let i = 0; i < array.length; i++) { if (array[i] === elem) { return true; } } @@ -82,7 +83,7 @@ var arrayContains = function(array, elem) { // ----------------- Generic functions --------------------- // eslint-disable-next-line no-unused-vars -var secondstominutes = function(secs) { +const secondstominutes = function(secs) { const m = (secs / 60) | 0; return (m < 10 ? "0" + m : m) @@ -91,7 +92,7 @@ var secondstominutes = function(secs) { }; // eslint-disable-next-line no-unused-vars -var msecondstominutes = function(msecs) { +const msecondstominutes = function(msecs) { const m = (msecs / 60000) | 0; msecs %= 60000; const secs = (msecs / 1000) | 0; @@ -109,14 +110,14 @@ var msecondstominutes = function(msecs) { // Converts an object with "red", "green" and "blue" properties (value range // 0-255) into an RGB color code (e.g. 0xFF0000). // eslint-disable-next-line no-unused-vars -var colorCodeFromObject = function(color) { +const colorCodeFromObject = function(color) { return ((color.red & 0xFF) << 16 | (color.green & 0xFF) << 8 | (color.blue & 0xFF)); }; // Converts an RGB color code (e.g. 0xFF0000) into an object with "red", // "green" and "blue" properties (value range 0-255). // eslint-disable-next-line no-unused-vars -var colorCodeToObject = function(colorCode) { +const colorCodeToObject = function(colorCode) { return { "red": (colorCode >> 16) & 0xFF, "green": (colorCode >> 8) & 0xFF, @@ -124,516 +125,540 @@ var colorCodeToObject = function(colorCode) { }; }; -var script = function() { -}; - -// DEPRECATED -- use script.midiDebug() instead -script.debug = function(channel, control, value, status, group) { - print("Warning: script.debug() is deprecated. Use script.midiDebug() instead."); - script.midiDebug(channel, control, value, status, group); -}; - -// DEPRECATED -- use script.midiPitch() instead -script.pitch = function(LSB, MSB, status) { - print("Warning: script.pitch() is deprecated. Use script.midiPitch() instead."); - return script.midiPitch(LSB, MSB, status); -}; - -// DEPRECATED -- use script.absoluteLin() instead -script.absoluteSlider = function(group, key, value, low, high, min, max) { - print("Warning: script.absoluteSlider() is deprecated. Use engine.setValue(group, key, script.absoluteLin(...)) instead."); - engine.setValue(group, key, script.absoluteLin(value, low, high, min, max)); -}; - -script.midiDebug = function(channel, control, value, status, group) { - print("Script.midiDebug - channel: 0x" + channel.toString(16) + - " control: 0x" + control.toString(16) + " value: 0x" + value.toString(16) + - " status: 0x" + status.toString(16) + " group: " + group); -}; -// Returns the deck number of a "ChannelN" or "SamplerN" group -script.deckFromGroup = function(group) { - let deck = 0; - if (group.substring(2, 8) === "hannel") { - // Extract deck number from the group text - deck = group.substring(8, group.length - 1); - } - /* - else if (group.substring(2,8)=="ampler") { - // Extract sampler number from the group text - deck = group.substring(8,group.length-1); +// @ts-ignore Same identifier for class and instance needed for backward compatibility +class script { + constructor() { + // ----------------- Common regular expressions -------------------------- + this.samplerRegEx = /^\[Sampler(\d+)\]$/; + this.channelRegEx = /^\[Channel(\d+)\]$/; + this.eqRegEx = /^\[EqualizerRack1_(\[.*\])_Effect1\]$/; + this.quickEffectRegEx = /^\[QuickEffectRack1_(\[.*\])\]$/; + this.effectUnitRegEx = /^\[EffectRack1_EffectUnit(\d+)\]$/; + this.individualEffectRegEx = /^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/; + } + + // @deprecated Use script.midiDebug() instead + static debug(channel, control, value, status, group) { + console.log("Warning: script.debug() is deprecated. Use script.midiDebug() instead."); + script.midiDebug(channel, control, value, status, group); + } + // @deprecated Use script.midiPitch() instead + static pitch(LSB, MSB, status) { + console.log("Warning: script.pitch() is deprecated. Use script.midiPitch() instead."); + return script.midiPitch(LSB, MSB, status); + } + // @deprecated Use script.absoluteLin() instead + static absoluteSlider(group, key, value, low, high, min, max) { + console.log("Warning: script.absoluteSlider() is deprecated. Use engine.setValue(group, key, script.absoluteLin(...)) instead."); + engine.setValue(group, key, script.absoluteLin(value, low, high, min, max)); + } + static midiDebug(channel, control, value, status, group) { + console.log("Script.midiDebug - channel: 0x" + channel.toString(16) + + " control: 0x" + control.toString(16) + " value: 0x" + value.toString(16) + + " status: 0x" + status.toString(16) + " group: " + group); + } + // Returns the deck number of a "ChannelN" or "SamplerN" group + static deckFromGroup(group) { + let deck = 0; + if (group.substring(2, 8) === "hannel") { + // Extract deck number from the group text + deck = group.substring(8, group.length - 1); } - */ - return parseInt(deck); -}; - -/* -------- ------------------------------------------------------ - script.bindConnections - Purpose: Binds multiple controls at once. See an example in Pioneer-DDJ-SB-scripts.js - Input: The group whose controls are to be bound and an object - (controlstToFunctions) where the properties' names are - controls names and the values are the functions those - controls will be bound to. - Output: none - -------- ------------------------------------------------------ */ -script.bindConnections = function(group, controlsToFunctions, remove) { - let control; - remove = (remove === undefined) ? false : remove; - - for (control in controlsToFunctions) { - engine.connectControl(group, control, controlsToFunctions[control], remove); - if (!remove) { - engine.trigger(group, control); + /* + else if (group.substring(2,8)=="ampler") { + // Extract sampler number from the group text + deck = group.substring(8,group.length-1); + } + */ + return parseInt(deck); + } + /* -------- ------------------------------------------------------ + script.bindConnections + Purpose: Binds multiple controls at once. See an example in Pioneer-DDJ-SB-scripts.js + Input: The group whose controls are to be bound and an object + (controlstToFunctions) where the properties' names are + controls names and the values are the functions those + controls will be bound to. + Output: none + -------- ------------------------------------------------------ */ + static bindConnections(group, controlsToFunctions, remove) { + let control; + remove = (remove === undefined) ? false : remove; + + for (control in controlsToFunctions) { + engine.connectControl(group, control, controlsToFunctions[control], remove); + if (!remove) { + engine.trigger(group, control); + } } } -}; - -/* -------- ------------------------------------------------------ - script.toggleControl - Purpose: Toggles an engine value - Input: Group and control names - Output: none - -------- ------------------------------------------------------ */ -script.toggleControl = function(group, control) { - engine.setValue(group, control, !(engine.getValue(group, control))); -}; - -/* -------- ------------------------------------------------------ - script.toggleControl - Purpose: Triggers an engine value and resets it back to 0 after a delay - This is helpful for mapping encoder turns to controls that are - represented by buttons in skins so the skin button lights up - briefly but does not stay lit. - Input: Group and control names, delay in milliseconds (optional) - Output: none - -------- ------------------------------------------------------ */ -script.triggerControl = function(group, control, delay) { - if (typeof delay !== "number") { - delay = 200; - } - engine.setValue(group, control, 1); - engine.beginTimer(delay, function() { - engine.setValue(group, control, 0); - }, true); -}; + /* -------- ------------------------------------------------------ + script.toggleControl + Purpose: Toggles an engine value + Input: Group and control names + Output: none + -------- ------------------------------------------------------ */ + static toggleControl(group, control) { + engine.setValue(group, control, !(engine.getValue(group, control))); + } + /* -------- ------------------------------------------------------ + script.toggleControl + Purpose: Triggers an engine value and resets it back to 0 after a delay + This is helpful for mapping encoder turns to controls that are + represented by buttons in skins so the skin button lights up + briefly but does not stay lit. + Input: Group and control names, delay in milliseconds (optional) + Output: none + -------- ------------------------------------------------------ */ + static triggerControl(group, control, delay) { + if (typeof delay !== "number") { + delay = 200; + } + engine.setValue(group, control, 1); + engine.beginTimer(delay, function() { + engine.setValue(group, control, 0); + }, true); + } + /* -------- ------------------------------------------------------ + script.absoluteLin + Purpose: Maps an absolute linear control value to a linear Mixxx control + value (like Volume: 0..1) + Input: Control value (e.g. a knob,) MixxxControl values for the lowest and + highest points, lowest knob value, highest knob value + (Default knob values are standard MIDI 0..127) + Output: MixxxControl value corresponding to the knob position + -------- ------------------------------------------------------ */ + static absoluteLin(value, low, high, min, max) { + if (!min) { + min = 0; + } + if (!max) { + max = 127; + } -/* -------- ------------------------------------------------------ - script.absoluteLin - Purpose: Maps an absolute linear control value to a linear Mixxx control - value (like Volume: 0..1) - Input: Control value (e.g. a knob,) MixxxControl values for the lowest and - highest points, lowest knob value, highest knob value - (Default knob values are standard MIDI 0..127) - Output: MixxxControl value corresponding to the knob position - -------- ------------------------------------------------------ */ -script.absoluteLin = function(value, low, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } - - if (value <= min) { - return low; - } else if (value >= max) { - return high; - } else { - return ((((high - low) / (max - min)) * (value - min)) + low); + if (value <= min) { + return low; + } else if (value >= max) { + return high; + } else { + return ((((high - low) / (max - min)) * (value - min)) + low); + } } -}; - -/* -------- ------------------------------------------------------ - script.absoluteLinInverse - Purpose: Maps a linear Mixxx control value (like balance: -1..1) to an absolute linear value - (inverse of the above function) - Input: Control value (e.g. a knob,) MixxxControl values for the lowest and - highest points, lowest knob value, highest knob value - (Default knob values are standard MIDI 0..127) - Output: Linear value corresponding to the knob position - -------- ------------------------------------------------------ */ -script.absoluteLinInverse = function(value, low, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } - const result = (((value - low) * (max - min)) / (high - low)) + min; - if (result < min) { - return min; - } else if (result > max) { - return max; - } else { - return result; + /* -------- ------------------------------------------------------ + script.absoluteLinInverse + Purpose: Maps a linear Mixxx control value (like balance: -1..1) to an absolute linear value + (inverse of the above function) + Input: Control value (e.g. a knob,) MixxxControl values for the lowest and + highest points, lowest knob value, highest knob value + (Default knob values are standard MIDI 0..127) + Output: Linear value corresponding to the knob position + -------- ------------------------------------------------------ */ + static absoluteLinInverse(value, low, high, min, max) { + if (!min) { + min = 0; + } + if (!max) { + max = 127; + } + const result = (((value - low) * (max - min)) / (high - low)) + min; + if (result < min) { + return min; + } else if (result > max) { + return max; + } else { + return result; + } } -}; + /* -------- ------------------------------------------------------ + script.absoluteNonLin + Purpose: Maps an absolute linear control value to a non-linear Mixxx control + value (like EQs: 0..1..4) + Input: Control value (e.g. a knob,) MixxxControl values for the lowest, + middle, and highest points, lowest knob value, highest knob value + (Default knob values are standard MIDI 0..127) + Output: MixxxControl value corresponding to the knob position + -------- ------------------------------------------------------ */ + static absoluteNonLin(value, low, mid, high, min, max) { + if (!min) { + min = 0; + } + if (!max) { + max = 127; + } + const center = (max - min) / 2; + if (value === center || value === Math.round(center)) { + return mid; + } else if (value < center) { + return low + (value / (center / (mid - low))); + } else { + return mid + ((value - center) / (center / (high - mid))); + } + } + /* -------- ------------------------------------------------------ + script.absoluteNonLinInverse + Purpose: Maps a non-linear Mixxx control to an absolute linear value (inverse of the above function). + Helpful for sending MIDI messages to controllers and comparing non-linear Mixxx controls to incoming MIDI values. + Input: MixxxControl value; lowest, middle, and highest MixxxControl value; + bottom of output range, top of output range. (Default output range is standard MIDI 0..127) + Output: MixxxControl value scaled to output range + -------- ------------------------------------------------------ */ + static absoluteNonLinInverse(value, low, mid, high, min, max) { + if (!min) { + min = 0; + } + if (!max) { + max = 127; + } + const center = (max - min) / 2; + let result; + if (value === mid) { + return center; + } else if (value < mid) { + result = (center / (mid - low)) * (value - low); + } else { + result = center + (center / (high - mid)) * (value - mid); + } -/* -------- ------------------------------------------------------ - script.absoluteNonLin - Purpose: Maps an absolute linear control value to a non-linear Mixxx control - value (like EQs: 0..1..4) - Input: Control value (e.g. a knob,) MixxxControl values for the lowest, - middle, and highest points, lowest knob value, highest knob value - (Default knob values are standard MIDI 0..127) - Output: MixxxControl value corresponding to the knob position - -------- ------------------------------------------------------ */ -script.absoluteNonLin = function(value, low, mid, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } - const center = (max - min) / 2; - if (value === center || value === Math.round(center)) { - return mid; - } else if (value < center) { - return low + (value / (center / (mid - low))); - } else { - return mid + ((value - center) / (center / (high - mid))); + if (result < min) { + return min; + } else if (result > max) { + return max; + } else { + return result; + } } -}; - -/* -------- ------------------------------------------------------ - script.absoluteNonLinInverse - Purpose: Maps a non-linear Mixxx control to an absolute linear value (inverse of the above function). - Helpful for sending MIDI messages to controllers and comparing non-linear Mixxx controls to incoming MIDI values. - Input: MixxxControl value; lowest, middle, and highest MixxxControl value; - bottom of output range, top of output range. (Default output range is standard MIDI 0..127) - Output: MixxxControl value scaled to output range - -------- ------------------------------------------------------ */ -script.absoluteNonLinInverse = function(value, low, mid, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } - const center = (max - min) / 2; - let result; - - if (value === mid) { - return center; - } else if (value < mid) { - result = (center / (mid - low)) * (value - low); - } else { - result = center + (center / (high - mid)) * (value - mid); - } - - if (result < min) { - return min; - } else if (result > max) { - return max; - } else { - return result; + /* -------- ------------------------------------------------------ + script.crossfaderCurve + Purpose: Adjusts the cross-fader's curve using a hardware control + Input: Current value of the hardware control, min and max values for that control + Output: none + -------- ------------------------------------------------------ */ + static crossfaderCurve(value, min, max) { + if (engine.getValue("[Mixer Profile]", "xFaderMode") === 1) { + // Constant Power + engine.setValue("[Mixer Profile]", "xFaderCalibration", + script.absoluteLin(value, 0.5, 0.962, min, max)); + } else { + // Additive + engine.setValue("[Mixer Profile]", "xFaderCurve", + script.absoluteLin(value, 1, 2, min, max)); + } } -}; - -/* -------- ------------------------------------------------------ - script.crossfaderCurve - Purpose: Adjusts the cross-fader's curve using a hardware control - Input: Current value of the hardware control, min and max values for that control - Output: none - -------- ------------------------------------------------------ */ -script.crossfaderCurve = function(value, min, max) { - if (engine.getValue("[Mixer Profile]", "xFaderMode") === 1) { - // Constant Power - engine.setValue("[Mixer Profile]", "xFaderCalibration", - script.absoluteLin(value, 0.5, 0.962, min, max)); - } else { - // Additive - engine.setValue("[Mixer Profile]", "xFaderCurve", - script.absoluteLin(value, 1, 2, min, max)); + /* -------- ------------------------------------------------------ + script.posMod + Purpose: Computes the euclidean modulo of m % n. The result is always + in the range [0, m[ + Input: dividend `a` and divisor `m` for modulo (a % m) + Output: positive remainder + -------- ------------------------------------------------------ */ + static posMod(a, m) { + return ((a % m) + m) % m; + } + /* -------- ------------------------------------------------------ + script.loopMove + Purpose: Moves the current loop by the specified number of beats (default 1/2) + in the specified direction (positive is forwards and is the default) + If the current loop length is shorter than the requested move distance, + it's only moved a distance equal to its length. + Input: MixxxControl group, direction to move, number of beats to move + Output: none + -------- ------------------------------------------------------ */ + static loopMove(group, direction, numberOfBeats) { + if (!numberOfBeats || numberOfBeats === 0) { numberOfBeats = 0.5; } + + if (direction < 0) { + engine.setValue(group, "loop_move", -numberOfBeats); + } else { + engine.setValue(group, "loop_move", numberOfBeats); + } } -}; + /* -------- ------------------------------------------------------ + script.midiPitch + Purpose: Takes the value from a little-endian 14-bit MIDI pitch + wheel message and returns the value for a "rate" (pitch + slider) Mixxx control + Input: Least significant byte, most sig. byte, MIDI status byte + Output: Value for a "rate" control, or false if the input MIDI + message was not a Pitch message (0xE#) + -------- ------------------------------------------------------ */ + // TODO: Is this still useful now that MidiController.cpp properly handles these? + static midiPitch(LSB, MSB, status) { + if ((status & 0xF0) !== 0xE0) { // Mask the upper nybble so we can check the opcode regardless of the channel + console.log("Script.midiPitch: Error, not a MIDI pitch (0xEn) message: " + status); + return false; + } + const value = (MSB << 7) | LSB; // Construct the 14-bit number + + // Range is 0x0000..0x3FFF center @ 0x2000, i.e. 0..16383 center @ 8192 + const rate = (value - 8192) / 8191; + // print("Script.Pitch: MSB="+MSB+", LSB="+LSB+", value="+value+", rate="+rate); + return rate; + } + /* -------- ------------------------------------------------------ + script.spinback + Purpose: wrapper around engine.spinback() that can be directly mapped + from xml for a spinback effect + e.g: script.spinback + Input: channel, control, value, status, group, factor (optional), start rate (optional) + Output: none + -------- ------------------------------------------------------ */ + static spinback(channel, control, value, status, group, factor, rate) { + // if brake is called without defined factor and rate, reset to defaults + if (factor === undefined) { + factor = 1; + } + // if brake is called without defined rate, reset to default + if (rate === undefined) { + rate = -10; + } + // disable on note-off or zero value note/cc + engine.spinback( + parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), + factor, rate); + } + /* -------- ------------------------------------------------------ + script.brake + Purpose: wrapper around engine.brake() that can be directly mapped + from xml for a brake effect + e.g: script.brake + Input: channel, control, value, status, group, factor (optional) + Output: none + -------- ------------------------------------------------------ */ + static brake(channel, control, value, status, group, factor) { + // if brake is called without factor defined, reset to default + if (factor === undefined) { + factor = 1; + } + // disable on note-off or zero value note/cc, use default decay rate '1' + engine.brake( + parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), + factor); + } + /* -------- ------------------------------------------------------ + script.softStart + Purpose: wrapper around engine.softStart() that can be directly mapped + from xml to start and accelerate a deck from zero to full rate + defined by pitch slider, can also interrupt engine.brake() + e.g: script.softStart + Input: channel, control, value, status, group, acceleration factor (optional) + Output: none + -------- ------------------------------------------------------ */ + static softStart(channel, control, value, status, group, factor) { + // if softStart is called without factor defined, reset to default + if (factor === undefined) { + factor = 1; + } + // disable on note-off or zero value note/cc, use default increase rate '1' + engine.softStart( + parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), + factor); + } +} +// Add class script to the Global JavaScript object +// @ts-ignore Same identifier for class and instance needed for backward compatibility +this.script = script; -/* -------- ------------------------------------------------------ - script.posMod - Purpose: Computes the euclidean modulo of m % n. The result is always - in the range [0, m[ - Input: dividend `a` and divisor `m` for modulo (a % m) - Output: positive remainder - -------- ------------------------------------------------------ */ -script.posMod = function(a, m) { - return ((a % m) + m) % m; -}; -/* -------- ------------------------------------------------------ - script.loopMove - Purpose: Moves the current loop by the specified number of beats (default 1/2) - in the specified direction (positive is forwards and is the default) - If the current loop length is shorter than the requested move distance, - it's only moved a distance equal to its length. - Input: MixxxControl group, direction to move, number of beats to move - Output: none - -------- ------------------------------------------------------ */ -script.loopMove = function(group, direction, numberOfBeats) { - if (!numberOfBeats || numberOfBeats === 0) { numberOfBeats = 0.5; } - - if (direction < 0) { - engine.setValue(group, "loop_move", -numberOfBeats); - } else { - engine.setValue(group, "loop_move", numberOfBeats); - } -}; -/* -------- ------------------------------------------------------ - script.midiPitch - Purpose: Takes the value from a little-endian 14-bit MIDI pitch - wheel message and returns the value for a "rate" (pitch - slider) Mixxx control - Input: Least significant byte, most sig. byte, MIDI status byte - Output: Value for a "rate" control, or false if the input MIDI - message was not a Pitch message (0xE#) - -------- ------------------------------------------------------ */ -// TODO: Is this still useful now that MidiController.cpp properly handles these? -script.midiPitch = function(LSB, MSB, status) { - if ((status & 0xF0) !== 0xE0) { // Mask the upper nybble so we can check the opcode regardless of the channel - print("Script.midiPitch: Error, not a MIDI pitch (0xEn) message: " + status); - return false; - } - const value = (MSB << 7) | LSB; // Construct the 14-bit number - // Range is 0x0000..0x3FFF center @ 0x2000, i.e. 0..16383 center @ 8192 - const rate = (value - 8192) / 8191; - // print("Script.Pitch: MSB="+MSB+", LSB="+LSB+", value="+value+", rate="+rate); - return rate; -}; +// bpm - Used for tapping the desired BPM for a deck -/* -------- ------------------------------------------------------ - script.spinback - Purpose: wrapper around engine.spinback() that can be directly mapped - from xml for a spinback effect - e.g: script.spinback - Input: channel, control, value, status, group, factor (optional), start rate (optional) - Output: none - -------- ------------------------------------------------------ */ -script.spinback = function(channel, control, value, status, group, factor, rate) { - // if brake is called without defined factor and rate, reset to defaults - if (factor === undefined) { - factor = 1; - } - // if brake is called without defined rate, reset to default - if (rate === undefined) { - rate = -10; - } - // disable on note-off or zero value note/cc - engine.spinback( - parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), - factor, rate); -}; -/* -------- ------------------------------------------------------ - script.brake - Purpose: wrapper around engine.brake() that can be directly mapped - from xml for a brake effect - e.g: script.brake - Input: channel, control, value, status, group, factor (optional) - Output: none - -------- ------------------------------------------------------ */ -script.brake = function(channel, control, value, status, group, factor) { - // if brake is called without factor defined, reset to default - if (factor === undefined) { - factor = 1; - } - // disable on note-off or zero value note/cc, use default decay rate '1' - engine.brake( - parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), - factor); -}; +// @ts-ignore Same identifier for class and instance needed for backward compatibility +class bpm { + constructor() { + this.tapTime = 0.0; + this.previousTapDelta = 0.0; + this.tap = []; // Tap sample values + } + + + /* -------- ------------------------------------------------------ + this.tapButton + Purpose: Sets the tempo of the track on a deck by tapping the desired beats, + useful for manually synchronizing a track to an external beat. + (This only works if the track's detected BPM value is correct.) + Call this each time the tap button is pressed. + Input: Mixxx deck to adjust + Output: - + -------- ------------------------------------------------------ */ + static tapButton(deck) { + const now = new Date() / 1000; // Current time in seconds + const tapDelta = now - this.tapTime; + this.tapTime = now; + + // assign tapDelta in cases where the button has not been pressed previously + if (this.tap.length < 1) { + this.previousTapDelta = tapDelta; + } + // reset if longer than two seconds between taps + if (tapDelta > 2.0) { + this.tap = []; + return; + } + // reject occurrences of accidental double or missed taps + // a tap is considered missed when the delta of this press is 80% longer than the previous one + // and a tap is considered double when the delta is shorter than 40% of the previous one. + // these numbers are just guesses that produced good results in practice + if ((tapDelta > this.previousTapDelta * 1.8) || (tapDelta < this.previousTapDelta * 0.6)) { + return; + } + this.previousTapDelta = tapDelta; + this.tap.push(60 / tapDelta); + // Keep the last 8 samples for averaging + if (this.tap.length > 8) { this.tap.shift(); } + let sum = 0; + for (let i = 0; i < this.tap.length; i++) { + sum += this.tap[i]; + } + const average = sum / this.tap.length; -/* -------- ------------------------------------------------------ - script.softStart - Purpose: wrapper around engine.softStart() that can be directly mapped - from xml to start and accelerate a deck from zero to full rate - defined by pitch slider, can also interrupt engine.brake() - e.g: script.softStart - Input: channel, control, value, status, group, acceleration factor (optional) - Output: none - -------- ------------------------------------------------------ */ -script.softStart = function(channel, control, value, status, group, factor) { - // if softStart is called without factor defined, reset to default - if (factor === undefined) { - factor = 1; - } - // disable on note-off or zero value note/cc, use default increase rate '1' - engine.softStart( - parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), - factor); -}; + const group = "[Channel" + deck + "]"; -// bpm - Used for tapping the desired BPM for a deck + // "bpm" was changed in 1.10 to reflect the *adjusted* bpm, but I presume it + // was supposed to return the tracks bpm (which it did before the change). + // "file_bpm" is supposed to return the set BPM of the loaded track of the + // channel. + let fRateScale = average / engine.getValue(group, "file_bpm"); -var bpm = function() { -}; + // Adjust the rate: + fRateScale = (fRateScale - 1.) / engine.getValue(group, "rateRange"); + + engine.setValue( + group, "rate", + fRateScale * engine.getValue(group, "rate_dir")); + } +} +// Add class bpm to the Global JavaScript object +// @ts-ignore Same identifier for class and instance needed for backward compatibility +this.bpm = bpm; -bpm.tapTime = 0.0; -bpm.previousTapDelta = 0.0; -bpm.tap = []; // Tap sample values - -/* -------- ------------------------------------------------------ - bpm.tapButton - Purpose: Sets the tempo of the track on a deck by tapping the desired beats, - useful for manually synchronizing a track to an external beat. - (This only works if the track's detected BPM value is correct.) - Call this each time the tap button is pressed. - Input: Mixxx deck to adjust - Output: - - -------- ------------------------------------------------------ */ -bpm.tapButton = function(deck) { - const now = new Date() / 1000; // Current time in seconds - const tapDelta = now - bpm.tapTime; - bpm.tapTime = now; - - // assign tapDelta in cases where the button has not been pressed previously - if (bpm.tap.length < 1) { - bpm.previousTapDelta = tapDelta; - } - // reset if longer than two seconds between taps - if (tapDelta > 2.0) { - bpm.tap = []; - return; - } - // reject occurrences of accidental double or missed taps - // a tap is considered missed when the delta of this press is 80% longer than the previous one - // and a tap is considered double when the delta is shorter than 40% of the previous one. - // these numbers are just guesses that produced good results in practice - if ((tapDelta > bpm.previousTapDelta * 1.8)||(tapDelta < bpm.previousTapDelta * 0.6)) { - return; - } - bpm.previousTapDelta = tapDelta; - bpm.tap.push(60 / tapDelta); - // Keep the last 8 samples for averaging - if (bpm.tap.length > 8) { bpm.tap.shift(); } - let sum = 0; - for (let i=0; i currentRelative - this.maxJump + if (this.softMode) { + const currentValue = engine.getValue(group, this.mappedFunction); + let currentRelative = 0.0; + if (currentValue <= this.midOutput) { + currentRelative = this.minInput + + ((currentValue - this.minOutput) / (this.midOutput - this.minOutput)) + * (this.midInput - this.minInput); + } else { + currentRelative = this.midInput + + ((currentValue - this.midOutput) / (this.maxOutput - this.midOutput)) + * (this.maxInput - this.midInput); + } + if (inputValue > currentRelative - this.maxJump && inputValue < currentRelative + this.maxJump) { + engine.setValue(group, this.mappedFunction, outputValue); + } + } else { engine.setValue(group, this.mappedFunction, outputValue); } - } else { - engine.setValue(group, this.mappedFunction, outputValue); } -}; +} +// Add class Control to the Global JavaScript object +// @ts-ignore Same identifier for class and instance needed for backward compatibility +this.Control = Control; + // Deck -var Deck = function(deckNumber, group) { - this.deckNumber = deckNumber; - this.group = group; - this.Buttons = []; -}; +// @ts-ignore Same identifier for class and instance needed for backward compatibility +class Deck { + constructor(deckNumber, group) { + this.deckNumber = deckNumber; + this.group = group; + this.Buttons = []; + } +} + +// Add class Deck to the Global JavaScript object +// @ts-ignore Same identifier for class and instance needed for backward compatibility +this.Deck = Deck; Deck.prototype.setControlValue = Controller.prototype.setControlValue; Deck.prototype.addButton = Controller.prototype.addButton; From 4b2420d6cc9ad29ddb755890a19121add54527c2 Mon Sep 17 00:00:00 2001 From: Joerg Date: Wed, 8 May 2024 11:24:40 +0200 Subject: [PATCH 02/16] Fixed qt.qml.compiler:warning warning 'Variable "stringifyObject"/"arrayContains" is used before its declaration' by reordering --- res/controllers/common-controller-scripts.js | 28 +++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index d508bfdd15b2..294ec0010ec2 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -42,12 +42,13 @@ const print = function(message) { console.log(message); }; -// eslint-disable-next-line no-unused-vars -const printObject = function(obj, maxdepth) { - console.log(stringifyObject(obj, maxdepth)); +const arrayContains = function(array, elem) { + for (let i = 0; i < array.length; i++) { + if (array[i] === elem) { return true; } + } + return false; }; - const stringifyObject = function(obj, maxdepth, checked, prefix) { if (!maxdepth) { maxdepth = 2; } try { @@ -72,12 +73,9 @@ const stringifyObject = function(obj, maxdepth, checked, prefix) { return obj; }; - -const arrayContains = function(array, elem) { - for (let i = 0; i < array.length; i++) { - if (array[i] === elem) { return true; } - } - return false; +// eslint-disable-next-line no-unused-vars +const printObject = function(obj, maxdepth) { + console.log(stringifyObject(obj, maxdepth)); }; // ----------------- Generic functions --------------------- @@ -155,9 +153,9 @@ class script { engine.setValue(group, key, script.absoluteLin(value, low, high, min, max)); } static midiDebug(channel, control, value, status, group) { - console.log("Script.midiDebug - channel: 0x" + channel.toString(16) + - " control: 0x" + control.toString(16) + " value: 0x" + value.toString(16) + - " status: 0x" + status.toString(16) + " group: " + group); + console.log(`Script.midiDebug - channel: 0x${ channel.toString(16) + } control: 0x${ control.toString(16) } value: 0x${ value.toString(16) + } status: 0x${ status.toString(16) } group: ${ group}`); } // Returns the deck number of a "ChannelN" or "SamplerN" group static deckFromGroup(group) { @@ -385,7 +383,7 @@ class script { // TODO: Is this still useful now that MidiController.cpp properly handles these? static midiPitch(LSB, MSB, status) { if ((status & 0xF0) !== 0xE0) { // Mask the upper nybble so we can check the opcode regardless of the channel - console.log("Script.midiPitch: Error, not a MIDI pitch (0xEn) message: " + status); + console.log(`Script.midiPitch: Error, not a MIDI pitch (0xEn) message: ${ status}`); return false; } const value = (MSB << 7) | LSB; // Construct the 14-bit number @@ -542,7 +540,7 @@ class bpm { } const average = sum / this.tap.length; - const group = "[Channel" + deck + "]"; + const group = `[Channel${ deck }]`; // "bpm" was changed in 1.10 to reflect the *adjusted* bpm, but I presume it // was supposed to return the tracks bpm (which it did before the change). From 412c95f96f28bfbb45b753b00b3f3156c83e738e Mon Sep 17 00:00:00 2001 From: Joerg Date: Wed, 8 May 2024 12:07:00 +0200 Subject: [PATCH 03/16] Made regular expressions proper constants --- res/controllers/common-controller-scripts.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 294ec0010ec2..718de02ebd13 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -127,15 +127,6 @@ const colorCodeToObject = function(colorCode) { // @ts-ignore Same identifier for class and instance needed for backward compatibility class script { - constructor() { - // ----------------- Common regular expressions -------------------------- - this.samplerRegEx = /^\[Sampler(\d+)\]$/; - this.channelRegEx = /^\[Channel(\d+)\]$/; - this.eqRegEx = /^\[EqualizerRack1_(\[.*\])_Effect1\]$/; - this.quickEffectRegEx = /^\[QuickEffectRack1_(\[.*\])\]$/; - this.effectUnitRegEx = /^\[EffectRack1_EffectUnit(\d+)\]$/; - this.individualEffectRegEx = /^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/; - } // @deprecated Use script.midiDebug() instead static debug(channel, control, value, status, group) { @@ -459,6 +450,14 @@ this.script = script; // ----------------- Mapping constants --------------------- +// Common regular expressions +script.samplerRegEx = Object.freeze(/^\[Sampler(\d+)\]$/); +script.channelRegEx = Object.freeze(/^\[Channel(\d+)\]$/); +script.eqRegEx = Object.freeze(/^\[EqualizerRack1_(\[.*\])_Effect1\]$/); +script.quickEffectRegEx = Object.freeze(/^\[QuickEffectRack1_(\[.*\])\]$/); +script.effectUnitRegEx = Object.freeze(/^\[EffectRack1_EffectUnit(\d+)\]$/); +script.individualEffectRegEx = Object.freeze(/^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/); + // Library column value, which can be used to interact with the CO for "[Library] sort_column" script.LIBRARY_COLUMNS = Object.freeze({ ARTIST: 1, From 977b3a6d6344d00c91e142655f65959239fea301 Mon Sep 17 00:00:00 2001 From: Joerg Date: Fri, 24 May 2024 20:16:15 +0200 Subject: [PATCH 04/16] Changed "script" class to less verbose object syntax --- res/controllers/common-controller-scripts.js | 179 ++++++++++--------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 718de02ebd13..913b5026134b 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -123,33 +123,71 @@ const colorCodeToObject = function(colorCode) { }; }; - - -// @ts-ignore Same identifier for class and instance needed for backward compatibility -class script { +const script = { + // ----------------- Mapping script constants --------------------- + + // Common regular expressions + samplerRegEx: Object.freeze(/^\[Sampler(\d+)\]$/), + channelRegEx: Object.freeze(/^\[Channel(\d+)\]$/), + eqRegEx: Object.freeze(/^\[EqualizerRack1_(\[.*\])_Effect1\]$/), + quickEffectRegEx: Object.freeze(/^\[QuickEffectRack1_(\[.*\])\]$/), + effectUnitRegEx: Object.freeze(/^\[EffectRack1_EffectUnit(\d+)\]$/), + individualEffectRegEx: Object.freeze(/^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/), + + // Library column value, which can be used to interact with the CO for "[Library] sort_column" + LIBRARY_COLUMNS: Object.freeze({ + ARTIST: 1, + TITLE: 2, + ALBUM: 3, + ALBUM_ARTIST: 4, + YEAR: 5, + GENRE: 6, + COMPOSER: 7, + GROUPING: 8, + TRACK_NUMBER: 9, + FILETYPE: 10, + NATIVE_LOCATION: 11, + COMMENT: 12, + DURATION: 13, + BITRATE: 14, + BPM: 15, + REPLAY_GAIN: 16, + DATETIME_ADDED: 17, + TIMES_PLAYED: 18, + RATING: 19, + KEY: 20, + PREVIEW: 21, + COVERART: 22, + TRACK_COLOR: 30, + LAST_PLAYED: 31, + }), // @deprecated Use script.midiDebug() instead - static debug(channel, control, value, status, group) { + debug: function(channel, control, value, status, group) { console.log("Warning: script.debug() is deprecated. Use script.midiDebug() instead."); script.midiDebug(channel, control, value, status, group); - } + }, + // @deprecated Use script.midiPitch() instead - static pitch(LSB, MSB, status) { + pitch: function(LSB, MSB, status) { console.log("Warning: script.pitch() is deprecated. Use script.midiPitch() instead."); return script.midiPitch(LSB, MSB, status); - } + }, + // @deprecated Use script.absoluteLin() instead - static absoluteSlider(group, key, value, low, high, min, max) { + absoluteSlider: function(group, key, value, low, high, min, max) { console.log("Warning: script.absoluteSlider() is deprecated. Use engine.setValue(group, key, script.absoluteLin(...)) instead."); engine.setValue(group, key, script.absoluteLin(value, low, high, min, max)); - } - static midiDebug(channel, control, value, status, group) { + }, + + midiDebug: function(channel, control, value, status, group) { console.log(`Script.midiDebug - channel: 0x${ channel.toString(16) } control: 0x${ control.toString(16) } value: 0x${ value.toString(16) } status: 0x${ status.toString(16) } group: ${ group}`); - } + }, + // Returns the deck number of a "ChannelN" or "SamplerN" group - static deckFromGroup(group) { + deckFromGroup: function(group) { let deck = 0; if (group.substring(2, 8) === "hannel") { // Extract deck number from the group text @@ -162,7 +200,8 @@ class script { } */ return parseInt(deck); - } + }, + /* -------- ------------------------------------------------------ script.bindConnections Purpose: Binds multiple controls at once. See an example in Pioneer-DDJ-SB-scripts.js @@ -172,7 +211,7 @@ class script { controls will be bound to. Output: none -------- ------------------------------------------------------ */ - static bindConnections(group, controlsToFunctions, remove) { + bindConnections: function(group, controlsToFunctions, remove) { let control; remove = (remove === undefined) ? false : remove; @@ -182,16 +221,18 @@ class script { engine.trigger(group, control); } } - } + }, + /* -------- ------------------------------------------------------ script.toggleControl Purpose: Toggles an engine value Input: Group and control names Output: none -------- ------------------------------------------------------ */ - static toggleControl(group, control) { + toggleControl: function(group, control) { engine.setValue(group, control, !(engine.getValue(group, control))); - } + }, + /* -------- ------------------------------------------------------ script.toggleControl Purpose: Triggers an engine value and resets it back to 0 after a delay @@ -201,13 +242,14 @@ class script { Input: Group and control names, delay in milliseconds (optional) Output: none -------- ------------------------------------------------------ */ - static triggerControl(group, control, delay) { + triggerControl: function(group, control, delay) { if (typeof delay !== "number") { delay = 200; } engine.setValue(group, control, 1); engine.beginTimer(delay, () => engine.setValue(group, control, 0), true); - } + }, + /* -------- ------------------------------------------------------ script.absoluteLin Purpose: Maps an absolute linear control value to a linear Mixxx control @@ -217,7 +259,7 @@ class script { (Default knob values are standard MIDI 0..127) Output: MixxxControl value corresponding to the knob position -------- ------------------------------------------------------ */ - static absoluteLin(value, low, high, min, max) { + absoluteLin: function(value, low, high, min, max) { if (!min) { min = 0; } @@ -232,7 +274,8 @@ class script { } else { return ((((high - low) / (max - min)) * (value - min)) + low); } - } + }, + /* -------- ------------------------------------------------------ script.absoluteLinInverse Purpose: Maps a linear Mixxx control value (like balance: -1..1) to an absolute linear value @@ -242,7 +285,7 @@ class script { (Default knob values are standard MIDI 0..127) Output: Linear value corresponding to the knob position -------- ------------------------------------------------------ */ - static absoluteLinInverse(value, low, high, min, max) { + absoluteLinInverse: function(value, low, high, min, max) { if (!min) { min = 0; } @@ -257,7 +300,8 @@ class script { } else { return result; } - } + }, + /* -------- ------------------------------------------------------ script.absoluteNonLin Purpose: Maps an absolute linear control value to a non-linear Mixxx control @@ -267,7 +311,7 @@ class script { (Default knob values are standard MIDI 0..127) Output: MixxxControl value corresponding to the knob position -------- ------------------------------------------------------ */ - static absoluteNonLin(value, low, mid, high, min, max) { + absoluteNonLin: function(value, low, mid, high, min, max) { if (!min) { min = 0; } @@ -282,7 +326,8 @@ class script { } else { return mid + ((value - center) / (center / (high - mid))); } - } + }, + /* -------- ------------------------------------------------------ script.absoluteNonLinInverse Purpose: Maps a non-linear Mixxx control to an absolute linear value (inverse of the above function). @@ -291,7 +336,7 @@ class script { bottom of output range, top of output range. (Default output range is standard MIDI 0..127) Output: MixxxControl value scaled to output range -------- ------------------------------------------------------ */ - static absoluteNonLinInverse(value, low, mid, high, min, max) { + absoluteNonLinInverse: function(value, low, mid, high, min, max) { if (!min) { min = 0; } @@ -316,14 +361,15 @@ class script { } else { return result; } - } + }, + /* -------- ------------------------------------------------------ script.crossfaderCurve Purpose: Adjusts the cross-fader's curve using a hardware control Input: Current value of the hardware control, min and max values for that control Output: none -------- ------------------------------------------------------ */ - static crossfaderCurve(value, min, max) { + crossfaderCurve: function(value, min, max) { if (engine.getValue("[Mixer Profile]", "xFaderMode") === 1) { // Constant Power engine.setValue("[Mixer Profile]", "xFaderCalibration", @@ -333,7 +379,8 @@ class script { engine.setValue("[Mixer Profile]", "xFaderCurve", script.absoluteLin(value, 1, 2, min, max)); } - } + }, + /* -------- ------------------------------------------------------ script.posMod Purpose: Computes the euclidean modulo of m % n. The result is always @@ -341,9 +388,10 @@ class script { Input: dividend `a` and divisor `m` for modulo (a % m) Output: positive remainder -------- ------------------------------------------------------ */ - static posMod(a, m) { + posMod: function(a, m) { return ((a % m) + m) % m; - } + }, + /* -------- ------------------------------------------------------ script.loopMove Purpose: Moves the current loop by the specified number of beats (default 1/2) @@ -353,7 +401,7 @@ class script { Input: MixxxControl group, direction to move, number of beats to move Output: none -------- ------------------------------------------------------ */ - static loopMove(group, direction, numberOfBeats) { + loopMove: function(group, direction, numberOfBeats) { if (!numberOfBeats || numberOfBeats === 0) { numberOfBeats = 0.5; } if (direction < 0) { @@ -361,7 +409,8 @@ class script { } else { engine.setValue(group, "loop_move", numberOfBeats); } - } + }, + /* -------- ------------------------------------------------------ script.midiPitch Purpose: Takes the value from a little-endian 14-bit MIDI pitch @@ -372,7 +421,7 @@ class script { message was not a Pitch message (0xE#) -------- ------------------------------------------------------ */ // TODO: Is this still useful now that MidiController.cpp properly handles these? - static midiPitch(LSB, MSB, status) { + midiPitch: function(LSB, MSB, status) { if ((status & 0xF0) !== 0xE0) { // Mask the upper nybble so we can check the opcode regardless of the channel console.log(`Script.midiPitch: Error, not a MIDI pitch (0xEn) message: ${ status}`); return false; @@ -383,7 +432,7 @@ class script { const rate = (value - 8192) / 8191; // print("Script.Pitch: MSB="+MSB+", LSB="+LSB+", value="+value+", rate="+rate); return rate; - } + }, /* -------- ------------------------------------------------------ script.spinback Purpose: wrapper around engine.spinback() that can be directly mapped @@ -392,7 +441,7 @@ class script { Input: channel, control, value, status, group, factor (optional), start rate (optional) Output: none -------- ------------------------------------------------------ */ - static spinback(channel, control, value, status, group, factor, rate) { + spinback: function(channel, control, value, status, group, factor, rate) { // if brake is called without defined factor and rate, reset to defaults if (factor === undefined) { factor = 1; @@ -405,7 +454,8 @@ class script { engine.spinback( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), factor, rate); - } + }, + /* -------- ------------------------------------------------------ script.brake Purpose: wrapper around engine.brake() that can be directly mapped @@ -414,7 +464,7 @@ class script { Input: channel, control, value, status, group, factor (optional) Output: none -------- ------------------------------------------------------ */ - static brake(channel, control, value, status, group, factor) { + brake: function(channel, control, value, status, group, factor) { // if brake is called without factor defined, reset to default if (factor === undefined) { factor = 1; @@ -423,7 +473,8 @@ class script { engine.brake( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), factor); - } + }, + /* -------- ------------------------------------------------------ script.softStart Purpose: wrapper around engine.softStart() that can be directly mapped @@ -433,7 +484,7 @@ class script { Input: channel, control, value, status, group, acceleration factor (optional) Output: none -------- ------------------------------------------------------ */ - static softStart(channel, control, value, status, group, factor) { + softStart: function(channel, control, value, status, group, factor) { // if softStart is called without factor defined, reset to default if (factor === undefined) { factor = 1; @@ -443,48 +494,8 @@ class script { parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), factor); } -} -// Add class script to the Global JavaScript object -// @ts-ignore Same identifier for class and instance needed for backward compatibility -this.script = script; - -// ----------------- Mapping constants --------------------- - -// Common regular expressions -script.samplerRegEx = Object.freeze(/^\[Sampler(\d+)\]$/); -script.channelRegEx = Object.freeze(/^\[Channel(\d+)\]$/); -script.eqRegEx = Object.freeze(/^\[EqualizerRack1_(\[.*\])_Effect1\]$/); -script.quickEffectRegEx = Object.freeze(/^\[QuickEffectRack1_(\[.*\])\]$/); -script.effectUnitRegEx = Object.freeze(/^\[EffectRack1_EffectUnit(\d+)\]$/); -script.individualEffectRegEx = Object.freeze(/^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/); - -// Library column value, which can be used to interact with the CO for "[Library] sort_column" -script.LIBRARY_COLUMNS = Object.freeze({ - ARTIST: 1, - TITLE: 2, - ALBUM: 3, - ALBUM_ARTIST: 4, - YEAR: 5, - GENRE: 6, - COMPOSER: 7, - GROUPING: 8, - TRACK_NUMBER: 9, - FILETYPE: 10, - NATIVE_LOCATION: 11, - COMMENT: 12, - DURATION: 13, - BITRATE: 14, - BPM: 15, - REPLAY_GAIN: 16, - DATETIME_ADDED: 17, - TIMES_PLAYED: 18, - RATING: 19, - KEY: 20, - PREVIEW: 21, - COVERART: 22, - TRACK_COLOR: 30, - LAST_PLAYED: 31, -}); +}; + // bpm - Used for tapping the desired BPM for a deck @@ -508,7 +519,7 @@ class bpm { Input: Mixxx deck to adjust Output: - -------- ------------------------------------------------------ */ - static tapButton(deck) { + tapButton(deck) { const now = new Date() / 1000; // Current time in seconds const tapDelta = now - this.tapTime; this.tapTime = now; From 1188c8f3e4124c10a71935fc994ef5652fd6b4fe Mon Sep 17 00:00:00 2001 From: Joerg Date: Sat, 25 May 2024 15:09:51 +0200 Subject: [PATCH 05/16] Remove redefinition of script.crossfaderCurve --- res/controllers/Reloop-Jockey-3-ME-scripts.js | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/res/controllers/Reloop-Jockey-3-ME-scripts.js b/res/controllers/Reloop-Jockey-3-ME-scripts.js index 347a044f8dfa..7f90e0962662 100644 --- a/res/controllers/Reloop-Jockey-3-ME-scripts.js +++ b/res/controllers/Reloop-Jockey-3-ME-scripts.js @@ -22,17 +22,6 @@ Jockey3ME.loop_move_bool = false; Jockey3ME.MixerDeck1 = 0; Jockey3ME.MixerDeck2 = 0; -script.crossfaderCurve = function (value, min, max) { - var newValue = script.absoluteLin(value, 2, 150, min, max); - var newValueThree = script.absoluteLin(value, 0.1, 0.999307, min, max); - if (engine.getValue("[Mixer Profile]", "xFaderMode")==1) { - // Constant Power - engine.setValue("[Mixer Profile]", "xFaderCalibration", script.absoluteLin(value, 0, newValueThree, min, max)); - } else { - // Additive - engine.setValue("[Mixer Profile]", "xFaderCurve", script.absoluteLin(value, 1, newValue, min, max)); - } -} // Functions // Funny Led show on FX Dry/Wet @@ -437,7 +426,17 @@ Jockey3ME.crossfader = function (channel, control, value, status, group) { } Jockey3ME.crossfaderCurve = function (channel, control, value, status, group) { - script.crossfaderCurve(value, 0 , 127); + const min = 0; + const max = 127; + const newValue = script.absoluteLin(value, 2, 150, min, max); + const newValueThree = script.absoluteLin(value, 0.1, 0.999307, min, max); + if (engine.getValue("[Mixer Profile]", "xFaderMode") === 1) { + // Constant Power + engine.setValue("[Mixer Profile]", "xFaderCalibration", script.absoluteLin(value, 0, newValueThree, min, max)); + } else { + // Additive + engine.setValue("[Mixer Profile]", "xFaderCurve", script.absoluteLin(value, 1, newValue, min, max)); + } } Jockey3ME.MixerVol = function (channel, control, value, status, group) { From e6ddf1f4278bcc2428be3048672c54c913588404 Mon Sep 17 00:00:00 2001 From: Joerg Date: Tue, 28 May 2024 23:23:54 +0200 Subject: [PATCH 06/16] Moved object definitions section (Controller, Deck, Control & Button) to the few mapping scripts that uses it --- res/controllers/.eslintrc.json | 8 +- .../American-Audio-RADIUS-2000-scripts.js | 3 +- .../American-Audio-VMS2-scripts.js | 51 +++++++ .../American-Audio-VMS4-scripts.js | 50 +++++++ .../Hercules-DJ-Console-Mk1-hid-scripts.js | 3 +- .../Hercules-DJ-Console-Mk2-hid-scripts.js | 3 +- .../Hercules-DJ-Console-RMX-hid-scripts.js | 4 +- .../Hercules-DJ-Console-RMX-scripts.js | 101 +++++++++++++ .../Hercules-DJ-Control-MP3-hid-scripts.js | 3 +- res/controllers/Kontrol-Dj-KDJ500-scripts.js | 4 +- .../Novation-Launchpad-Mini-scripts.js | 5 +- res/controllers/Numark-NS7-scripts.js | 52 +++++++ res/controllers/common-controller-scripts.js | 137 ------------------ 13 files changed, 271 insertions(+), 153 deletions(-) diff --git a/res/controllers/.eslintrc.json b/res/controllers/.eslintrc.json index 2a6722aac48c..09af92a77ed6 100644 --- a/res/controllers/.eslintrc.json +++ b/res/controllers/.eslintrc.json @@ -13,12 +13,6 @@ "colorCodeToObject": "readonly", "colorCodeFromObject": "readonly", "script": "readonly", - "bpm": "readonly", - "ButtonState": "readonly", - "LedState": "readonly", - "Controller": "readonly", - "Button": "readonly", - "Control": "readonly", - "Deck": "readonly" + "bpm": "readonly" } } diff --git a/res/controllers/American-Audio-RADIUS-2000-scripts.js b/res/controllers/American-Audio-RADIUS-2000-scripts.js index 9faf58f7057a..f35bb24f13db 100644 --- a/res/controllers/American-Audio-RADIUS-2000-scripts.js +++ b/res/controllers/American-Audio-RADIUS-2000-scripts.js @@ -6,7 +6,8 @@ * The Mapping for the Jog Wheel and Scratch function is great. **/ -RADIUS2000 = new Controller(); +// eslint-disable-next-line no-var +var RADIUS2000 = {}; RADIUS2000.currentDeck = function (group) { if (group == "[Channel1]") diff --git a/res/controllers/American-Audio-VMS2-scripts.js b/res/controllers/American-Audio-VMS2-scripts.js index 8224dfd1fe9d..9881ace1d8d0 100644 --- a/res/controllers/American-Audio-VMS2-scripts.js +++ b/res/controllers/American-Audio-VMS2-scripts.js @@ -27,6 +27,57 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ + + +// ----------------- Object definitions -------------------------- + +var ButtonState = {"released": 0x00, "pressed": 0x7F}; + +var LedState = {"off": 0x00, "on": 0x7F}; + +// Controller + +var Controller = function() { + this.group = "[Master]"; + this.Buttons = []; +}; + +Controller.prototype.addButton = function(buttonName, button, eventHandler) { + if (eventHandler) { + /* eslint @typescript-eslint/no-this-alias: "off" */ + const executionEnvironment = this; + const handler = function(value) { + button.state = value; + executionEnvironment[eventHandler](value); + }; + button.handler = handler; + } + this.Buttons[buttonName] = button; +}; + +// Button + +var Button = function(controlId) { + this.controlId = controlId; + this.state = ButtonState.released; +}; +Button.prototype.handleEvent = function(value) { + this.handler(value); +}; + +// Deck + +var Deck = function(deckNumber, group) { + this.deckNumber = deckNumber; + this.group = group; + this.Buttons = []; +}; + +Deck.prototype.addButton = Controller.prototype.addButton; + +// ----------------- END Object definitions ---------------------- + + VMS2 = new Controller(); VMS2.RateRanges = [0.08, 0.10, 0.30, 1.00]; diff --git a/res/controllers/American-Audio-VMS4-scripts.js b/res/controllers/American-Audio-VMS4-scripts.js index 07dbdb209d3c..6eec7f608435 100644 --- a/res/controllers/American-Audio-VMS4-scripts.js +++ b/res/controllers/American-Audio-VMS4-scripts.js @@ -17,6 +17,56 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ + +// ----------------- Object definitions -------------------------- + + + +var ButtonState = {"released": 0x00, "pressed": 0x7F}; + +var LedState = {"off": 0x00, "on": 0x7F}; + +// Controller + +var Controller = function() { + this.group = "[Master]"; + this.Buttons = []; +}; + +Controller.prototype.addButton = function(buttonName, button, eventHandler) { + if (eventHandler) { + /* eslint @typescript-eslint/no-this-alias: "off" */ + const executionEnvironment = this; + const handler = function(value) { + button.state = value; + executionEnvironment[eventHandler](value); + }; + button.handler = handler; + } + this.Buttons[buttonName] = button; +}; + +// Button + +var Button = function(controlId) { + this.controlId = controlId; + this.state = ButtonState.released; +}; +Button.prototype.handleEvent = function(value) { + this.handler(value); +}; + +// Deck + +var Deck = function(deckNumber, group) { + this.deckNumber = deckNumber; + this.group = group; + this.Buttons = []; +}; +Deck.prototype.addButton = Controller.prototype.addButton; + +// ----------------- END Object definitions ---------------------- + VMS4 = new Controller(); VMS4.RateRanges = [0.08, 0.10, 0.30, 1.00]; diff --git a/res/controllers/Hercules-DJ-Console-Mk1-hid-scripts.js b/res/controllers/Hercules-DJ-Console-Mk1-hid-scripts.js index 05f194ac7f36..aa0ee5278198 100644 --- a/res/controllers/Hercules-DJ-Console-Mk1-hid-scripts.js +++ b/res/controllers/Hercules-DJ-Console-Mk1-hid-scripts.js @@ -4,7 +4,8 @@ /* Author: zestoi with changes by ewanuno */ /****************************************************************/ -HerculesMk1Hid = new Controller(); +// eslint-disable-next-line no-var +var HerculesMk1Hid = {}; HerculesMk1Hid.controls = []; HerculesMk1Hid.leds = []; HerculesMk1Hid.cache_in = []; diff --git a/res/controllers/Hercules-DJ-Console-Mk2-hid-scripts.js b/res/controllers/Hercules-DJ-Console-Mk2-hid-scripts.js index a491711aa291..92a36cd0580e 100644 --- a/res/controllers/Hercules-DJ-Console-Mk2-hid-scripts.js +++ b/res/controllers/Hercules-DJ-Console-Mk2-hid-scripts.js @@ -4,7 +4,8 @@ /* Author: zestoi */ /****************************************************************/ -HerculesMk2Hid = new Controller(); +// eslint-disable-next-line no-var +var HerculesMk2Hid = {}; HerculesMk2Hid.controls = []; HerculesMk2Hid.leds = []; HerculesMk2Hid.cache_in = []; diff --git a/res/controllers/Hercules-DJ-Console-RMX-hid-scripts.js b/res/controllers/Hercules-DJ-Console-RMX-hid-scripts.js index 0e5cb8c1f3e8..c1d169dd5da5 100644 --- a/res/controllers/Hercules-DJ-Console-RMX-hid-scripts.js +++ b/res/controllers/Hercules-DJ-Console-RMX-hid-scripts.js @@ -6,8 +6,8 @@ /* Based on zestoi's script */ /****************************************************************/ -RMX = new Controller(); - +// eslint-disable-next-line no-var +var RMX = {}; RMX.controls = []; RMX.leds = []; RMX.cache_in = []; diff --git a/res/controllers/Hercules-DJ-Console-RMX-scripts.js b/res/controllers/Hercules-DJ-Console-RMX-scripts.js index 14a23f45ea80..2ab53e27c873 100644 --- a/res/controllers/Hercules-DJ-Console-RMX-scripts.js +++ b/res/controllers/Hercules-DJ-Console-RMX-scripts.js @@ -17,6 +17,107 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ +// ----------------- Object definitions -------------------------- + + + +var ButtonState = {"released": 0x00, "pressed": 0x7F}; + +var LedState = {"off": 0x00, "on": 0x7F}; + +// Controller + +var Controller = function() { + this.group = "[Master]"; + this.Controls = []; + this.Buttons = []; +}; + +Controller.prototype.addButton = function(buttonName, button, eventHandler) { + if (eventHandler) { + /* eslint @typescript-eslint/no-this-alias: "off" */ + const executionEnvironment = this; + const handler = function(value) { + button.state = value; + executionEnvironment[eventHandler](value); + }; + button.handler = handler; + } + this.Buttons[buttonName] = button; +}; + + +// Button + +var Button = function(controlId) { + this.controlId = controlId; + this.state = ButtonState.released; +}; +Button.prototype.handleEvent = function(value) { + this.handler(value); +}; + +// Control + +var Control = function(mappedFunction, softMode) { + // These defaults are for MIDI controllers + this.minInput = 0; + this.midInput = 0x3F; + this.maxInput = 0x7F; + // ---- + + this.minOutput = -1.0; + this.midOutput = 0.0; + this.maxOutput = 1.0; + this.mappedFunction = mappedFunction; + this.softMode = softMode; + this.maxJump = 10; +}; + +Control.prototype.setValue = function(group, inputValue) { + let outputValue = 0; + if (inputValue <= this.midInput) { + outputValue = this.minOutput + + ((inputValue - this.minInput) / (this.midInput - this.minInput)) + * (this.midOutput - this.minOutput); + } else { + outputValue = this.midOutput + + ((inputValue - this.midInput) / (this.maxInput - this.midInput)) + * (this.maxOutput - this.midOutput); + } + if (this.softMode) { + const currentValue = engine.getValue(group, this.mappedFunction); + let currentRelative = 0.0; + if (currentValue <= this.midOutput) { + currentRelative = this.minInput + + ((currentValue - this.minOutput) / (this.midOutput - this.minOutput)) + * (this.midInput - this.minInput); + } else { + currentRelative = this.midInput + + ((currentValue - this.midOutput) / (this.maxOutput - this.midOutput)) + * (this.maxInput - this.midInput); + } + if (inputValue > currentRelative - this.maxJump + && inputValue < currentRelative + this.maxJump) { + engine.setValue(group, this.mappedFunction, outputValue); + } + } else { + engine.setValue(group, this.mappedFunction, outputValue); + } +}; + +// Deck + +var Deck = function(deckNumber, group) { + this.deckNumber = deckNumber; + this.group = group; + this.Buttons = []; +}; + +Deck.prototype.addButton = Controller.prototype.addButton; + +// ----------------- END Object definitions ---------------------- + //TODO: Cleanup, create objects from init. //Remove led timers when alsa midi is working properly. HerculesRMX = new Controller(); diff --git a/res/controllers/Hercules-DJ-Control-MP3-hid-scripts.js b/res/controllers/Hercules-DJ-Control-MP3-hid-scripts.js index 367b228b2ac9..e4628e1aa440 100644 --- a/res/controllers/Hercules-DJ-Control-MP3-hid-scripts.js +++ b/res/controllers/Hercules-DJ-Control-MP3-hid-scripts.js @@ -7,7 +7,8 @@ /* */ /****************************************************************/ -HerculesMP3Hid = new Controller(); +// eslint-disable-next-line no-var +var HerculesMP3Hid = {}; HerculesMP3Hid.controls = []; HerculesMP3Hid.leds = []; HerculesMP3Hid.cache_in = []; diff --git a/res/controllers/Kontrol-Dj-KDJ500-scripts.js b/res/controllers/Kontrol-Dj-KDJ500-scripts.js index e472e1805031..a1dc3637a027 100644 --- a/res/controllers/Kontrol-Dj-KDJ500-scripts.js +++ b/res/controllers/Kontrol-Dj-KDJ500-scripts.js @@ -1,4 +1,6 @@ -KDJ500 = new Controller(); + +// eslint-disable-next-line no-var +var KDJ500 = {}; KDJ500.init = function (id, debugging) { diff --git a/res/controllers/Novation-Launchpad-Mini-scripts.js b/res/controllers/Novation-Launchpad-Mini-scripts.js index 94476e78151d..8f9e670e12e5 100644 --- a/res/controllers/Novation-Launchpad-Mini-scripts.js +++ b/res/controllers/Novation-Launchpad-Mini-scripts.js @@ -394,9 +394,10 @@ function SeekKey(ch, pos) { } SeekKey.keys = new Array(); -//Define the controller +// Define the controller +// eslint-disable-next-line no-var +var NLM = {}; -NLM = new Controller(); NLM.init = function() { NLM.page = 0; diff --git a/res/controllers/Numark-NS7-scripts.js b/res/controllers/Numark-NS7-scripts.js index f2fcf7213f92..e8a2b02e161c 100644 --- a/res/controllers/Numark-NS7-scripts.js +++ b/res/controllers/Numark-NS7-scripts.js @@ -16,6 +16,58 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ + +// ----------------- Object definitions -------------------------- + + + +var ButtonState = {"released": 0x00, "pressed": 0x7F}; + +var LedState = {"off": 0x00, "on": 0x7F}; + +// Controller + +var Controller = function() { + this.group = "[Master]"; + this.Buttons = []; +}; + +Controller.prototype.addButton = function(buttonName, button, eventHandler) { + if (eventHandler) { + /* eslint @typescript-eslint/no-this-alias: "off" */ + const executionEnvironment = this; + const handler = function(value) { + button.state = value; + executionEnvironment[eventHandler](value); + }; + button.handler = handler; + } + this.Buttons[buttonName] = button; +}; + +// Button + +var Button = function(controlId) { + this.controlId = controlId; + this.state = ButtonState.released; +}; +Button.prototype.handleEvent = function(value) { + this.handler(value); +}; + + +// Deck + +var Deck = function(deckNumber, group) { + this.deckNumber = deckNumber; + this.group = group; + this.Buttons = []; +}; + +Deck.prototype.addButton = Controller.prototype.addButton; + +// ----------------- END Object definitions ---------------------- + NumarkNS7 = new Controller(); NumarkNS7.RateRanges = [0.08, 0.10, 0.30, 1.00]; diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 913b5026134b..3c0d7516fd5f 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -9,12 +9,6 @@ colorCodeFromObject:off script:off bpm:off - ButtonState:off - LedState:off - Controller:off - Button:off - Control:off - Deck:off */ // ----------------- Prototype enhancements --------------------- @@ -569,134 +563,3 @@ class bpm { // Add class bpm to the Global JavaScript object // @ts-ignore Same identifier for class and instance needed for backward compatibility this.bpm = bpm; - - - -// ----------------- Object definitions -------------------------- - - - -const ButtonState = {"released": 0x00, "pressed": 0x7F}; -// eslint-disable-next-line no-unused-vars -const LedState = {"off": 0x00, "on": 0x7F}; - -// Controller - -// @ts-ignore Same identifier for class and instance needed for backward compatibility -class Controller { - constructor() { - this.group = "[Master]"; - this.Controls = []; - this.Buttons = []; - } - addButton(buttonName, button, eventHandler) { - if (eventHandler) { - const executionEnvironment = this; - const handler = function(value) { - button.state = value; - executionEnvironment[eventHandler](value); - }; - button.handler = handler; - } - this.Buttons[buttonName] = button; - } - setControlValue(control, value) { - this.Controls[control].setValue(this.group, value); - } -} -// Add class Controller to the Global JavaScript object -// @ts-ignore Same identifier for class and instance needed for backward compatibility -this.Controller = Controller; - - - -// Button - - -// @ts-ignore Same identifier for class and instance needed for backward compatibility -class Button { - constructor(controlId) { - this.controlId = controlId; - this.state = ButtonState.released; - } - handleEvent(value) { - this.handler(value); - } -} -// Add class Button to the Global JavaScript object -// @ts-ignore Same identifier for class and instance needed for backward compatibility -this.Button = Button; - -// Control - - -// @ts-ignore Same identifier for class and instance needed for backward compatibility -class Control { - constructor(mappedFunction, softMode) { - // These defaults are for MIDI controllers - this.minInput = 0; - this.midInput = 0x3F; - this.maxInput = 0x7F; - // ---- - this.minOutput = -1.0; - this.midOutput = 0.0; - this.maxOutput = 1.0; - this.mappedFunction = mappedFunction; - this.softMode = softMode; - this.maxJump = 10; - } - setValue(group, inputValue) { - let outputValue = 0; - if (inputValue <= this.midInput) { - outputValue = this.minOutput - + ((inputValue - this.minInput) / (this.midInput - this.minInput)) - * (this.midOutput - this.minOutput); - } else { - outputValue = this.midOutput - + ((inputValue - this.midInput) / (this.maxInput - this.midInput)) - * (this.maxOutput - this.midOutput); - } - if (this.softMode) { - const currentValue = engine.getValue(group, this.mappedFunction); - let currentRelative = 0.0; - if (currentValue <= this.midOutput) { - currentRelative = this.minInput - + ((currentValue - this.minOutput) / (this.midOutput - this.minOutput)) - * (this.midInput - this.minInput); - } else { - currentRelative = this.midInput - + ((currentValue - this.midOutput) / (this.maxOutput - this.midOutput)) - * (this.maxInput - this.midInput); - } - if (inputValue > currentRelative - this.maxJump - && inputValue < currentRelative + this.maxJump) { - engine.setValue(group, this.mappedFunction, outputValue); - } - } else { - engine.setValue(group, this.mappedFunction, outputValue); - } - } -} -// Add class Control to the Global JavaScript object -// @ts-ignore Same identifier for class and instance needed for backward compatibility -this.Control = Control; - - -// Deck - -// @ts-ignore Same identifier for class and instance needed for backward compatibility -class Deck { - constructor(deckNumber, group) { - this.deckNumber = deckNumber; - this.group = group; - this.Buttons = []; - } -} - -// Add class Deck to the Global JavaScript object -// @ts-ignore Same identifier for class and instance needed for backward compatibility -this.Deck = Deck; -Deck.prototype.setControlValue = Controller.prototype.setControlValue; -Deck.prototype.addButton = Controller.prototype.addButton; - -// ----------------- END Object definitions ---------------------- From 91d41c1c69e8d5f2d12bcbeb40a7a586c2c0e249 Mon Sep 17 00:00:00 2001 From: Joerg Date: Wed, 29 May 2024 19:31:30 +0200 Subject: [PATCH 07/16] Call the constructor of bpmClass, instead of just aliasing it --- res/controllers/common-controller-scripts.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 3c0d7516fd5f..d8d9df437bc9 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -496,7 +496,7 @@ const script = { // @ts-ignore Same identifier for class and instance needed for backward compatibility -class bpm { +class bpmClass { constructor() { this.tapTime = 0.0; this.previousTapDelta = 0.0; @@ -560,6 +560,5 @@ class bpm { fRateScale * engine.getValue(group, "rate_dir")); } } -// Add class bpm to the Global JavaScript object -// @ts-ignore Same identifier for class and instance needed for backward compatibility -this.bpm = bpm; +// Add instance bpm of bpmClass to the Global JavaScript object +this.bpm = new bpmClass(); From dfc0f2eb759f3bb5ab93be5d3d7dc390811fd5c9 Mon Sep 17 00:00:00 2001 From: Joerg Date: Wed, 29 May 2024 19:52:59 +0200 Subject: [PATCH 08/16] Freeze object "script" --- res/controllers/common-controller-scripts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index d8d9df437bc9..47119a2e734b 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -117,7 +117,7 @@ const colorCodeToObject = function(colorCode) { }; }; -const script = { +const script = Object.freeze({ // ----------------- Mapping script constants --------------------- // Common regular expressions @@ -488,7 +488,7 @@ const script = { parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), factor); } -}; +}); From 8b57df36b832858b22ebfed5ff1077891f83aa90 Mon Sep 17 00:00:00 2001 From: Joerg Date: Tue, 16 Jul 2024 22:23:25 +0200 Subject: [PATCH 09/16] Use var instead of const for script to make script.deepmerge working --- res/controllers/common-controller-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 5690a661f2a6..05d3830bdf97 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -117,7 +117,7 @@ const colorCodeToObject = function(colorCode) { }; }; -const script = Object.freeze({ +var script = Object.freeze({ // ----------------- Mapping script constants --------------------- // Common regular expressions From acb1d938bc57079b845fb970487d92e9e964f7d4 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Tue, 16 Jul 2024 08:54:37 +0200 Subject: [PATCH 10/16] Changed script's method definitions to ES6 methods --- res/controllers/common-controller-scripts.js | 97 ++++++++++---------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 05d3830bdf97..4550f342f386 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -26,7 +26,6 @@ String.prototype.toInt = function() { /** * Prints a message to the terminal and the log file. - * * @param {string} message - The log message. * @deprecated Use console.log()/console.warn()/console.debug() instead. */ @@ -57,11 +56,11 @@ const stringifyObject = function(obj, maxdepth, checked, prefix) { for (const property in obj) { const value = obj[property]; if (typeof value === "function") { continue; } - output += prefix + property + ": " - + stringifyObject(value, maxdepth - 1, checked, prefix + " ") - + "\n"; + output += `${prefix + property }: ${ + stringifyObject(value, maxdepth - 1, checked, `${prefix } `) + }\n`; } - return output + prefix.substr(2) + "}"; + return `${output + prefix.substr(2) }}`; } } return obj; @@ -78,9 +77,9 @@ const printObject = function(obj, maxdepth) { const secondstominutes = function(secs) { const m = (secs / 60) | 0; - return (m < 10 ? "0" + m : m) - + ":" - + ((secs %= 60) < 10 ? "0" + secs : secs); + return `${m < 10 ? `0${ m}` : m + }:${ + (secs %= 60) < 10 ? `0${ secs}` : secs}`; }; // eslint-disable-next-line no-unused-vars @@ -92,11 +91,11 @@ const msecondstominutes = function(msecs) { msecs = Math.round(msecs * 100 / 1000); if (msecs === 100) { msecs = 99; } - return (m < 10 ? "0" + m : m) - + ":" - + (secs < 10 ? "0" + secs : secs) - + "." - + (msecs < 10 ? "0" + msecs : msecs); + return `${m < 10 ? `0${ m}` : m + }:${ + secs < 10 ? `0${ secs}` : secs + }.${ + msecs < 10 ? `0${ msecs}` : msecs}`; }; // Converts an object with "red", "green" and "blue" properties (value range @@ -113,7 +112,7 @@ const colorCodeToObject = function(colorCode) { return { "red": (colorCode >> 16) & 0xFF, "green": (colorCode >> 8) & 0xFF, - "blue": colorCode & 0xFF, + "blue": colorCode & 0xFF }; }; @@ -153,7 +152,7 @@ var script = Object.freeze({ PREVIEW: 21, COVERART: 22, TRACK_COLOR: 30, - LAST_PLAYED: 31, + LAST_PLAYED: 31 }), /** @@ -170,7 +169,7 @@ var script = Object.freeze({ * @param {any} obj Object to test * @returns {boolean} true if obj was created using the `{}` or `new Object()` synthax, false otherwise */ - isSimpleObject: function(obj) { + isSimpleObject(obj) { return obj !== null && typeof obj === "object" && obj.constructor.name === "Object"; }, @@ -180,7 +179,7 @@ var script = Object.freeze({ * @param target {object | Array} Object to merge source into * @param source {object | Array} Object to merge into source */ - deepMerge: function(target, source) { + deepMerge(target, source) { if (target === source || target === undefined || target === null || source === undefined || source === null) { return; } @@ -207,31 +206,32 @@ var script = Object.freeze({ // @deprecated Use script.midiDebug() instead - debug: function(channel, control, value, status, group) { + debug(channel, control, value, status, group) { console.log("Warning: script.debug() is deprecated. Use script.midiDebug() instead."); script.midiDebug(channel, control, value, status, group); }, // @deprecated Use script.midiPitch() instead - pitch: function(LSB, MSB, status) { + pitch(LSB, MSB, status) { console.log("Warning: script.pitch() is deprecated. Use script.midiPitch() instead."); return script.midiPitch(LSB, MSB, status); }, // @deprecated Use script.absoluteLin() instead - absoluteSlider: function(group, key, value, low, high, min, max) { - console.log("Warning: script.absoluteSlider() is deprecated. Use engine.setValue(group, key, script.absoluteLin(...)) instead."); + absoluteSlider(group, key, value, low, high, min, max) { + console.log( + "Warning: script.absoluteSlider() is deprecated. Use engine.setValue(group, key, script.absoluteLin(...)) instead."); engine.setValue(group, key, script.absoluteLin(value, low, high, min, max)); }, - midiDebug: function(channel, control, value, status, group) { + midiDebug(channel, control, value, status, group) { console.log(`Script.midiDebug - channel: 0x${ channel.toString(16) } control: 0x${ control.toString(16) } value: 0x${ value.toString(16) } status: 0x${ status.toString(16) } group: ${ group}`); }, // Returns the deck number of a "ChannelN" or "SamplerN" group - deckFromGroup: function(group) { + deckFromGroup(group) { let deck = 0; if (group.substring(2, 8) === "hannel") { // Extract deck number from the group text @@ -243,7 +243,7 @@ var script = Object.freeze({ deck = group.substring(8,group.length-1); } */ - return parseInt(deck); + return Number.parseInt(deck); }, /* -------- ------------------------------------------------------ @@ -255,7 +255,7 @@ var script = Object.freeze({ controls will be bound to. Output: none -------- ------------------------------------------------------ */ - bindConnections: function(group, controlsToFunctions, remove) { + bindConnections(group, controlsToFunctions, remove) { let control; remove = (remove === undefined) ? false : remove; @@ -273,7 +273,7 @@ var script = Object.freeze({ Input: Group and control names Output: none -------- ------------------------------------------------------ */ - toggleControl: function(group, control) { + toggleControl(group, control) { engine.setValue(group, control, !(engine.getValue(group, control))); }, @@ -286,7 +286,7 @@ var script = Object.freeze({ Input: Group and control names, delay in milliseconds (optional) Output: none -------- ------------------------------------------------------ */ - triggerControl: function(group, control, delay) { + triggerControl(group, control, delay) { if (typeof delay !== "number") { delay = 200; } @@ -303,7 +303,7 @@ var script = Object.freeze({ (Default knob values are standard MIDI 0..127) Output: MixxxControl value corresponding to the knob position -------- ------------------------------------------------------ */ - absoluteLin: function(value, low, high, min, max) { + absoluteLin(value, low, high, min, max) { if (!min) { min = 0; } @@ -329,7 +329,7 @@ var script = Object.freeze({ (Default knob values are standard MIDI 0..127) Output: Linear value corresponding to the knob position -------- ------------------------------------------------------ */ - absoluteLinInverse: function(value, low, high, min, max) { + absoluteLinInverse(value, low, high, min, max) { if (!min) { min = 0; } @@ -355,7 +355,7 @@ var script = Object.freeze({ (Default knob values are standard MIDI 0..127) Output: MixxxControl value corresponding to the knob position -------- ------------------------------------------------------ */ - absoluteNonLin: function(value, low, mid, high, min, max) { + absoluteNonLin(value, low, mid, high, min, max) { if (!min) { min = 0; } @@ -380,7 +380,7 @@ var script = Object.freeze({ bottom of output range, top of output range. (Default output range is standard MIDI 0..127) Output: MixxxControl value scaled to output range -------- ------------------------------------------------------ */ - absoluteNonLinInverse: function(value, low, mid, high, min, max) { + absoluteNonLinInverse(value, low, mid, high, min, max) { if (!min) { min = 0; } @@ -413,15 +413,17 @@ var script = Object.freeze({ Input: Current value of the hardware control, min and max values for that control Output: none -------- ------------------------------------------------------ */ - crossfaderCurve: function(value, min, max) { + crossfaderCurve(value, min, max) { if (engine.getValue("[Mixer Profile]", "xFaderMode") === 1) { // Constant Power engine.setValue("[Mixer Profile]", "xFaderCalibration", - script.absoluteLin(value, 0.5, 0.962, min, max)); + script.absoluteLin(value, 0.5, 0.962, min, max) + ); } else { // Additive engine.setValue("[Mixer Profile]", "xFaderCurve", - script.absoluteLin(value, 1, 2, min, max)); + script.absoluteLin(value, 1, 2, min, max) + ); } }, @@ -432,7 +434,7 @@ var script = Object.freeze({ Input: dividend `a` and divisor `m` for modulo (a % m) Output: positive remainder -------- ------------------------------------------------------ */ - posMod: function(a, m) { + posMod(a, m) { return ((a % m) + m) % m; }, @@ -445,7 +447,7 @@ var script = Object.freeze({ Input: MixxxControl group, direction to move, number of beats to move Output: none -------- ------------------------------------------------------ */ - loopMove: function(group, direction, numberOfBeats) { + loopMove(group, direction, numberOfBeats) { if (!numberOfBeats || numberOfBeats === 0) { numberOfBeats = 0.5; } if (direction < 0) { @@ -465,7 +467,7 @@ var script = Object.freeze({ message was not a Pitch message (0xE#) -------- ------------------------------------------------------ */ // TODO: Is this still useful now that MidiController.cpp properly handles these? - midiPitch: function(LSB, MSB, status) { + midiPitch(LSB, MSB, status) { if ((status & 0xF0) !== 0xE0) { // Mask the upper nybble so we can check the opcode regardless of the channel console.log(`Script.midiPitch: Error, not a MIDI pitch (0xEn) message: ${ status}`); return false; @@ -485,7 +487,7 @@ var script = Object.freeze({ Input: channel, control, value, status, group, factor (optional), start rate (optional) Output: none -------- ------------------------------------------------------ */ - spinback: function(channel, control, value, status, group, factor, rate) { + spinback(channel, control, value, status, group, factor, rate) { // if brake is called without defined factor and rate, reset to defaults if (factor === undefined) { factor = 1; @@ -497,7 +499,8 @@ var script = Object.freeze({ // disable on note-off or zero value note/cc engine.spinback( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), - factor, rate); + factor, rate + ); }, /* -------- ------------------------------------------------------ @@ -508,7 +511,7 @@ var script = Object.freeze({ Input: channel, control, value, status, group, factor (optional) Output: none -------- ------------------------------------------------------ */ - brake: function(channel, control, value, status, group, factor) { + brake(channel, control, value, status, group, factor) { // if brake is called without factor defined, reset to default if (factor === undefined) { factor = 1; @@ -516,7 +519,8 @@ var script = Object.freeze({ // disable on note-off or zero value note/cc, use default decay rate '1' engine.brake( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), - factor); + factor + ); }, /* -------- ------------------------------------------------------ @@ -528,7 +532,7 @@ var script = Object.freeze({ Input: channel, control, value, status, group, acceleration factor (optional) Output: none -------- ------------------------------------------------------ */ - softStart: function(channel, control, value, status, group, factor) { + softStart(channel, control, value, status, group, factor) { // if softStart is called without factor defined, reset to default if (factor === undefined) { factor = 1; @@ -536,15 +540,13 @@ var script = Object.freeze({ // disable on note-off or zero value note/cc, use default increase rate '1' engine.softStart( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), - factor); + factor + ); } }); - // bpm - Used for tapping the desired BPM for a deck - - // @ts-ignore Same identifier for class and instance needed for backward compatibility class bpmClass { constructor() { @@ -607,7 +609,8 @@ class bpmClass { engine.setValue( group, "rate", - fRateScale * engine.getValue(group, "rate_dir")); + fRateScale * engine.getValue(group, "rate_dir") + ); } } // Add instance bpm of bpmClass to the Global JavaScript object From 8153113dc33993a25c4964dcb24ba4a7f3dd0956 Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 18 Jul 2024 00:44:41 +0200 Subject: [PATCH 11/16] Refactored arrayContains --- res/controllers/common-controller-scripts.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 4550f342f386..5cc6cd0dafb7 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -35,11 +35,15 @@ const print = function(message) { console.log(message); }; +/** + * Checks if an array contains a specific element. + * @param {Array} array The array to search. + * @param {*} elem The element to search for in the array. + * @returns {boolean} True if the element is found in the array, false otherwise. + * @deprecated Use array.includes(elem) instead. + */ const arrayContains = function(array, elem) { - for (let i = 0; i < array.length; i++) { - if (array[i] === elem) { return true; } - } - return false; + return array.includes(elem); }; const stringifyObject = function(obj, maxdepth, checked, prefix) { From ca59e0683386543942c22dd33a53174265d392c5 Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 18 Jul 2024 01:12:54 +0200 Subject: [PATCH 12/16] Use default values and further optimization --- res/controllers/common-controller-scripts.js | 86 ++++---------------- 1 file changed, 18 insertions(+), 68 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 5cc6cd0dafb7..71f9a0a9aa06 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -46,13 +46,10 @@ const arrayContains = function(array, elem) { return array.includes(elem); }; -const stringifyObject = function(obj, maxdepth, checked, prefix) { - if (!maxdepth) { maxdepth = 2; } +const stringifyObject = function(obj, maxdepth = 2, checked = [], prefix ="") { try { return JSON.stringify(obj, null, maxdepth); } catch (e) { - if (!checked) { checked = []; } - if (!prefix) { prefix = ""; } if (maxdepth > 0 && typeof obj === "object" && obj !== null && Object.getPrototypeOf(obj) !== "" && !arrayContains(checked, obj)) { checked.push(obj); @@ -259,11 +256,8 @@ var script = Object.freeze({ controls will be bound to. Output: none -------- ------------------------------------------------------ */ - bindConnections(group, controlsToFunctions, remove) { - let control; - remove = (remove === undefined) ? false : remove; - - for (control in controlsToFunctions) { + bindConnections(group, controlsToFunctions, remove = false) { + for (const control in controlsToFunctions) { engine.connectControl(group, control, controlsToFunctions[control], remove); if (!remove) { engine.trigger(group, control); @@ -290,7 +284,7 @@ var script = Object.freeze({ Input: Group and control names, delay in milliseconds (optional) Output: none -------- ------------------------------------------------------ */ - triggerControl(group, control, delay) { + triggerControl(group, control, delay = 200) { if (typeof delay !== "number") { delay = 200; } @@ -307,14 +301,7 @@ var script = Object.freeze({ (Default knob values are standard MIDI 0..127) Output: MixxxControl value corresponding to the knob position -------- ------------------------------------------------------ */ - absoluteLin(value, low, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } - + absoluteLin(value, low, high, min = 0, max = 127) { if (value <= min) { return low; } else if (value >= max) { @@ -333,23 +320,12 @@ var script = Object.freeze({ (Default knob values are standard MIDI 0..127) Output: Linear value corresponding to the knob position -------- ------------------------------------------------------ */ - absoluteLinInverse(value, low, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } + absoluteLinInverse(value, low, high, min = 0, max = 127) { const result = (((value - low) * (max - min)) / (high - low)) + min; - if (result < min) { - return min; - } else if (result > max) { - return max; - } else { - return result; - } + return Math.min(Math.max(result, min), max); }, + /* -------- ------------------------------------------------------ script.absoluteNonLin Purpose: Maps an absolute linear control value to a non-linear Mixxx control @@ -359,7 +335,7 @@ var script = Object.freeze({ (Default knob values are standard MIDI 0..127) Output: MixxxControl value corresponding to the knob position -------- ------------------------------------------------------ */ - absoluteNonLin(value, low, mid, high, min, max) { + absoluteNonLin(value, low, mid, high, min = 0, max = 127) { if (!min) { min = 0; } @@ -384,13 +360,7 @@ var script = Object.freeze({ bottom of output range, top of output range. (Default output range is standard MIDI 0..127) Output: MixxxControl value scaled to output range -------- ------------------------------------------------------ */ - absoluteNonLinInverse(value, low, mid, high, min, max) { - if (!min) { - min = 0; - } - if (!max) { - max = 127; - } + absoluteNonLinInverse(value, low, mid, high, min = 0, max = 127) { const center = (max - min) / 2; let result; @@ -402,13 +372,7 @@ var script = Object.freeze({ result = center + (center / (high - mid)) * (value - mid); } - if (result < min) { - return min; - } else if (result > max) { - return max; - } else { - return result; - } + return Math.min(Math.max(result, min), max); }, /* -------- ------------------------------------------------------ @@ -451,8 +415,8 @@ var script = Object.freeze({ Input: MixxxControl group, direction to move, number of beats to move Output: none -------- ------------------------------------------------------ */ - loopMove(group, direction, numberOfBeats) { - if (!numberOfBeats || numberOfBeats === 0) { numberOfBeats = 0.5; } + loopMove(group, direction, numberOfBeats = 0.5) { + if (numberOfBeats === 0) { numberOfBeats = 0.5; } if (direction < 0) { engine.setValue(group, "loop_move", -numberOfBeats); @@ -483,6 +447,7 @@ var script = Object.freeze({ // print("Script.Pitch: MSB="+MSB+", LSB="+LSB+", value="+value+", rate="+rate); return rate; }, + /* -------- ------------------------------------------------------ script.spinback Purpose: wrapper around engine.spinback() that can be directly mapped @@ -491,15 +456,7 @@ var script = Object.freeze({ Input: channel, control, value, status, group, factor (optional), start rate (optional) Output: none -------- ------------------------------------------------------ */ - spinback(channel, control, value, status, group, factor, rate) { - // if brake is called without defined factor and rate, reset to defaults - if (factor === undefined) { - factor = 1; - } - // if brake is called without defined rate, reset to default - if (rate === undefined) { - rate = -10; - } + spinback(channel, control, value, status, group, factor = 1, rate = -10) { // disable on note-off or zero value note/cc engine.spinback( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), @@ -515,11 +472,7 @@ var script = Object.freeze({ Input: channel, control, value, status, group, factor (optional) Output: none -------- ------------------------------------------------------ */ - brake(channel, control, value, status, group, factor) { - // if brake is called without factor defined, reset to default - if (factor === undefined) { - factor = 1; - } + brake(channel, control, value, status, group, factor = 1) { // disable on note-off or zero value note/cc, use default decay rate '1' engine.brake( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), @@ -536,17 +489,14 @@ var script = Object.freeze({ Input: channel, control, value, status, group, acceleration factor (optional) Output: none -------- ------------------------------------------------------ */ - softStart(channel, control, value, status, group, factor) { - // if softStart is called without factor defined, reset to default - if (factor === undefined) { - factor = 1; - } + softStart(channel, control, value, status, group, factor = 1) { // disable on note-off or zero value note/cc, use default increase rate '1' engine.softStart( parseInt(group.substring(8, 9)), ((status & 0xF0) !== 0x80 && value > 0), factor ); } + }); From fcff02a6118331e867df22a75dea922602c86c6d Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 18 Jul 2024 01:16:33 +0200 Subject: [PATCH 13/16] Optimized String.prototype.toInt --- res/controllers/common-controller-scripts.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 71f9a0a9aa06..a4c7d44773d8 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -13,13 +13,13 @@ // ----------------- Prototype enhancements --------------------- -// Returns an ASCII byte array for the string +/** + * Converts the string to an array of ASCII values. + * @returns {number[]} An array of ASCII values corresponding to the string's characters. + */ +// @ts-ignore String.prototype.toInt = function() { - const a = []; - for (let i = 0; i < this.length; i++) { - a[i] = this.charCodeAt(i); - } - return a; + return Array.from(this, char => char.charCodeAt(0)); }; // ----------------- Function overloads --------------------- From 45feddcd742dfe4d5cc5cf44031e8f60027f547a Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 18 Jul 2024 18:26:52 +0200 Subject: [PATCH 14/16] Fix tslint warnings --- res/controllers/common-controller-scripts.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index a4c7d44773d8..0f8eb02c9a98 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -190,7 +190,8 @@ var script = Object.freeze({ const objSource = source.reduce((acc, val, idx) => Object.assign(acc, {[idx]: val}), {}); script.deepMerge(objTarget, objSource); target.length = 0; - target.push(...Object.values(objTarget)); + const values = Object.keys(objTarget).map(key => objTarget[key]); + target.push(...values); } else if (script.isSimpleObject(target) && script.isSimpleObject(source)) { Object.keys(source).forEach(key => { if ( @@ -233,7 +234,7 @@ var script = Object.freeze({ // Returns the deck number of a "ChannelN" or "SamplerN" group deckFromGroup(group) { - let deck = 0; + let deck = "0"; if (group.substring(2, 8) === "hannel") { // Extract deck number from the group text deck = group.substring(8, group.length - 1); @@ -272,7 +273,7 @@ var script = Object.freeze({ Output: none -------- ------------------------------------------------------ */ toggleControl(group, control) { - engine.setValue(group, control, !(engine.getValue(group, control))); + engine.setValue(group, control, engine.getValue(group, control) === 0 ? 1 : 0); }, /* -------- ------------------------------------------------------ @@ -520,7 +521,7 @@ class bpmClass { Output: - -------- ------------------------------------------------------ */ tapButton(deck) { - const now = new Date() / 1000; // Current time in seconds + const now = new Date().getTime() / 1000; // Current time in seconds const tapDelta = now - this.tapTime; this.tapTime = now; From dbe9a264b42a3ef6c889861ce56b6eba9fad2f75 Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 18 Jul 2024 18:30:20 +0200 Subject: [PATCH 15/16] Mark script.deepMerge as deprecated --- res/controllers/common-controller-scripts.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 0f8eb02c9a98..f732291845c7 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -179,8 +179,11 @@ var script = Object.freeze({ * Deeply merges 2 objects (Arrays and Objects only, not Map for instance). * @param target {object | Array} Object to merge source into * @param source {object | Array} Object to merge into source + * @deprecated Use {@link Object.assign} instead */ deepMerge(target, source) { + console.warn("script.deepMerge is deprecated; use Object.assign instead"); + if (target === source || target === undefined || target === null || source === undefined || source === null) { return; } From 0b22ff021ce234d9d3000714754a56e8ec893054 Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 18 Jul 2024 20:22:36 +0200 Subject: [PATCH 16/16] JSDoc --- res/controllers/common-controller-scripts.js | 311 +++++++++++-------- 1 file changed, 181 insertions(+), 130 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index f732291845c7..da5ea911513b 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -26,7 +26,7 @@ String.prototype.toInt = function() { /** * Prints a message to the terminal and the log file. - * @param {string} message - The log message. + * @param {string} message The log message. * @deprecated Use console.log()/console.warn()/console.debug() instead. */ @@ -46,6 +46,14 @@ const arrayContains = function(array, elem) { return array.includes(elem); }; +/** + * Convert an object to a string representation with a specified maximum depth. + * @param {Object} obj The object to stringify. + * @param {number} maxdepth The maximum depth to traverse the object. + * @param {Array} [checked] An array to keep track of already checked objects to avoid circular references. + * @param {string} [prefix] A prefix used for indentation purposes, aiding in the readability of the output. + * @returns {string} The stringified object or the original object if it cannot be stringified. + */ const stringifyObject = function(obj, maxdepth = 2, checked = [], prefix ="") { try { return JSON.stringify(obj, null, maxdepth); @@ -67,6 +75,11 @@ const stringifyObject = function(obj, maxdepth = 2, checked = [], prefix ="") { return obj; }; +/** + * Logs a stringified representation of an object to the console with a specified maximum depth. + * @param {Object} obj The object to be logged. + * @param {number} maxdepth The maximum depth to which the object should be stringified. + */ // eslint-disable-next-line no-unused-vars const printObject = function(obj, maxdepth) { console.log(stringifyObject(obj, maxdepth)); @@ -74,6 +87,11 @@ const printObject = function(obj, maxdepth) { // ----------------- Generic functions --------------------- +/** + * Converts seconds into a minutes:seconds format string. + * @param {number} secs The number of seconds to convert. + * @returns {string} The time in minutes:seconds format, padded with zeros if necessary. + */ // eslint-disable-next-line no-unused-vars const secondstominutes = function(secs) { const m = (secs / 60) | 0; @@ -83,6 +101,11 @@ const secondstominutes = function(secs) { (secs %= 60) < 10 ? `0${ secs}` : secs}`; }; +/** + * Converts milliseconds into a formatted string of minutes, seconds, and hundredths of a second. + * @param {number} msecs The number of milliseconds to convert. + * @returns {string} The formatted time string + */ // eslint-disable-next-line no-unused-vars const msecondstominutes = function(msecs) { const m = (msecs / 60000) | 0; @@ -99,15 +122,21 @@ const msecondstominutes = function(msecs) { msecs < 10 ? `0${ msecs}` : msecs}`; }; -// Converts an object with "red", "green" and "blue" properties (value range -// 0-255) into an RGB color code (e.g. 0xFF0000). +/** + * Converts an object with "red", "green", and "blue" properties (value range 0-255) into an RGB color code (e.g. 0xFF0000). + * @param {Object} color An object with "red", "green", and "blue" properties, each a number from 0 to 255. + * @returns {number} Single integer representing the RGB color code corresponding to the input color components. + */ // eslint-disable-next-line no-unused-vars const colorCodeFromObject = function(color) { return ((color.red & 0xFF) << 16 | (color.green & 0xFF) << 8 | (color.blue & 0xFF)); }; -// Converts an RGB color code (e.g. 0xFF0000) into an object with "red", -// "green" and "blue" properties (value range 0-255). +/** + * Converts an RGB color code (e.g. 0xFF0000) into an object with "red", "green", and "blue" properties. + * @param {number} colorCode The RGB color code to convert, e.g., 0xFF0000 for red. + * @returns {{red: number, green: number, blue: number}} An object containing the red, green, and blue components (ranging from 0 to 255) of the color. + */ // eslint-disable-next-line no-unused-vars const colorCodeToObject = function(colorCode) { return { @@ -117,6 +146,9 @@ const colorCodeToObject = function(colorCode) { }; }; +/** + * A collection of generic functions and regular expression + */ var script = Object.freeze({ // ----------------- Mapping script constants --------------------- @@ -229,6 +261,15 @@ var script = Object.freeze({ engine.setValue(group, key, script.absoluteLin(value, low, high, min, max)); }, + /** + * Logs MIDI message information to the console. + * Useful for debugging MIDI scripts by printing out the MIDI message components in a readable format. + * @param {number} channel The MIDI channel of the message. + * @param {number} control The control number (e.g., note number or controller number). + * @param {number} value The value of the control (e.g., velocity or controller value). + * @param {number} status The status of the MIDI message. + * @param {string} group The Mixxx control group the message is associated with. + */ midiDebug(channel, control, value, status, group) { console.log(`Script.midiDebug - channel: 0x${ channel.toString(16) } control: 0x${ control.toString(16) } value: 0x${ value.toString(16) @@ -251,15 +292,13 @@ var script = Object.freeze({ return Number.parseInt(deck); }, - /* -------- ------------------------------------------------------ - script.bindConnections - Purpose: Binds multiple controls at once. See an example in Pioneer-DDJ-SB-scripts.js - Input: The group whose controls are to be bound and an object - (controlstToFunctions) where the properties' names are - controls names and the values are the functions those - controls will be bound to. - Output: none - -------- ------------------------------------------------------ */ + /** + * Binds or unbinds functions to Mixxx controls for a specific group. + * @param {string} group The Mixxx control group (e.g., "[Channel1]", "[Master]") to which the bindings should apply. + * @param {Object.} controlsToFunctions An object mapping control names to callback functions. + * @param {boolean} [remove] If true, the bindings are removed instead of created. Defaults is false. + * @deprecated Use engine.makeConnection or connection.disconnect() based code instead + */ bindConnections(group, controlsToFunctions, remove = false) { for (const control in controlsToFunctions) { engine.connectControl(group, control, controlsToFunctions[control], remove); @@ -269,25 +308,24 @@ var script = Object.freeze({ } }, - /* -------- ------------------------------------------------------ - script.toggleControl - Purpose: Toggles an engine value - Input: Group and control names - Output: none - -------- ------------------------------------------------------ */ + /** + * Toggles the value of a specified control in Mixxx. + * If the current value is 0, it sets the value to 1, and vice versa. + * @param {string} group The Mixxx control group (e.g., "[Channel1]", "[Master]") the control belongs to. + * @param {string} control The name of the control to toggle. + */ toggleControl(group, control) { engine.setValue(group, control, engine.getValue(group, control) === 0 ? 1 : 0); }, - /* -------- ------------------------------------------------------ - script.toggleControl - Purpose: Triggers an engine value and resets it back to 0 after a delay - This is helpful for mapping encoder turns to controls that are - represented by buttons in skins so the skin button lights up - briefly but does not stay lit. - Input: Group and control names, delay in milliseconds (optional) - Output: none - -------- ------------------------------------------------------ */ + /** + * Triggers an engine value and resets it back to 0 after a specified delay. + * This is helpful for mapping encoder turns to controls that are represented by buttons in skins, + * so the skin button lights up briefly but does not stay lit. + * @param {string} group The Mixxx control group (e.g., "[Channel1]", "[Master]") the control belongs to. + * @param {string} control The name of the control to trigger. + * @param {number} [delay] The delay in milliseconds before resetting the control value back to 0. Defaults is 200ms. + */ triggerControl(group, control, delay = 200) { if (typeof delay !== "number") { delay = 200; @@ -296,15 +334,16 @@ var script = Object.freeze({ engine.beginTimer(delay, () => engine.setValue(group, control, 0), true); }, - /* -------- ------------------------------------------------------ - script.absoluteLin - Purpose: Maps an absolute linear control value to a linear Mixxx control - value (like Volume: 0..1) - Input: Control value (e.g. a knob,) MixxxControl values for the lowest and - highest points, lowest knob value, highest knob value - (Default knob values are standard MIDI 0..127) - Output: MixxxControl value corresponding to the knob position - -------- ------------------------------------------------------ */ + /** + * Maps an absolute linear control value to a linear Mixxx control value. + * This function is useful for mapping physical control inputs (e.g., knobs) to Mixxx control values that expect a linear range, such as volume (0..1). + * @param {number} value The control value to convert (e.g., the position of a knob). + * @param {number} low The lowest value of the Mixxx control (e.g., 0 for volume). + * @param {number} high The highest value of the Mixxx control (e.g., 1 for volume). + * @param {number} [min] The minimum value the control input can take (default is 0, typical for MIDI). + * @param {number} [max] The maximum value the control input can take (default is 127, typical for MIDI). + * @returns {number} The Mixxx control value corresponding to the current position of the control input. + */ absoluteLin(value, low, high, min = 0, max = 127) { if (value <= min) { return low; @@ -315,30 +354,33 @@ var script = Object.freeze({ } }, - /* -------- ------------------------------------------------------ - script.absoluteLinInverse - Purpose: Maps a linear Mixxx control value (like balance: -1..1) to an absolute linear value - (inverse of the above function) - Input: Control value (e.g. a knob,) MixxxControl values for the lowest and - highest points, lowest knob value, highest knob value - (Default knob values are standard MIDI 0..127) - Output: Linear value corresponding to the knob position - -------- ------------------------------------------------------ */ + /** + * Maps a linear Mixxx control value (e.g., balance: -1 to 1) to an absolute linear value. + * This function is the inverse of script.absoluteLin, converting a Mixxx control value back to a hardware control value. + * @param {number} value The control value to convert (e.g., a balance control value between -1 and 1). + * @param {number} low The lowest value of the Mixxx control (e.g., -1 for balance). + * @param {number} high The highest value of the Mixxx control (e.g., 1 for balance). + * @param {number} [min] The minimum value the hardware control can take (default is 0, typical for MIDI). + * @param {number} [max] The maximum value the hardware control can take (default is 127, typical for MIDI). + * @returns {number} The absolute linear value corresponding to the Mixxx control value, scaled to the hardware control's range. + */ absoluteLinInverse(value, low, high, min = 0, max = 127) { const result = (((value - low) * (max - min)) / (high - low)) + min; return Math.min(Math.max(result, min), max); }, - /* -------- ------------------------------------------------------ - script.absoluteNonLin - Purpose: Maps an absolute linear control value to a non-linear Mixxx control - value (like EQs: 0..1..4) - Input: Control value (e.g. a knob,) MixxxControl values for the lowest, - middle, and highest points, lowest knob value, highest knob value - (Default knob values are standard MIDI 0..127) - Output: MixxxControl value corresponding to the knob position - -------- ------------------------------------------------------ */ + /** + * Maps an absolute linear control value to a non-linear Mixxx control value. + * This function is useful for mapping physical control inputs (e.g., knobs) to Mixxx control values that have a non-linear response, such as EQs. + * @param {number} value The current value of the control input (e.g., the position of a knob). + * @param {number} low The lowest value of the Mixxx control. + * @param {number} mid The middle point of the Mixxx control. + * @param {number} high The highest value of the Mixxx control. + * @param {number} [min] The minimum value the hardware control can take (default is 0, typical for MIDI). + * @param {number} [max] The maximum value the hardware control can take (default is 127, typical for MIDI). + * @returns {number} The value corresponding to the current position of the control input, adjusted for a non-linear response. + */ absoluteNonLin(value, low, mid, high, min = 0, max = 127) { if (!min) { min = 0; @@ -356,14 +398,17 @@ var script = Object.freeze({ } }, - /* -------- ------------------------------------------------------ - script.absoluteNonLinInverse - Purpose: Maps a non-linear Mixxx control to an absolute linear value (inverse of the above function). - Helpful for sending MIDI messages to controllers and comparing non-linear Mixxx controls to incoming MIDI values. - Input: MixxxControl value; lowest, middle, and highest MixxxControl value; - bottom of output range, top of output range. (Default output range is standard MIDI 0..127) - Output: MixxxControl value scaled to output range - -------- ------------------------------------------------------ */ + /** + * Maps a non-linear Mixxx control to an absolute linear value. + * This function is the inverse of script.absoluteNonLin, useful for sending MIDI messages to controllers and comparing non-linear Mixxx controls to incoming MIDI values. + * @param {number} value The current value of the control input (e.g., the position of a knob). + * @param {number} low The lowest value of the Mixxx control. + * @param {number} mid The middle point of the Mixxx control. + * @param {number} high The highest value of the Mixxx control. + * @param {number} [min] The minimum value the hardware control can take (default is 0, typical for MIDI). + * @param {number} [max] The maximum value the hardware control can take (default is 127, typical for MIDI). + * @returns {number} The value corresponding to the current position of the control input, adjusted for a inverse non-linear response. + */ absoluteNonLinInverse(value, low, mid, high, min = 0, max = 127) { const center = (max - min) / 2; let result; @@ -379,12 +424,14 @@ var script = Object.freeze({ return Math.min(Math.max(result, min), max); }, - /* -------- ------------------------------------------------------ - script.crossfaderCurve - Purpose: Adjusts the cross-fader's curve using a hardware control - Input: Current value of the hardware control, min and max values for that control - Output: none - -------- ------------------------------------------------------ */ + /** + * Adjusts the crossfader curve based on the current value of a hardware control. + * This function supports both Constant Power and Additive modes for the crossfader curve. + * It maps the hardware control value to a Mixxx control value using a linear mapping. + * @param {number} value The current value of the hardware control. + * @param {number} min The minimum value for the hardware control. + * @param {number} max The maximum value for the hardware control. + */ crossfaderCurve(value, min, max) { if (engine.getValue("[Mixer Profile]", "xFaderMode") === 1) { // Constant Power @@ -399,26 +446,24 @@ var script = Object.freeze({ } }, - /* -------- ------------------------------------------------------ - script.posMod - Purpose: Computes the euclidean modulo of m % n. The result is always - in the range [0, m[ - Input: dividend `a` and divisor `m` for modulo (a % m) - Output: positive remainder - -------- ------------------------------------------------------ */ + /** + * Computes the euclidean modulo of a given m % n. The result is always + * in the range [0, m] + * @param {number} a The dividend. + * @param {number} m The divisor. + * @returns {number} The positive remainder of the euclidean modulo operation. + */ posMod(a, m) { return ((a % m) + m) % m; }, - /* -------- ------------------------------------------------------ - script.loopMove - Purpose: Moves the current loop by the specified number of beats (default 1/2) - in the specified direction (positive is forwards and is the default) - If the current loop length is shorter than the requested move distance, - it's only moved a distance equal to its length. - Input: MixxxControl group, direction to move, number of beats to move - Output: none - -------- ------------------------------------------------------ */ + /** + * Moves the current loop by the specified number of beats (default 1/2) in the specified direction. + * If the current loop length is shorter than the requested move distance, it's only moved a distance equal to its length. + * @param {string} group The Mixxx control group (e.g., "[Channel1]", "[Master]") the loop belongs to. + * @param {number} [direction] The direction to move the loop. Positive direction value moves the loop forwards, negative value moves it backwards. Forward is default. + * @param {number} [numberOfBeats] The number of beats to move the loop. Defaults to 0.5 beats. + */ loopMove(group, direction, numberOfBeats = 0.5) { if (numberOfBeats === 0) { numberOfBeats = 0.5; } @@ -429,15 +474,14 @@ var script = Object.freeze({ } }, - /* -------- ------------------------------------------------------ - script.midiPitch - Purpose: Takes the value from a little-endian 14-bit MIDI pitch - wheel message and returns the value for a "rate" (pitch - slider) Mixxx control - Input: Least significant byte, most sig. byte, MIDI status byte - Output: Value for a "rate" control, or false if the input MIDI - message was not a Pitch message (0xE#) - -------- ------------------------------------------------------ */ + /** + * Takes the value from a little-endian 14-bit MIDI pitch wheel message and returns the value for a "rate" (pitch slider) Mixxx control. + * This function is useful for handling MIDI pitch wheel messages and converting them to a format that can be used to control the pitch in Mixxx. + * @param {number} LSB The least significant byte of the MIDI pitch wheel message. + * @param {number} MSB The most significant byte of the MIDI pitch wheel message. + * @param {number} status The MIDI status byte, used to verify that the message is a pitch wheel message. + * @returns {number|boolean} The value for a "rate" control in Mixxx, or false if the input MIDI message was not a Pitch message (0xE#). + */ // TODO: Is this still useful now that MidiController.cpp properly handles these? midiPitch(LSB, MSB, status) { if ((status & 0xF0) !== 0xE0) { // Mask the upper nybble so we can check the opcode regardless of the channel @@ -452,14 +496,18 @@ var script = Object.freeze({ return rate; }, - /* -------- ------------------------------------------------------ - script.spinback - Purpose: wrapper around engine.spinback() that can be directly mapped - from xml for a spinback effect - e.g: script.spinback - Input: channel, control, value, status, group, factor (optional), start rate (optional) - Output: none - -------- ------------------------------------------------------ */ + /** + * Wrapper around engine.spinback() that can be directly mapped from XML for a spinback effect. + * This function initiates a spinback effect on the specified deck. The spinback effect simulates the sound of quickly + * reversing the direction of a playing track. The duration and speed of the spinback can be adjusted. + * @param {number} channel not used + * @param {number} control not used + * @param {number} value Positive value activates spinback + * @param {number} status The status of the MIDI message, if the MSB bit 0x80 of the byte is not set, value is evaluated. + * @param {string} group The group + * @param {number} [factor] Multiplier for the spinback effect. Higher values increase the speed and duration of the spinback. Default is 1.0. + * @param {number} [rate] Rate at which the spinback starts. Can be used to simulate different starting speeds for the effect. Default is 0.0. + */ spinback(channel, control, value, status, group, factor = 1, rate = -10) { // disable on note-off or zero value note/cc engine.spinback( @@ -468,14 +516,16 @@ var script = Object.freeze({ ); }, - /* -------- ------------------------------------------------------ - script.brake - Purpose: wrapper around engine.brake() that can be directly mapped - from xml for a brake effect - e.g: script.brake - Input: channel, control, value, status, group, factor (optional) - Output: none - -------- ------------------------------------------------------ */ + /** + * Wrapper around engine.brake() that can be directly mapped from XML for a brake effect. + * Initiates a brake effect on the specified deck, simulating the sound of a turntable slowing to a stop. + * @param {number} channel not used + * @param {number} control not used + * @param {number} value Positive value activates brake + * @param {number} status The status of the MIDI message, if the MSB bit 0x80 of the byte is not set, value is evaluated. + * @param {string} group The group where the deck number is derived from, e.g. "[Channel1]" + * @param {number} [factor] Multiplier for the brake effect. Higher values increase the duration of the brake effect. + */ brake(channel, control, value, status, group, factor = 1) { // disable on note-off or zero value note/cc, use default decay rate '1' engine.brake( @@ -484,15 +534,16 @@ var script = Object.freeze({ ); }, - /* -------- ------------------------------------------------------ - script.softStart - Purpose: wrapper around engine.softStart() that can be directly mapped - from xml to start and accelerate a deck from zero to full rate - defined by pitch slider, can also interrupt engine.brake() - e.g: script.softStart - Input: channel, control, value, status, group, acceleration factor (optional) - Output: none - -------- ------------------------------------------------------ */ + /** + * Wrapper around engine.softStart() that can be directly mapped from XML to start and accelerate a deck from zero to full rate defined by the pitch slider. It can also interrupt engine.brake(). + * This function is useful for creating a soft start effect on a deck, gradually increasing its playback speed to the rate determined by the pitch slider. + * @param {number} channel not used + * @param {number} control not used + * @param {number} value Positive value activates spinback + * @param {number} status The status of the MIDI message, if the MSB bit 0x80 of the byte is not set, value is evaluated. + * @param {string} group The group where the deck number is derived from, e.g. "[Channel1]" + * @param {number} [factor] Multiplier for the acceleration effect. Higher values increase the acceleration of the soft start effect. + */ softStart(channel, control, value, status, group, factor = 1) { // disable on note-off or zero value note/cc, use default increase rate '1' engine.softStart( @@ -504,7 +555,9 @@ var script = Object.freeze({ }); -// bpm - Used for tapping the desired BPM for a deck +/** + * Used for tapping the desired BPM for a deck + */ // @ts-ignore Same identifier for class and instance needed for backward compatibility class bpmClass { constructor() { @@ -514,15 +567,13 @@ class bpmClass { } - /* -------- ------------------------------------------------------ - this.tapButton - Purpose: Sets the tempo of the track on a deck by tapping the desired beats, - useful for manually synchronizing a track to an external beat. - (This only works if the track's detected BPM value is correct.) - Call this each time the tap button is pressed. - Input: Mixxx deck to adjust - Output: - - -------- ------------------------------------------------------ */ + /** + * Sets the tempo of the track on a deck by tapping the desired beats. + * Useful for manually synchronizing a track to an external beat. + * This function only works if the track's detected BPM value is correct. + * Call this function each time the tap button is pressed. + * @param {number} deck The deck to adjust. + */ tapButton(deck) { const now = new Date().getTime() / 1000; // Current time in seconds const tapDelta = now - this.tapTime;