From 9909b16ef4e5744c5f5e61d70691d22aaaf74550 Mon Sep 17 00:00:00 2001 From: NotYourAverageAl Date: Thu, 30 Jun 2022 21:28:09 +1000 Subject: [PATCH] feat(controllers): add new controller mapping for Numark Scratch Co-Authored-By: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- res/controllers/Numark-Scratch-scripts.js | 396 ++++++++++ res/controllers/Numark-Scratch.midi.xml | 921 ++++++++++++++++++++++ 2 files changed, 1317 insertions(+) create mode 100644 res/controllers/Numark-Scratch-scripts.js create mode 100644 res/controllers/Numark-Scratch.midi.xml diff --git a/res/controllers/Numark-Scratch-scripts.js b/res/controllers/Numark-Scratch-scripts.js new file mode 100644 index 00000000000..fb5d7c2ac18 --- /dev/null +++ b/res/controllers/Numark-Scratch-scripts.js @@ -0,0 +1,396 @@ +// eslint-disable-next-line no-var +var NumarkScratch = {}; + +/* + * USER CONFIGURABLE SETTINGS. + */ + +// Defines the Beatloop Roll sizes for the 4 pads, for available values see: +// https://manual.mixxx.org/2.4/en/chapters/appendix/mixxx_controls.html#control-[ChannelN]-beatlooproll_X_activate +// Default: [0.25, 0.5, 1, 2] +NumarkScratch.autoLoopSizes = [ + "0.25", + "0.5", + "1", + "2", +]; + +// Defines how the Loop Encoder functions. +// If 'true' the Encoder scrolls the library/loads track. Shift + Encoder manages looping. +// If 'false' the Encoder manages looping. Shift + Encoder scrolls the library/loads track (Serato default). +// Default: false +NumarkScratch.invertLoopEncoderFunction = false; + +// Defines the bightness of button LEDs when they inactive. +// '0x00' sets the LEDSs to completely off. '0x01 sets the LEDs to dim. +// Default: 0x01 (dim) +NumarkScratch.LOW_LIGHT = 0x01; + +/* + * CODE + */ + + +components.Button.prototype.off = NumarkScratch.LOW_LIGHT; + +NumarkScratch.init = function() { + // Initialize component containers + NumarkScratch.deck = new components.ComponentContainer(); + NumarkScratch.effect = new components.ComponentContainer(); + for (let i = 0; i < 2; i++) { + NumarkScratch.deck[i] = new NumarkScratch.Deck(i + 1); + NumarkScratch.effect[i] = new NumarkScratch.EffectUnit(i + 1); + } + + NumarkScratch.xfader = new NumarkScratch.XfaderContainer(); + + const createVuCallback = deckOffset => value => midi.sendShortMsg(0xB0 + deckOffset, 0x1F, value * 90); + + engine.makeConnection("[Channel1]", "vu_meter", createVuCallback(0)); + engine.makeConnection("[Channel2]", "vu_meter", createVuCallback(1)); + + // Trigger is needed to initialize lights + NumarkScratch.deck.forEachComponent(component => component.trigger()); + NumarkScratch.effect.forEachComponent(component => component.trigger()); + + NumarkScratch.shiftButton = new components.Button({ + midi: [0x9F, 0x32], + input: function(channel, control, value) { + if (this.isPress(channel, control, value)) { + NumarkScratch.shift(); + this.send(this.on); + } else { + NumarkScratch.unshift(); + this.send(this.off); + } + }, + trigger: function() { + this.send(this.off); + } + }); + + NumarkScratch.shiftButton.trigger(); + + // Send Serato SysEx messages to request initial state and unlock pads + // Delay by 10sec for Mixxx to fully initialise (to accommodate channelInput) + engine.beginTimer(10000, function() { + midi.sendSysexMsg([0xF0, 0x00, 0x20, 0x7F, 0x00, 0xF7], 6); + }, true); + +}; + +NumarkScratch.shutdown = function() { + midi.sendSysexMsg([0xF0, 0x00, 0x20, 0x7F, 0x04, 0xF7]); +}; + +NumarkScratch.shift = function() { + NumarkScratch.deck.shift(); + NumarkScratch.effect.shift(); +}; + +NumarkScratch.unshift = function() { + NumarkScratch.deck.unshift(); + NumarkScratch.effect.unshift(); +}; + +NumarkScratch.EffectUnit = function(deckNumber) { + + const inputUnshifted = function(channel, control, value, _status, _group) { + if (!this.isPress(channel, control, value)) { + return; + } + // Enable the current button + engine.setValue(this.group, this.inKey, true); + + // Disable all other buttons in the same unit + NumarkScratch.effect[deckNumber - 1].effectButtons.forEach((effectButton) => { + if (effectButton !== this) { + engine.setValue(effectButton.group, effectButton.inKey, false); + } + }); + + // Disable all buttons in the other unit + NumarkScratch.effect[1 - (deckNumber - 1)].effectButtons.forEach((effectButton) => { + engine.setValue(effectButton.group, effectButton.inKey, false); + }); + }; + + this.effectButtons = []; + for (let i = 0; i < 3; i++) { + this.effectButtons[i] = new components.Button({ + group: `[EffectRack1_EffectUnit${deckNumber}_Effect${i + 1}]`, + midi: [0x98 + deckNumber - 1, (deckNumber - 1) * 3 + i], + inKey: "enabled", + outKey: "enabled", // Bind the LED state to the effect enabled state + shift: function() { + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.toggle; + }, + unshift: function() { + this.input = inputUnshifted; + }, + }); + } + + this.paddle = new components.Button({ + group: `[EffectRack1_EffectUnit${deckNumber}]`, + inKey: `group_[Channel${deckNumber}]_enable`, + inSetValue: function(value) { + engine.setValue("[EffectRack1_EffectUnit1]", this.inKey, value); + engine.setValue("[EffectRack1_EffectUnit2]", this.inKey, value); + } + }); + + this.dryWetKnob = new components.Pot({ + group: `[EffectRack1_EffectUnit${deckNumber}]`, + shift: function() { + this.inKey = "super1"; + }, + unshift: function() { + this.inKey = "mix"; + }, + }); +}; +NumarkScratch.EffectUnit.prototype = new components.ComponentContainer(); + +NumarkScratch.XfaderContainer = function() { + this.crossfader = new components.Pot({ + midi: [0xBF, 0x08], + group: "[Master]", + inKey: "crossfader", + }); + + this.setCurve = function(channel, control, value, _status, _group) { + switch (value) { + case 0x00: // Additive/Linear + engine.setValue("[Mixer Profile]", "xFaderMode", 0); + engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.4); + engine.setValue("[Mixer Profile]", "xFaderCurve", 0.9); + break; + case 0x7F: // Picnic Bench/Fast Cut + engine.setValue("[Mixer Profile]", "xFaderMode", 0); + engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.9); + engine.setValue("[Mixer Profile]", "xFaderCurve", 7.0); + break; + } + }; + + this.xFaderReverse = new components.Button({ + group: "[Mixer Profile]", + inKey: "xFaderReverse" + }); +}; + +NumarkScratch.XfaderContainer.prototype = new components.ComponentContainer(); + +NumarkScratch.setChannelInput = function(channel, control, value, _status, _group) { + const number = (control === 0x57) ? 1 : 2; + const channelgroup = `[Channel${number}]`; + + switch (value) { + case 0x00: // PC and turn on vinyl control + engine.setValue(channelgroup, "passthrough", 0); + engine.setValue(channelgroup, "vinylcontrol_enabled", 1); + break; + case 0x02: // PHONO/LINE and turn off vinyl control + engine.setValue(channelgroup, "passthrough", 1); + engine.setValue(channelgroup, "vinylcontrol_enabled", 0); + break; + } +}; + +NumarkScratch.Deck = function(number) { + components.Deck.call(this, number); + + const channel = number; + + this.pflButton = new components.Button({ + midi: [0x90 + channel, 0x1B], + key: "pfl", + output: function(value) { + const note = (value === 0x00 ? 0x80 : 0x90) + channel; + midi.sendShortMsg(note, 0x1B, this.outValueScale(value)); + } + }); + + this.volume = new components.Pot({ + inKey: "volume" + }); + + this.treble = new components.Pot({ + group: `[EqualizerRack1_${this.currentDeck}_Effect1]`, + inKey: "parameter3" + }); + + this.mid = new components.Pot({ + group: `[EqualizerRack1_${this.currentDeck}_Effect1]`, + inKey: "parameter2" + }); + + this.bass = new components.Pot({ + group: `[EqualizerRack1_${this.currentDeck}_Effect1]`, + inKey: "parameter1" + }); + + this.filter = new components.Pot({ + group: `[QuickEffectRack1_${this.currentDeck}]`, + inKey: "super1" + }); + + this.gain = new components.Pot({ + inKey: "pregain" + }); + + const encoderInvert = function(key) { + return NumarkScratch.invertLoopEncoderFunction ? (key === "unshift" ? "shift" : "unshift") : key; + }; + + this.loopPlus = new components.Encoder({ + midi: [0x93 + channel, 0x35], + [encoderInvert("unshift")]: function() { + this.group = `[Channel${ channel}]`; + this.inKey = "loop_double"; + }, + [encoderInvert("shift")]: function() { + this.group = "[Library]"; + this.inKey = "MoveDown"; + } + }); + + this.loopMinus = new components.Encoder({ + midi: [0x93 + channel, 0x34], + [encoderInvert("unshift")]: function() { + this.group = `[Channel${ channel}]`; + this.inKey = "loop_halve"; + }, + [encoderInvert("shift")]: function() { + this.group = "[Library]"; + this.inKey = "MoveUp"; + } + }); + + this.loopButton = new components.Button({ + midi: [0x93 + channel, 0x3F], + [encoderInvert("unshift")]: function() { + this.group = `[Channel${ channel}]`; + this.inKey = "beatloop_activate"; + }, + [encoderInvert("shift")]: function() { + this.group = `[Channel${ channel}]`; + this.inKey = "LoadSelectedTrack"; + } + }); + + this.padSection = new NumarkScratch.PadSection(number); + + this.reconnectComponents(function(component) { + if (component.group === undefined) { + component.group = this.currentDeck; + } + }); +}; + +NumarkScratch.Deck.prototype = new components.Deck(); + +// Pad modes control codes +NumarkScratch.PadModeControls = { + HOTCUE: 0x00, + SAMPLER: 0x07, + ROLL: 0x0B, +}; + +NumarkScratch.PadSection = function(deckNumber) { + components.ComponentContainer.call(this); + + this.modes = {}; + this.modes[NumarkScratch.PadModeControls.HOTCUE] = new NumarkScratch.ModeHotcue(deckNumber); + this.modes[NumarkScratch.PadModeControls.SAMPLER] = new NumarkScratch.ModeSampler(deckNumber); + this.modes[NumarkScratch.PadModeControls.ROLL] = new NumarkScratch.ModeRoll(deckNumber); + + this.modeButtonPress = function(channel, control, _value) { + this.setMode(channel, control); + }; + + this.padPress = function(channel, control, value, status, group) { + const i = (control - 0x14) % 8; + this.currentMode.pads[i].input(channel, control, value, status, group); + }; + + this.setMode = function(_channel, control) { + const newMode = this.modes[control]; + this.currentMode.forEachComponent(function(component) { + component.disconnect(); + }); + + newMode.forEachComponent(function(component) { + component.connect(); + component.trigger(); + }); + + this.currentMode = newMode; + }; + + this.currentMode = this.modes[NumarkScratch.PadModeControls.HOTCUE]; +}; +NumarkScratch.PadSection.prototype = Object.create(components.ComponentContainer.prototype); + +NumarkScratch.ModeHotcue = function(deckNumber) { + components.ComponentContainer.call(this); + + this.control = NumarkScratch.PadModeControls.HOTCUE; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 4; i++) { + this.pads[i] = new components.HotcueButton({ + group: `[Channel${deckNumber}]`, + midi: [0x93 + deckNumber, 0x14 + i], + number: i + 1, + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + outConnect: false + }); + } +}; +NumarkScratch.ModeHotcue.prototype = Object.create(components.ComponentContainer.prototype); + +NumarkScratch.ModeRoll = function(deckNumber) { + components.ComponentContainer.call(this); + + this.control = NumarkScratch.PadModeControls.ROLL; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 4; i++) { + this.pads[i] = new components.Button({ + group: `[Channel${deckNumber}]`, + midi: [0x93 + deckNumber, 0x14 + i], + size: NumarkScratch.autoLoopSizes[i], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + inKey: `beatlooproll_${NumarkScratch.autoLoopSizes[i]}_activate`, + outKey: `beatloop_${NumarkScratch.autoLoopSizes[i]}_enabled`, + outConnect: false + }); + } +}; +NumarkScratch.ModeRoll.prototype = Object.create(components.ComponentContainer.prototype); + +NumarkScratch.ModeSampler = function(deckNumber) { + components.ComponentContainer.call(this); + + this.control = NumarkScratch.PadModeControls.SAMPLER; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 4; i++) { + this.pads[i] = new components.SamplerButton({ + midi: [0x93 + deckNumber, 0x14 + i], + number: i + 1, + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + outConnect: false + }); + } +}; +NumarkScratch.ModeSampler.prototype = Object.create(components.ComponentContainer.prototype); diff --git a/res/controllers/Numark-Scratch.midi.xml b/res/controllers/Numark-Scratch.midi.xml new file mode 100644 index 00000000000..d550bf831a3 --- /dev/null +++ b/res/controllers/Numark-Scratch.midi.xml @@ -0,0 +1,921 @@ + + + + Numark Scratch + Al Hadebe(NotYourAverageAl) + Mapping for the Numark Scratch Mixer + NumarkScratch + NumarkScratch + + + + + + + + + + + [Master] + NumarkScratch.setChannelInput + CH 1 PC + 0x8F + 0x57 + + + + + + [Master] + NumarkScratch.setChannelInput + CH 1 LINE/PHONO + 0x9F + 0x57 + + + + + + + [Master] + NumarkScratch.setChannelInput + CH 2 PC + 0x8F + 0x60 + + + + + + [Master] + NumarkScratch.setChannelInput + CH 2 LINE/PHONO + 0x9F + 0x60 + + + + + + + [Master] + NumarkScratch.xfader.crossfader.input + 0xBF + 0x08 + + + + + + + [Master] + headMix + 0xBF + 0x0D + + + + + + + [Channel1] + NumarkScratch.deck[0].gain.input + 0xB0 + 0x16 + + + + + + [Channel2] + NumarkScratch.deck[1].gain.input + 0xB1 + 0x16 + + + + + + + [EqualizerRack1_[Channel1]] + NumarkScratch.deck[0].treble.input + 0xB0 + 0x17 + + + + + + [EqualizerRack1_[Channel2]] + NumarkScratch.deck[1].treble.input + 0xB1 + 0x17 + + + + + + [EqualizerRack1_[Channel1]] + NumarkScratch.deck[0].mid.input + 0xB0 + 0x18 + + + + + + [EqualizerRack1_[Channel2]] + NumarkScratch.deck[1].mid.input + 0xB1 + 0x18 + + + + + + [EqualizerRack1_[Channel1]] + NumarkScratch.deck[0].bass.input + 0xB0 + 0x19 + + + + + + [EqualizerRack1_[Channel2]] + NumarkScratch.deck[1].bass.input + 0xB1 + 0x19 + + + + + + + [QuickEffectRack1_[Channel1]] + NumarkScratch.deck[0].filter.input + 0xB0 + 0x1A + + + + + + [QuickEffectRack1_[Channel2]] + NumarkScratch.deck[1].filter.input + 0xB1 + 0x1A + + + + + + + [Channel1] + NumarkScratch.deck[0].volume.input + 0xB0 + 0x1C + + + + + + [Channel2] + NumarkScratch.deck[1].volume.input + 0xB1 + 0x1C + + + + + + + [Mixer Profile] + NumarkScratch.xfader.xFaderReverse.input + 0x9F + 0x1E + + + + + + + [Mixer Profile] + NumarkScratch.xfader.xFaderReverse.input + 0x8F + 0x1E + + + + + + + [Mixer Profile] + NumarkScratch.xfader.setCurve + 0xBF + 0x09 + + + + + + + [Master] + NumarkScratch.shiftButton.input + 0x9F + 0x32 + + + + + + + [Master] + NumarkScratch.shiftButton.input + 0x8F + 0x32 + + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.modeButtonPress + 0x94 + 0x00 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.modeButtonPress + 0x95 + 0x00 + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.modeButtonPress + 0x94 + 0x07 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.modeButtonPress + 0x95 + 0x07 + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.modeButtonPress + 0x94 + 0x0B + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.modeButtonPress + 0x95 + 0x0B + + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.modeButtonPress + 0x84 + 0x00 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.modeButtonPress + 0x85 + 0x00 + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.modeButtonPress + 0x84 + 0x07 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.modeButtonPress + 0x85 + 0x07 + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.modeButtonPress + 0x84 + 0x0B + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.modeButtonPress + 0x85 + 0x0B + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x14 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x14 + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x15 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x15 + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x16 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x16 + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x17 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x17 + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x14 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x14 + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x15 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x15 + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x16 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x16 + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x17 + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x17 + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x1C + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x1C + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x1D + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x1D + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x1E + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x1E + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x94 + 0x1F + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x95 + 0x1F + + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x1C + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x1C + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x1D + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x1D + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x1E + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x1E + + + + + + [Channel1] + NumarkScratch.deck[0].padSection.padPress + 0x84 + 0x1F + + + + + + [Channel2] + NumarkScratch.deck[1].padSection.padPress + 0x85 + 0x1F + + + + + + + [Channel1] + NumarkScratch.deck[0].loopMinus.input + 0x94 + 0x34 + + + + + + [Channel1] + NumarkScratch.deck[0].loopPlus.input + 0x94 + 0x35 + + + + + + [Channel2] + NumarkScratch.deck[1].loopMinus.input + 0x95 + 0x34 + + + + + + [Channel2] + NumarkScratch.deck[1].loopPlus.input + 0x95 + 0x35 + + + + + + [Channel1] + NumarkScratch.deck[0].loopButton.input + 0x94 + 0x3F + + + + + + [Channel2] + NumarkScratch.deck[1].loopButton.input + 0x95 + 0x3F + + + + + + + [Channel1] + NumarkScratch.deck[0].loopButton.input + 0x84 + 0x3F + + + + + + [Channel2] + NumarkScratch.deck[1].loopButton.input + 0x85 + 0x3F + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].dryWetKnob.input + 0xB8 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].dryWetKnob.input + 0xB9 + 0x04 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].effectButtons[0].input + 0x98 + 0x00 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].effectButtons[0].input + 0x88 + 0x00 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].effectButtons[1].input + 0x98 + 0x01 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].effectButtons[1].input + 0x88 + 0x01 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].effectButtons[2].input + 0x98 + 0x02 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].effectButtons[2].input + 0x88 + 0x02 + + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].effectButtons[0].input + 0x99 + 0x03 + + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].effectButtons[0].input + 0x89 + 0x03 + + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].effectButtons[1].input + 0x99 + 0x04 + + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].effectButtons[1].input + 0x89 + 0x04 + + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].effectButtons[2].input + 0x99 + 0x05 + + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].effectButtons[2].input + 0x89 + 0x05 + + + + + + + [EffectRack1_EffectUnit1] + NumarkScratch.effect[0].paddle.input + 0xB8 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + NumarkScratch.effect[1].paddle.input + 0xB9 + 0x03 + + + + + + + [Channel1] + NumarkScratch.deck[0].pflButton.input + 0x90 + 0x1B + + + + + + [Channel2] + NumarkScratch.deck[1].pflButton.input + 0x91 + 0x1B + + + + + + + [Channel1] + NumarkScratch.deck[0].pflButton.input + 0x80 + 0x1B + + + + + + [Channel2] + NumarkScratch.deck[1].pflButton.input + 0x81 + 0x1B + + + + + + +