diff --git a/res/controllers/Soundless_joyMIDI.midi.xml b/res/controllers/Soundless_joyMIDI.midi.xml
new file mode 100644
index 00000000000..cb3d68f138a
--- /dev/null
+++ b/res/controllers/Soundless_joyMIDI.midi.xml
@@ -0,0 +1,1227 @@
+
+
+
+ Soundless joyMIDI
+ Ozzy Chiu
+ Soundless Portable MIDI Controller
+ https://www.mixxx.org/forums/viewtopic.php?f=7&t=13166
+ https://www.mixxx.org/wiki/doku.php/soundless_studio_joymidi
+
+
+
+
+
+
+
+
+ [Master]
+ crossfader
+ 0xB0
+ 0x01
+
+
+
+
+
+ [Master]
+ joyMIDI.fsr
+ 0xB0
+ 0x02
+
+
+
+
+
+ [Master]
+ joyMIDI.joystick
+ 0xB0
+ 0x10
+
+
+
+
+
+ [Master]
+ joyMIDI.joystick
+ 0xB0
+ 0x11
+
+
+
+
+
+ [Master]
+ joyMIDI.joystick
+ 0xB0
+ 0x12
+
+
+
+
+
+ [Master]
+ joyMIDI.joystick
+ 0xB0
+ 0x13
+
+
+
+
+
+ [Master]
+ joyMIDI.gsensor
+ 0xB0
+ 0x14
+
+
+
+
+
+ [Master]
+ joyMIDI.gsensor
+ 0xB0
+ 0x15
+
+
+
+
+
+ [Master]
+ joyMIDI.gsensor
+ 0xB0
+ 0x16
+
+
+
+
+
+ [Master]
+ joyMIDI.gsensor
+ 0xB0
+ 0x17
+
+
+
+
+
+
+
+ [Channel1]
+ joyMIDI.wheel
+ 0xB0
+ 0x0E
+
+
+
+
+
+
+
+ [Channel2]
+ joyMIDI.wheel
+ 0xB0
+ 0x0F
+
+
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x90
+ 0x24
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x80
+ 0x24
+
+
+
+
+
+ [Channel1]
+ joyMIDI.syncButton
+ 0x90
+ 0x25
+
+
+
+
+
+ [Channel1]
+ joyMIDI.syncButton
+ 0x80
+ 0x25
+
+
+
+
+
+ [Channel1]
+ joyMIDI.cueButton
+ 0x90
+ 0x26
+
+
+
+
+
+ [Channel1]
+ joyMIDI.cueButton
+ 0x80
+ 0x26
+
+
+
+
+
+ [Channel1]
+ joyMIDI.playButton
+ 0x90
+ 0x27
+
+
+
+
+
+ [Channel1]
+ joyMIDI.playButton
+ 0x80
+ 0x27
+
+
+
+
+
+ [Channel1]
+ joyMIDI.scratchButton
+ 0x90
+ 0x2C
+
+
+
+
+
+ [Channel1]
+ joyMIDI.scratchButton
+ 0x80
+ 0x2C
+
+
+
+
+
+ [Channel1]
+ joyMIDI.keyButton
+ 0x90
+ 0x2D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.keyButton
+ 0x80
+ 0x2D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.beatgridButton
+ 0x90
+ 0x2E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.beatgridButton
+ 0x80
+ 0x2E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.volumeButton
+ 0x90
+ 0x2F
+
+
+
+
+
+ [Channel1]
+ joyMIDI.volumeButton
+ 0x80
+ 0x2F
+
+
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x90
+ 0x34
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x80
+ 0x34
+
+
+
+
+
+ [Channel1]
+ joyMIDI.filterLowButton
+ 0x90
+ 0x35
+
+
+
+
+
+ [Channel1]
+ joyMIDI.filterLowButton
+ 0x80
+ 0x35
+
+
+
+
+
+ [Channel1]
+ joyMIDI.filterMiddleButton
+ 0x90
+ 0x36
+
+
+
+
+
+ [Channel1]
+ joyMIDI.filterMiddleButton
+ 0x80
+ 0x36
+
+
+
+
+
+ [Channel1]
+ joyMIDI.filterHighButton
+ 0x90
+ 0x37
+
+
+
+
+
+ [Channel1]
+ joyMIDI.filterHighButton
+ 0x80
+ 0x37
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x3C
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x3C
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x3D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x3D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x3E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x3E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x3F
+
+
+
+
+
+ [Channel1]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x3F
+
+
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x90
+ 0x44
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x80
+ 0x44
+
+
+
+
+
+ [Channel1]
+ joyMIDI.reloopToggleButton
+ 0x90
+ 0x45
+
+
+
+
+
+ [Channel1]
+ joyMIDI.reloopToggleButton
+ 0x80
+ 0x45
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loopInButton
+ 0x90
+ 0x46
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loopInButton
+ 0x80
+ 0x46
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loopOutButton
+ 0x90
+ 0x47
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loopOutButton
+ 0x80
+ 0x47
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop0p125Button
+ 0x90
+ 0x4C
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop0p125Button
+ 0x80
+ 0x4C
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop0p25Button
+ 0x90
+ 0x4D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop0p25Button
+ 0x80
+ 0x4D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop0p5Button
+ 0x90
+ 0x4E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop0p5Button
+ 0x80
+ 0x4E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop1Button
+ 0x90
+ 0x4F
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loop1Button
+ 0x80
+ 0x4F
+
+
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x90
+ 0x54
+
+
+
+
+
+ [Channel1]
+ joyMIDI.shiftButton
+ 0x80
+ 0x54
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loadButton
+ 0x90
+ 0x55
+
+
+
+
+
+ [Channel1]
+ joyMIDI.loadButton
+ 0x80
+ 0x55
+
+
+
+
+
+ [Sampler1]
+ joyMIDI.sampler1Button
+ 0x90
+ 0x5C
+
+
+
+
+
+ [Sampler1]
+ joyMIDI.sampler1Button
+ 0x80
+ 0x5C
+
+
+
+
+
+ [Sampler2]
+ joyMIDI.sampler2Button
+ 0x90
+ 0x5D
+
+
+
+
+
+ [Sampler2]
+ joyMIDI.sampler2Button
+ 0x80
+ 0x5D
+
+
+
+
+
+ [Channel1]
+ joyMIDI.fx1Button
+ 0x90
+ 0x5E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.fx1Button
+ 0x80
+ 0x5E
+
+
+
+
+
+ [Channel1]
+ joyMIDI.fx2Button
+ 0x90
+ 0x5F
+
+
+
+
+
+ [Channel1]
+ joyMIDI.fx2Button
+ 0x80
+ 0x5F
+
+
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x90
+ 0x28
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x80
+ 0x28
+
+
+
+
+
+ [Channel2]
+ joyMIDI.syncButton
+ 0x90
+ 0x29
+
+
+
+
+
+ [Channel2]
+ joyMIDI.syncButton
+ 0x80
+ 0x29
+
+
+
+
+
+ [Channel2]
+ joyMIDI.cueButton
+ 0x90
+ 0x2A
+
+
+
+
+
+ [Channel2]
+ joyMIDI.cueButton
+ 0x80
+ 0x2A
+
+
+
+
+
+ [Channel2]
+ joyMIDI.playButton
+ 0x90
+ 0x2B
+
+
+
+
+
+ [Channel2]
+ joyMIDI.playButton
+ 0x80
+ 0x2B
+
+
+
+
+
+ [Channel2]
+ joyMIDI.scratchButton
+ 0x90
+ 0x30
+
+
+
+
+
+ [Channel2]
+ joyMIDI.scratchButton
+ 0x80
+ 0x30
+
+
+
+
+
+ [Channel2]
+ joyMIDI.keyButton
+ 0x90
+ 0x31
+
+
+
+
+
+ [Channel2]
+ joyMIDI.keyButton
+ 0x80
+ 0x31
+
+
+
+
+
+ [Channel2]
+ joyMIDI.beatgridButton
+ 0x90
+ 0x32
+
+
+
+
+
+ [Channel2]
+ joyMIDI.beatgridButton
+ 0x80
+ 0x32
+
+
+
+
+
+ [Channel2]
+ joyMIDI.volumeButton
+ 0x90
+ 0x33
+
+
+
+
+
+ [Channel2]
+ joyMIDI.volumeButton
+ 0x80
+ 0x33
+
+
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x90
+ 0x38
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x80
+ 0x38
+
+
+
+
+
+ [Channel2]
+ joyMIDI.filterLowButton
+ 0x90
+ 0x39
+
+
+
+
+
+ [Channel2]
+ joyMIDI.filterLowButton
+ 0x80
+ 0x39
+
+
+
+
+
+ [Channel2]
+ joyMIDI.filterMiddleButton
+ 0x90
+ 0x3A
+
+
+
+
+
+ [Channel2]
+ joyMIDI.filterMiddleButton
+ 0x80
+ 0x3A
+
+
+
+
+
+ [Channel2]
+ joyMIDI.filterHighButton
+ 0x90
+ 0x3B
+
+
+
+
+
+ [Channel2]
+ joyMIDI.filterHighButton
+ 0x80
+ 0x3B
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x40
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x40
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x41
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x41
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x42
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x42
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x90
+ 0x43
+
+
+
+
+
+ [Channel2]
+ joyMIDI.hotcueButton
+ 0x80
+ 0x43
+
+
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x90
+ 0x48
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x80
+ 0x48
+
+
+
+
+
+ [Channel2]
+ joyMIDI.reloopToggleButton
+ 0x90
+ 0x49
+
+
+
+
+
+ [Channel2]
+ joyMIDI.reloopToggleButton
+ 0x80
+ 0x49
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loopInButton
+ 0x90
+ 0x4A
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loopInButton
+ 0x80
+ 0x4A
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loopOutButton
+ 0x90
+ 0x4B
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loopOutButton
+ 0x80
+ 0x4B
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop0p125Button
+ 0x90
+ 0x50
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop0p125Button
+ 0x80
+ 0x50
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop0p25Button
+ 0x90
+ 0x51
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop0p25Button
+ 0x80
+ 0x51
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop0p5Button
+ 0x90
+ 0x52
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop0p5Button
+ 0x80
+ 0x52
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop1Button
+ 0x90
+ 0x53
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loop1Button
+ 0x80
+ 0x53
+
+
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x90
+ 0x58
+
+
+
+
+
+ [Channel2]
+ joyMIDI.shiftButton
+ 0x80
+ 0x58
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loadButton
+ 0x90
+ 0x59
+
+
+
+
+
+ [Channel2]
+ joyMIDI.loadButton
+ 0x80
+ 0x59
+
+
+
+
+
+ [Sampler3]
+ joyMIDI.sampler3Button
+ 0x90
+ 0x60
+
+
+
+
+
+ [Sampler3]
+ joyMIDI.sampler3Button
+ 0x80
+ 0x60
+
+
+
+
+
+ [Sampler4]
+ joyMIDI.sampler4Button
+ 0x90
+ 0x61
+
+
+
+
+
+ [Sampler4]
+ joyMIDI.sampler4Button
+ 0x80
+ 0x61
+
+
+
+
+
+ [Channel2]
+ joyMIDI.fx1Button
+ 0x90
+ 0x62
+
+
+
+
+
+ [Channel2]
+ joyMIDI.fx1Button
+ 0x80
+ 0x62
+
+
+
+
+
+ [Channel2]
+ joyMIDI.fx2Button
+ 0x90
+ 0x63
+
+
+
+
+
+ [Channel2]
+ joyMIDI.fx2Button
+ 0x80
+ 0x63
+
+
+
+
+
+
+
+
diff --git a/res/controllers/Soundless_joyMIDI_scripts.js b/res/controllers/Soundless_joyMIDI_scripts.js
new file mode 100644
index 00000000000..0b6f47e263c
--- /dev/null
+++ b/res/controllers/Soundless_joyMIDI_scripts.js
@@ -0,0 +1,710 @@
+/*******************************************************************
+ * Soundless studio.
+ * Author: Ozzy Chiu (ozzy.chc@gmail.com)
+ * Version: 2020-01-17
+ *
+ * Variables on joyMIDI.userSetting can be modified by users to suit their preferences
+ *******************************************************************/
+var joyMIDI = {
+
+ // User Setting
+ "userSetting": {
+ // Enable the knob(button+jog) function
+ "knobEnable": true,
+ // Enable the beatloop with roll(1) function otherwise toggle(0)
+ "beatLoopRollEnable": true,
+ // Enable the samplers played from cue(1) otherwise from track start(0)
+ "samplerCueModeEnable": true,
+ },
+
+ // Channel1 variable
+ "[Channel1]": {
+ btnShift: 0,
+ btnSync: 0, syncTimerID: null, syncLongPress: 0,
+ btnScratch: 0, isScratchPermanent: 0, scratchTimerID: 0,
+ btnKey: 0,
+ btnBeatgrid: 0,
+ btnVolume: 0,
+ btnFilter: {"low": 0, "middle": 0, "high": 0},
+ btnFx: {"1": 0, "2": 0},
+
+ connBeatIndicator: {},
+ },
+
+ // Channel2 variable
+ "[Channel2]": {
+ btnShift: 0,
+ btnSync: 0, syncTimerID: null, syncLongPress: 0,
+ btnScratch: 0, isScratchPermanent: 0, scratchTimerID: 0,
+ btnKey: 0,
+ btnBeatgrid: 0,
+ btnVolume: 0,
+ btnFilter: {"low": 0, "middle": 0, "high": 0},
+ btnFx: {"1": 0, "2": 0},
+
+ connBeatIndicator: {},
+ },
+};
+
+// Constant
+var RELEASED = 0,
+ PRESSED = 1,
+ ADJUSTED = 2;
+
+//----------------------------
+// Constants for scratching
+//----------------------------
+
+/* Internal jog-wheel */
+var forwardFinetune = 0;
+var backwardFinetune = 0;
+
+var intervalPerRev = 64;
+var rpm = 33 + 1 / 3;
+var alpha = 1 / 16;
+var beta = (1 / 12) / 32;
+
+var scratchDisableTime = 100;
+var enableAccVal = true;
+
+/* external jog-wheel */
+// var forwardFinetune = 0;
+// var backwardFinetune = 1;
+
+// var intervalPerRev = 1000;
+// var rpm = 33 + 1 / 3;
+// var alpha = 1.0 / 8;
+// var beta = alpha / 32;
+
+// var scratchDisableTime = 150;
+// var enableAccVal = false;
+
+//----------------------------
+// Common variable
+//----------------------------
+var joystickDelay = false;
+var fsrDelay = false;
+
+
+
+
+//==== Init, Shutdown ========================================================
+joyMIDI.init = function(_id, _debug) {
+ joyMIDI["[Channel1]"].connBeatIndicator = engine.makeConnection("[Channel1]", "beat_active", joyMIDI.onBeatIndicator);
+ joyMIDI["[Channel2]"].connBeatIndicator = engine.makeConnection("[Channel2]", "beat_active", joyMIDI.onBeatIndicator);
+};
+
+joyMIDI.shutdown = function() {
+ joyMIDI["[Channel1]"].connBeatIndicator.disconnect();
+ joyMIDI["[Channel2]"].connBeatIndicator.disconnect();
+};
+
+//==== Callback ==============================================================
+joyMIDI.onBeatIndicator = function(value, group, _control) {
+ var fader = engine.getParameter("[Master]", "crossfader");
+ if (fader < 0.5) {
+ // Left
+ if (group === "[Channel1]") {
+ midi.sendShortMsg(0x91, 0x00, (value) ? 0x7F : 0x00);
+ }
+ } else {
+ // Right
+ if (group === "[Channel2]") {
+ midi.sendShortMsg(0x92, 0x00, (value) ? 0x7F : 0x00);
+ }
+ }
+};
+
+//==== Button ================================================================
+joyMIDI.shiftButton = function(channel, control, value, status, group) {
+ joyMIDI[group].btnShift = (value > 0) ? PRESSED : RELEASED;
+};
+
+joyMIDI.syncButton = function(channel, control, value, status, group) {
+ if (!joyMIDI.userSetting.knobEnable) {
+ // knob disabled
+ if (value > 0) {
+ // press
+ if (!joyMIDI[group].btnShift) {
+ if (engine.getValue(group, "sync_enabled") > 0) {
+ engine.setValue(group, "sync_enabled", 0);
+ } else {
+ engine.setValue(group, "sync_enabled", 1);
+ if (joyMIDI[group].syncTimerID !== null) {
+ engine.stopTimer(joyMIDI[group].syncTimerID);
+ }
+ joyMIDI[group].syncLongPress = 0;
+ joyMIDI[group].syncTimerID =
+ engine.beginTimer(1000, function() { joyMIDI.syncTimerHandler(group); }, true);
+ }
+ } else {
+ engine.setValue(group, "rate", 0);
+ }
+ } else {
+ // release
+ if (!joyMIDI[group].btnShift) {
+ if (joyMIDI[group].syncTimerID !== null) {
+ if (joyMIDI[group].syncLongPress === 0 && engine.getValue(group, "sync_enabled") === 1) {
+ engine.stopTimer(joyMIDI[group].syncTimerID);
+ engine.setValue(group, "sync_enabled", 0);
+ }
+ joyMIDI[group].syncTimerID = null;
+ }
+ }
+ }
+ } else {
+ // knob enabled
+ if (value > 0) {
+ // press
+ joyMIDI[group].btnSync = PRESSED;
+ } else {
+ // release
+ if (joyMIDI[group].btnSync !== ADJUSTED) {
+ if (!joyMIDI[group].btnShift) {
+ engine.setValue(group, "beatsync", 1);
+ } else {
+ engine.setValue(group, "rate", 0);
+ }
+ }
+ joyMIDI[group].btnSync = RELEASED;
+ }
+ }
+};
+
+joyMIDI.syncTimerHandler = function(group) {
+ joyMIDI[group].syncLongPress = 1;
+};
+
+joyMIDI.cueButton = function(channel, control, value, status, group) {
+ var sft = joyMIDI[group].btnShift;
+ var cmd = sft ? "start" : "cue_default";
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.playButton = function(channel, control, value, status, group) {
+ var sft = joyMIDI[group].btnShift;
+ var cmd = sft ? "reverse" : "play";
+
+ if (value > 0) {
+ script.toggleControl(group, cmd);
+ }
+};
+
+joyMIDI.scratchButton = function(channel, control, value, status, group) {
+ if (value > 0) {
+ joyMIDI[group].isScratchPermanent = !joyMIDI[group].isScratchPermanent;
+ }
+};
+
+joyMIDI.keyButton = function(channel, control, value, status, group) {
+ if (!joyMIDI.userSetting.knobEnable) {
+ // knob disabled
+ if (value > 0) {
+ // press
+ if (!joyMIDI[group].btnShift) {
+ engine.setValue(group, "sync_key", 1);
+ } else {
+ engine.setValue(group, "reset_key", 1);
+ }
+ } else {
+ // release
+ }
+ } else {
+ // knob enabled
+ if (value > 0) {
+ // press
+ joyMIDI[group].btnKey = PRESSED;
+ } else {
+ // release
+ if (joyMIDI[group].btnKey !== ADJUSTED) {
+ if (joyMIDI[group].btnShift === RELEASED) {
+ engine.setValue(group, "sync_key", 1);
+ } else {
+ engine.setValue(group, "reset_key", 1);
+ }
+ }
+ joyMIDI[group].btnKey = RELEASED;
+ }
+ }
+};
+
+joyMIDI.beatgridButton = function(channel, control, value, status, group) {
+ if (!joyMIDI.userSetting.knobEnable) {
+ // knob disabled
+ if (value > 0) {
+ // press
+ if (!joyMIDI[group].btnShift) {
+ engine.setValue(group, "beats_translate_curpos", 1);
+ engine.setValue(group, "beats_translate_curpos", 0);
+ } else {
+ engine.setValue(group, "beats_translate_match_alignment", 1);
+ engine.setValue(group, "beats_translate_match_alignment", 0);
+ }
+ }
+ } else {
+ // knob enabled
+ if (value > 0) {
+ // press
+ joyMIDI[group].btnBeatgrid = PRESSED;
+ } else {
+ // release
+ if (joyMIDI[group].btnBeatgrid !== ADJUSTED) {
+ if (joyMIDI[group].btnShift === RELEASED) {
+ engine.setValue(group, "beats_translate_curpos", 1);
+ engine.setValue(group, "beats_translate_curpos", 0);
+ } else {
+ engine.setValue(group, "beats_translate_match_alignment", 1);
+ engine.setValue(group, "beats_translate_match_alignment", 0);
+ }
+ }
+ joyMIDI[group].btnBeatgrid = RELEASED;
+ }
+ }
+};
+
+joyMIDI.volumeButton = function(channel, control, value, status, group) {
+ if (!joyMIDI.userSetting.knobEnable) {
+ // knob disabled
+ if (value > 0) {
+ // press
+ if (!joyMIDI[group].btnShift) {
+ engine.setValue(group, "volume", 1.0);
+ }
+ }
+ } else {
+ // knob enabled
+ if (value > 0) {
+ // press
+ joyMIDI[group].btnVolume = PRESSED;
+ } else {
+ // release
+ if (joyMIDI[group].btnVolume !== ADJUSTED) {
+ if (joyMIDI[group].btnShift === RELEASED) {
+ engine.setValue(group, "volume", 1.0);
+ }
+ }
+ joyMIDI[group].btnVolume = RELEASED;
+ }
+ }
+};
+
+joyMIDI.filterLowButton = function(channel, control, value, status, group) {
+ joyMIDI.filterButton(channel, control, value, status, group,
+ "[EqualizerRack1_" + group + "_Effect1]", "button_parameter1", "low");
+};
+
+joyMIDI.filterMiddleButton = function(channel, control, value, status, group) {
+ joyMIDI.filterButton(channel, control, value, status, group,
+ "[EqualizerRack1_" + group + "_Effect1]", "button_parameter2", "middle");
+};
+
+joyMIDI.filterHighButton = function(channel, control, value, status, group) {
+ joyMIDI.filterButton(channel, control, value, status, group,
+ "[EqualizerRack1_" + group + "_Effect1]", "button_parameter3", "high");
+};
+
+joyMIDI.filterButton = function(channel, control, value, status, group, group2, param, select) {
+ if (!joyMIDI.userSetting.knobEnable) {
+ // knob disabled
+ if (value > 0) {
+ // press
+ if (!joyMIDI[group].btnShift) {
+ script.toggleControl(group2, param);
+ }
+ }
+ } else {
+ // knob enabled
+ if (value > 0) {
+ // press
+ joyMIDI[group].btnFilter[select] = PRESSED;
+ } else {
+ // release
+ if (joyMIDI[group].btnFilter[select] !== ADJUSTED) {
+ if (joyMIDI[group].btnShift === RELEASED) {
+ script.toggleControl(group2, param);
+ }
+ }
+ joyMIDI[group].btnFilter[select] = RELEASED;
+ }
+ }
+};
+
+joyMIDI.hotcueButton = function(channel, control, value, status, group) {
+ var num = control - ((group === "[Channel1]") ? 0x3B : 0x3F);
+ var sft = joyMIDI[group].btnShift;
+ var cmd = "hotcue_" + num + (sft ? "_clear" : "_activate");
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.reloopToggleButton = function(channel, control, value, status, group) {
+ var usr = joyMIDI.userSetting.beatLoopRollEnable;
+ var sft = joyMIDI[group].btnShift;
+ var cmd = sft ? (usr?"beatlooproll_activate":"beatloop_activate") : "reloop_toggle";
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.loopInButton = function(channel, control, value, status, group) {
+ var sft = joyMIDI[group].btnShift;
+ var cmd = sft ? "loop_halve" : "loop_in";
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.loopOutButton = function(channel, control, value, status, group) {
+ var sft = joyMIDI[group].btnShift;
+ var cmd = sft ? "loop_double" : "loop_out";
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.loop0p125Button = function(channel, control, value, status, group) {
+ joyMIDI.loopXButton(channel, control, value, status, group,
+ ["beatloop_0.125_toggle", "beatloop_2_toggle",
+ "beatlooproll_0.125_activate", "beatlooproll_2_activate"]);
+};
+
+joyMIDI.loop0p25Button = function(channel, control, value, status, group) {
+ joyMIDI.loopXButton(channel, control, value, status, group,
+ ["beatloop_0.25_toggle", "beatloop_4_toggle",
+ "beatlooproll_0.25_activate", "beatlooproll_4_activate"]);
+};
+
+joyMIDI.loop0p5Button = function(channel, control, value, status, group) {
+ joyMIDI.loopXButton(channel, control, value, status, group,
+ ["beatloop_0.5_toggle", "beatloop_8_toggle",
+ "beatlooproll_0.5_activate", "beatlooproll_8_activate"]);
+};
+
+joyMIDI.loop1Button = function(channel, control, value, status, group) {
+ joyMIDI.loopXButton(channel, control, value, status, group,
+ ["beatloop_1_toggle", "beatloop_16_toggle",
+ "beatlooproll_1_activate", "beatlooproll_16_activate"]);
+};
+
+joyMIDI.loopXButton = function(channel, control, value, status, group, commands) {
+ var usr = joyMIDI.userSetting.beatLoopRollEnable;
+ var sft = joyMIDI[group].btnShift;
+ var cmd = commands[sft + (usr?2:0)];
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.loadButton = function(channel, control, value, status, group) {
+ var sft = joyMIDI[group].btnShift;
+ var cmd = sft ? "eject" : "LoadSelectedTrack";
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.sampler1Button = function(channel, control, value, status, group) {
+ joyMIDI.samplerButton(channel, control, value, status, group, 1);
+};
+
+joyMIDI.sampler2Button = function(channel, control, value, status, group) {
+ joyMIDI.samplerButton(channel, control, value, status, group, 2);
+};
+
+joyMIDI.sampler3Button = function(channel, control, value, status, group) {
+ joyMIDI.samplerButton(channel, control, value, status, group, 3);
+};
+
+joyMIDI.sampler4Button = function(channel, control, value, status, group) {
+ joyMIDI.samplerButton(channel, control, value, status, group, 4);
+};
+
+joyMIDI.samplerButton = function(channel, control, value, status, group, number) {
+ var commands = ["start_play", "start_stop", "cue_gotoandplay", "cue_default"];
+ var usr = joyMIDI.userSetting.samplerCueModeEnable;
+ var sft = joyMIDI["[Channel"+ ((number > 2) ? 2 : 1) +"]"].btnShift;
+ var cmd = commands[sft + (usr?2:0)];
+ var val = (value > 0) ? 1 : 0;
+
+ engine.setValue(group, cmd, val);
+};
+
+joyMIDI.fx1Button = function(channel, control, value, status, group) {
+ joyMIDI.fxButton(channel, control, value, status, group,
+ "[EffectRack1_EffectUnit1]", "group_"+group+"_enable", 1);
+};
+
+joyMIDI.fx2Button = function(channel, control, value, status, group) {
+ joyMIDI.fxButton(channel, control, value, status, group,
+ "[EffectRack1_EffectUnit2]", "group_"+group+"_enable", 2);
+};
+
+joyMIDI.fxButton = function(channel, control, value, status, group, group2, param, select) {
+ if (!joyMIDI.userSetting.knobEnable) {
+ // knob disabled
+ if (value > 0) {
+ // press
+ if (!joyMIDI[group].btnShift) {
+ script.toggleControl(group2, param);
+ }
+ }
+ } else {
+ // knob enabled
+ if (value > 0) {
+ // press
+ joyMIDI[group].btnFx[select] = PRESSED;
+ } else {
+ // release
+ if (joyMIDI[group].btnFx[select] !== ADJUSTED) {
+ if (joyMIDI[group].btnShift === RELEASED) {
+ script.toggleControl(group2, param);
+ }
+ }
+ joyMIDI[group].btnFx[select] = RELEASED;
+ }
+ }
+};
+
+//==== Wheel ============================================================
+joyMIDI.wheel = function(channel, control, value, status, group) {
+ if (joyMIDI[group].btnSync >= PRESSED) {
+ joyMIDI.wheelSync(channel, control, value, status, group);
+ joyMIDI[group].btnSync = ADJUSTED;
+ } else if (joyMIDI[group].btnKey >= PRESSED) {
+ joyMIDI.wheelKey(channel, control, value, status, group);
+ joyMIDI[group].btnKey = ADJUSTED;
+ } else if (joyMIDI[group].btnBeatgrid >= PRESSED) {
+ joyMIDI.wheelBeatgrid(channel, control, value, status, group);
+ joyMIDI[group].btnBeatgrid = ADJUSTED;
+ } else if (joyMIDI[group].btnVolume >= PRESSED) {
+ joyMIDI.wheelVolume(channel, control, value, status, group);
+ joyMIDI[group].btnVolume = ADJUSTED;
+
+ } else if (joyMIDI[group].btnFilter["low"] >= PRESSED) {
+ joyMIDI.wheelFilterLow(channel, control, value, status, group);
+ joyMIDI[group].btnFilter["low"] = ADJUSTED;
+ } else if (joyMIDI[group].btnFilter["middle"] >= PRESSED) {
+ joyMIDI.wheelFilterMiddle(channel, control, value, status, group);
+ joyMIDI[group].btnFilter["middle"] = ADJUSTED;
+ } else if (joyMIDI[group].btnFilter["high"] >= PRESSED) {
+ joyMIDI.wheelFilterHigh(channel, control, value, status, group);
+ joyMIDI[group].btnFilter["high"] = ADJUSTED;
+
+ } else if (joyMIDI[group].btnFx["1"] >= PRESSED) {
+ joyMIDI.wheelFx1(channel, control, value, status, group);
+ joyMIDI[group].btnFx["1"] = ADJUSTED;
+ } else if (joyMIDI[group].btnFx["2"] >= PRESSED) {
+ joyMIDI.wheelFx2(channel, control, value, status, group);
+ joyMIDI[group].btnFx["2"] = ADJUSTED;
+ } else if (joyMIDI[group].btnFx["3"] >= PRESSED) {
+ joyMIDI.wheelFx3(channel, control, value, status, group);
+ joyMIDI[group].btnFx["3"] = ADJUSTED;
+
+ } else if (joyMIDI[group].isScratchPermanent) {
+ joyMIDI.wheelScratch(channel, control, value, status, group);
+ } else {
+ joyMIDI.wheelPitchBend(channel, control, value, status, group);
+ }
+};
+
+joyMIDI.wheelPitchBend = function(channel, control, value, status, group) {
+ var newValue = joyMIDI.helperAccel(value - 64);
+
+ engine.setValue(group, "jog", newValue);
+ print(group + "jog=" + newValue);
+};
+
+joyMIDI.wheelScratch = function(channel, control, value, status, group) {
+ var deck = script.deckFromGroup(group);
+ var newValue = joyMIDI.helperAccel(value - 64);
+
+ if (newValue > 0) newValue = newValue + forwardFinetune;
+ else newValue = newValue - backwardFinetune;
+
+ if (joyMIDI[group].scratchTimerID) {
+ engine.stopTimer(joyMIDI[group].scratchTimerID);
+ }
+
+ // if (!engine.isScratching(deck)) {
+ engine.scratchEnable(deck, intervalPerRev, rpm, alpha, beta);
+ // }
+
+ engine.scratchTick(deck, newValue);
+
+ joyMIDI[group].scratchTimerID =
+ engine.beginTimer(scratchDisableTime, function() { joyMIDI.ScratchTimerHandler(deck, group); }, true);
+};
+
+joyMIDI.ScratchTimerHandler = function(deck, group) {
+ engine.scratchDisable(deck);
+ joyMIDI[group].scratchTimerID = 0;
+};
+
+joyMIDI.wheelSync = function(channel, control, value, status, group) {
+ var delta = joyMIDI.helperAccel(value - 64) * 0.01;
+ var oldValue = engine.getValue(group, "rate");
+ var newValue = oldValue + delta;
+
+ newValue = joyMIDI.helperLimit(newValue, 1.0, -1.0);
+ engine.setValue(group, "rate", newValue);
+ print(group + "rate=" + newValue);
+};
+
+joyMIDI.wheelKey = function(channel, control, value, status, group) {
+ var delta = /*joyMIDI.helperAccel*/(value - 64) * 0.2;
+ var oldValue = engine.getValue(group, "pitch");
+ var newValue = oldValue + delta;
+
+ newValue = joyMIDI.helperLimit(newValue, 6.0, -6.0);
+ engine.setValue(group, "pitch", newValue);
+ print(group + "pitch=" + newValue);
+};
+
+joyMIDI.wheelBeatgrid = function(channel, control, value, status, group) {
+ var delta = joyMIDI.helperAccel(value - 64);
+ var count = Math.abs(delta);
+
+ for (var i=0; i 0) {
+ engine.setValue(group, "beats_translate_earlier", 1);
+ print(group + "beats_translate_earlier");
+ } else {
+ engine.setValue(group, "beats_translate_later", 1);
+ print(group + "beats_translate_later");
+ }
+ }
+};
+
+joyMIDI.wheelVolume = function(channel, control, value, status, group) {
+ var delta = joyMIDI.helperAccel(value - 64) * 0.01;
+ var oldValue = engine.getValue(group, "volume");
+ var newValue = oldValue + delta;
+
+ newValue = joyMIDI.helperLimit(newValue, 1.0, 0);
+ engine.setValue(group, "volume", newValue);
+ print(group + "volume=" + newValue);
+};
+
+joyMIDI.wheelFilterLow = function(channel, control, value, status, group) {
+ joyMIDI.wheelFilter(channel, control, value, status, group,
+ "[EqualizerRack1_" + group + "_Effect1]", "parameter1", "low");
+};
+
+joyMIDI.wheelFilterMiddle = function(channel, control, value, status, group) {
+ joyMIDI.wheelFilter(channel, control, value, status, group,
+ "[EqualizerRack1_" + group + "_Effect1]", "parameter2", "middle");
+};
+
+joyMIDI.wheelFilterHigh = function(channel, control, value, status, group) {
+ joyMIDI.wheelFilter(channel, control, value, status, group,
+ "[EqualizerRack1_" + group + "_Effect1]", "parameter3", "high");
+};
+
+joyMIDI.wheelFilter = function(channel, control, value, status, group, group2, param, select) {
+ var delta = joyMIDI.helperAccel(value - 64) * 0.01;
+ var oldValue = engine.getParameter(group2, param);
+ var newValue = oldValue + delta;
+
+ newValue = joyMIDI.helperLimit(newValue, 1.0, 0);
+ engine.setParameter(group2, param, newValue);
+ print(group + "EQ_" + select + "=" + newValue);
+};
+
+joyMIDI.wheelFx1 = function(channel, control, value, status, group) {
+ if (group === "[Channel1]") {
+ joyMIDI.wheelFx(channel, control, value, status, group,
+ "[EffectRack1_EffectUnit1]", "mix", 1);
+ } else {
+ joyMIDI.wheelFx(channel, control, value, status, group,
+ "[EffectRack1_EffectUnit2]", "mix", 1);
+ }
+};
+
+joyMIDI.wheelFx2 = function(channel, control, value, status, group) {
+ if (group === "[Channel1]") {
+ joyMIDI.wheelFx(channel, control, value, status, group,
+ "[EffectRack1_EffectUnit1]", "super1", 2);
+ } else {
+ joyMIDI.wheelFx(channel, control, value, status, group,
+ "[EffectRack1_EffectUnit2]", "super1", 2);
+ }
+};
+
+joyMIDI.wheelFx = function(channel, control, value, status, group, group2, param, _select) {
+ var delta = joyMIDI.helperAccel(value - 64) * 0.01;
+ var oldValue = engine.getParameter(group2, param);
+ var newValue = oldValue + delta;
+
+ newValue = joyMIDI.helperLimit(newValue, 1.0, 0);
+ engine.setParameter(group2, param, newValue);
+};
+
+
+//==== Jojstick ============================================================
+joyMIDI.joystick = function(channel, control, _value, _status, _group) {
+
+ if (joystickDelay === false) {
+ /* Delay a while */
+ joystickDelay = true;
+ engine.beginTimer(300, function() { joystickDelay = false; }, true);
+
+ if (!joyMIDI["[Channel1]"].btnShift) {
+ /* Joystick only */
+ switch (control) {
+ case 0x10: engine.setValue("[Library]", "MoveLeft", true); break;
+ case 0x11: engine.setValue("[Library]", "MoveRight", true); break;
+ case 0x12: engine.setValue("[Library]", "MoveDown", true); break;
+ case 0x13: engine.setValue("[Library]", "MoveUp", true); break;
+ }
+ } else {
+ /* Joystick + shift */
+ switch (control) {
+ case 0x10: /* Do nothing */ break;
+ case 0x11: /* Do nothing */ break;
+ case 0x12: engine.setValue("[Library]", "ScrollDown", true); break;
+ case 0x13: engine.setValue("[Library]", "ScrollUp", true); break;
+ }
+ }
+ }
+};
+
+
+//==== FSR ============================================================
+joyMIDI.fsr = function(_channel, _control, _value, _status, _group) {
+
+ if (fsrDelay === false) {
+ /* Delay a while */
+ fsrDelay = true;
+ engine.beginTimer(300, function() { fsrDelay = false; }, true);
+
+ var isShift = joyMIDI["[Channel1]"].btnShift === true || joyMIDI["[Channel2]"].btnShift === true;
+ if (! isShift) {
+ /* FSR only */
+ engine.setValue("[Library]", "MoveFocusForward", true);
+ } else {
+ /* FSR + shift */
+ engine.setValue("[Library]", "MoveFocusBackward", true);
+ }
+ }
+};
+
+//==== Helper =============================================================
+joyMIDI.helperLimit = function(input, max, min) {
+ input = input > max ? max : input;
+ input = input < min ? min : input;
+ return input;
+};
+
+joyMIDI.helperAccel = function(input) {
+ var acc = 0;
+ if (enableAccVal === true) {
+ acc = Math.pow(Math.abs(input), 2);
+ acc = (input > 0) ? acc : -acc;
+ } else {
+ acc = input;
+ }
+ return acc;
+};