Last active
January 8, 2025 07:03
-
-
Save mxmilkiib/75d869404d583271f0a9c078ccbb8686 to your computer and use it in GitHub Desktop.
Launchpad Pro MK3 MIDI mapping for Mixxx, WIP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//// Launchpad Pro MK3 MIDI mapping for Mixxx | |
//// by Milkii | |
//// DEBUG stuff | |
// Terminal colour codes for DEBUG messages | |
const COLOURS = { | |
RED: "\x1b[31m", | |
GREEN: "\x1b[32m", | |
ORANGE: "\x1b[33m", | |
BLUE: "\x1b[34m", | |
//MAGENTA : "\x1b[35m", | |
//CYAN : "\x1b[36m", | |
RESET: "\x1b[0m" | |
}; | |
// Shorthand for the above | |
const C = { | |
R: COLOURS.RED, | |
G: COLOURS.GREEN, | |
O: COLOURS.ORANGE, | |
B: COLOURS.BLUE, | |
//M: COLOURS.MAGENTA, | |
//C: COLOURS.CYAN, | |
RE: COLOURS.RESET | |
}; | |
const DEBUG = function(message, colour, linesbefore, linesafter) { | |
if (LaunchpadProMK3.DEBUGstate) { | |
if (colour === undefined) { colour = ""; } | |
if (typeof linesbefore === "number" && linesbefore > 0 && linesbefore < 50) { for (i = 0; i < linesbefore; i+= 1) { console.log(" "); } } | |
console.log(`${COLOURS.RED}DEBUG ${COLOURS.RESET}${colour}${message}${COLOURS.RESET}`); | |
if (typeof linesafter === "number" && linesafter > 0 && linesafter < 50) { for (i = 0; i < linesafter; i+= 1) { console.log(" "); } } | |
} | |
}; | |
const dbg = function(variable) { | |
if (LaunchpadProMK3.DEBUG) { console.log(C.O + `DBG ${variable}: ` + variable); } | |
}; | |
/// Main object to represent the controller | |
var LaunchpadProMK3 = {}; | |
LaunchpadProMK3.DEBUGstate = 1; | |
//// Initialise variables | |
/// MIDI addresses of the main 8x8 grid | |
LaunchpadProMK3.mainpadAddresses = [ | |
81, 82, 83, 84, 85, 86, 87, 88, | |
71, 72, 73, 74, 75, 76, 77, 78, | |
61, 62, 63, 64, 65, 66, 67, 68, | |
51, 52, 53, 54, 55, 56, 57, 58, | |
41, 42, 43, 44, 45, 46, 47, 48, | |
31, 32, 33, 34, 35, 36, 37, 38, | |
21, 22, 23, 24, 25, 26, 27, 28, | |
11, 12, 13, 14, 15, 16, 17, 18 ]; | |
/// from top to bottom | |
LaunchpadProMK3.mainpadLayout = "3124"; | |
// alternative layouts | |
//LaunchpadProMK3.mainpadLayout = "1234"; | |
//LaunchpadProMK3.mainpadLayout = "4321"; | |
//LaunchpadProMK3.mainpadLayout = "4213"; | |
// MIDI addresses of the left/right side pads | |
LaunchpadProMK3.sidepadAddresses = [ | |
80, 70, 89, 79, | |
60, 50, 69, 59, | |
40, 30, 49, 39, | |
20, 10, 29, 19 ]; | |
// Templates for assigning side pad controls | |
LaunchpadProMK3.sidepadNames = [ | |
"intro_start_", | |
"intro_end_", | |
"outro_start_", | |
"outro_end_" | |
]; | |
// Pad addresses and deck colours | |
LaunchpadProMK3.switchDecksButtons = [ | |
[ 0x65, 0x7F, 0x00, 0x7F ], | |
[ 0x66, 0x1D, 0x46, 0x7B ], | |
[ 0x67, 0x7F, 0x58, 0x04 ], | |
[ 0x68, 0x44, 0x60, 0x0D ] ]; | |
LaunchpadProMK3.extrasButtons = [ 11, 12, 13, 14, 15, 16, 17, 18, 28 ]; | |
LaunchpadProMK3.extrasControls = [ | |
"beats_set_halve", | |
"beats_set_twothirds", | |
"beats_set_threefourths", | |
"beats_set_fourthirds", | |
"beats_set_threehalves", | |
"beats_set_double", | |
"beats_undo_adjustment", | |
"stars_down", | |
"stars_up" | |
]; | |
LaunchpadProMK3.loopbeatControls = [ | |
//"hotcue_X_activate", | |
// If hotcue X is not set, this sets a hotcue at the current play position and saves it as hotcue X of type “Hotcue” | |
// In case a loop is currently enabled (i.e. if [ChannelN],loop_enabled is set to 1), | |
// the loop will be saved as hotcue X instead and hotcue_X_type will be set to “Loop” | |
// If hotcue X has been set as a regular cue point, the player seeks to the saved play position. | |
//"hotcue_X_enabled", | |
// 0 Hotcue X is not set, 1 Hotcue X is set, 2 Hotcue X is active (saved loop is enabled or hotcue is previewing) | |
/// | |
//"beetjump", | |
// Jump forward (positive) or backward (negative) by N beats. If a loop is active, the loop is moved by X beats | |
//"beatjump_size", | |
// Set the number of beats to jump with beatloop_activate / beatjump_forward / beatjump_backward | |
//"beatjump_size_halve", | |
// Halve the value of beatjump_size | |
//"beatjump_size_double", | |
// Double the value of beatjump_size | |
//"beatjump_backward"r | |
// Jump backward by beatjump_size. If a loop is active, the loop is moved backward by X beats | |
//"beatjump_forward", | |
// Jump forward by beatjump_size. If a loop is active, the loop is moved forward by X beats | |
//"beatjump_X_backward", | |
// Jump backward by X beats. If a loop is active, the loop is moved backward by X beats | |
// ditto | |
//"beatjump_X_forward", | |
// Jump forward by X beats. If a loop is active, the loop is moved forward by X beats. | |
// A control exists for X = 0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512. | |
"beatjump_32_backward", | |
"beatjump_16_backward", | |
"beatjump_8_backward", | |
"beatjump_4_backward", | |
"beatjump_4_forward", | |
"beatjump_8_forward", | |
"beatjump_16_forward", | |
"beatjump_32_forward", | |
/// | |
//"beatjump_64_backward", | |
//"beatjump_128_backward", | |
//"beatjump_2_backward", | |
//"beatjump_1_backward", | |
//"beatjump_1_forward", | |
//"beatjump_2_forward", | |
//"beatjump_128_forward", | |
//"beatjump_64_forward", | |
/// | |
//"beatloop_activate", | |
// Set a loop that is beatloop_size beats long and enables the loop | |
//"beatloop_X_activate", | |
// Activates a loop over X beats. | |
//"beatloop_X_toggle", | |
// Toggles a loop over X beats | |
//"beatloop_1_activate", | |
//"beatloop_2_activate", | |
//"beatloop_4_activate", | |
//"beatloop_8_activate", | |
//"beatloop_16_activate", | |
//"beatloop_32_activate", | |
//"beatloop_64_activate", | |
//"beatloop_128_activate", | |
/// | |
//"beatlooproll_activate", | |
// Activates a rolling loop over beatloop_size beats. Once disabled, playback | |
// will resume where the track would have been if it had not entered the loop. | |
// "beatlooproll_X_activate", | |
// Activates rolling loop over X beats. Once disabled, playback resumes where | |
// the track would have been if it had not entered the loop. A control exists | |
// for X = 0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 | |
"beatlooproll_1_activate", | |
"beatlooproll_2_activate", | |
"beatlooproll_4_activate", | |
"beatlooproll_8_activate", | |
"beatlooproll_16_activate", | |
"beatlooproll_32_activate", | |
"beatlooproll_64_activate", | |
"beatlooproll_128_activate", | |
/// | |
///"loop_move", | |
// Move loop forward by X beats (positive) or backward by X beats (negative). | |
// If a saved loop is currently enabled, the modification is saved to the hotcue slot immediately. | |
//"loop_move_x_forward", | |
// Loop moves forward by X beats. If a saved loop is currently enabled, the modification is saved to the hotcue slot immediately. | |
//"loop_move_x_backward", | |
// Loop moves back by X beats. If a saved loop is currently enabled, the modification is saved to the hotcue slot immediately. | |
"loop_move_32_backward", | |
"loop_move_16_backward", | |
"loop_move_8_backward", | |
"loop_move_4_backward", | |
"loop_move_4_forward", | |
"loop_move_8_forward", | |
"loop_move_16_forward", | |
"loop_move_32_forward", | |
/// | |
//"loop_move_64_backward", | |
//"loop_move_128_backward", | |
//"loop_move_2_backward", | |
//"loop_move_1_backward", | |
//"loop_move_1_forward", | |
//"loop_move_2_forward", | |
//"loop_move_64_forward", | |
//"loop_move_128_forward", | |
/// | |
"loop_in_goto", | |
// Seek to the loop in point. | |
"loop out_goto", | |
// Seek to the loop out point. | |
"loop_half", | |
// Halves beatloop_size. If beatloop_size equals the size of the loop, the loop is resized. | |
// If a saved loop is currently enabled, the modification is saved to the hotcue slot immediately. | |
"loop_double", | |
// Doubles beatloop_size. If beatloop_size equals size of the loop, loop is resized. | |
// If a saved loop is currently enabled, the modification is saved to the hotcue slot immediately. | |
//"loop_scale", | |
// Scale the loop length by the value scale is set to by moving the end marker. | |
// beatloop_size is not updated to reflect the change. If a saved loop is | |
// currently enabled, the modification is saved to the hotcue slot immediately. | |
//"loop_in", | |
// If loop disabled, sets player loop in position to the current play position. | |
// If loop enabled, press and hold to move loop in position to the current play position. | |
// If quantize is enabled, beatloop_size will be updated to reflect the new loop size | |
//"loop_out", | |
// If loop disabled, sets player loop out position to the current play position. | |
// If loop enabled, press & hold to move loop out position to the current play position. | |
// If quantize is enabled, beatloop_size will be updated to reflect the new loop size. | |
"slip_enabled", | |
// When active, playback continues muted in the background during a loop, scratch etc. | |
// Once disabled, the audible playback will resume where the track would have been. | |
"loop_enabled", | |
// Indicates whether or not a loop is enabled. | |
//"loop_start_position", | |
// The player loop-in position in samples, -1 if not set. | |
//"loop_end_position", | |
// The player loop-in position in samples, -1 if not set. "reloop_toggle", | |
// Toggles the current loop on or off. If the loop is ahead of the current play position, | |
// the track will keep playing normally until it reaches the loop. | |
"reloop_andstop", // Activate current loop, jump to its loop in point, and stop playback | |
"loop_remove" | |
// Clears the last active loop | |
/// | |
// Reverse | |
//"reverse", | |
//"reverseroll", | |
]; | |
// Preset colours for decks | |
LaunchpadProMK3.deckColours = [ 0x000002, 0x040201, 0x020002, 0x000200 ]; | |
// Which deck actions will be performed on | |
// LaunchpadProMK3.lastHotcueChannel=1 | |
// Track which page is selected | |
LaunchpadProMK3.currentPage = 0; // Page 0 for hotcues | |
// Track which hotcue was last used | |
LaunchpadProMK3.lastHotcue = []; // Page 0 for hotcues | |
// Track what hotcue was last deleted | |
LaunchpadProMK3.redoLastDeletedHotcue = []; | |
// Track if the shift button is pressed | |
LaunchpadProMK3.shift = 0; | |
//// Startup function, sets up decks, etc. | |
LaunchpadProMK3.init = function() { | |
DEBUG("######", C.R); | |
DEBUG("######", C.O); | |
DEBUG("###### init controller script n object", C.G); | |
DEBUG("######", C.O); | |
DEBUG("######", C.R, 0, 2); | |
// Set LPP3 from DAW mode to programmer mode | |
LaunchpadProMK3.setProgrammerMode(); | |
// Clear already lit pads | |
LaunchpadProMK3.clearAll(); | |
// Create deck objects | |
LaunchpadProMK3.decks = { | |
"1": new LaunchpadProMK3.Deck(1), | |
"2": new LaunchpadProMK3.Deck(2), | |
"3": new LaunchpadProMK3.Deck(3), | |
"4": new LaunchpadProMK3.Deck(4) | |
}; | |
// MIDI handlers for deck selection, actions, and page selection | |
LaunchpadProMK3.initExtras(); | |
// Select the initial desk | |
LaunchpadProMK3.selectDeck(1); | |
// Initialise zeroth page (hotcues) | |
LaunchpadProMK3.switchPage(0); | |
DEBUG("######", C.R); | |
DEBUG("######", C.O); | |
DEBUG("init finished",C.G); | |
DEBUG("######", C.O); | |
DEBUG("######", C.R, 0, 24); | |
}; | |
// Set Launchpad Pro MK3 to Programmer Mode | |
LaunchpadProMK3.setProgrammerMode = function() { | |
DEBUG("# sending programmer mode sysex..", C.O); | |
LaunchpadProMK3.sendSysEx([0x0E, 0x01]); | |
}; | |
// Helper to construct and send SysEx message | |
LaunchpadProMK3.sendSysEx = function(data) { | |
signal = [0xF0, 0x00, 0x20, 0x29, 0x02, 0x0E].concat(data, [0xF7]); | |
//DEBUG(signal) | |
midi.sendSysexMsg(signal, signal.lenth); | |
}; | |
//// Initialise buttons | |
//LaunchpadProMK3.initMidiHigher = function(cc, r, g, b, func, args) { | |
// DEBUG(`initMidiHigher cc: ${cc} deck: {deck} func: ${func} r: ${r} g: ${g} b: ${b}`) | |
// midi.makeInputHandler(0xB0, cc, (channel, control, value, status, group) => { | |
// if (value != 0) { func } | |
// LaunchpadProMK3.sendRGB(cc, r, g, b); // bright | |
// }) | |
//}; | |
LaunchpadProMK3.initExtras = function() { | |
// Deck selection buttons | |
// deck 3 | |
//LaunchpadProMK3.initMidiHigher(0x65, LaunchpadProMK3.selectDeck; deck }, 0x7F, 0x00, 0x7F); | |
midi.makeInputHandler(0xB0, 0x65, (channel, control, value, status, group) => { | |
if (value != 0) { LaunchpadProMK3.selectDeck(3); } | |
}); | |
LaunchpadProMK3.sendRGB(0x66, 0x1D, 0x46, 0x7B); // brightP | |
// deck 1 | |
//LaunchpadProMK3.initMidiHigher(0x66, LaunchpadProMK3.selectDeck; deck, 0x1D, 0x46, 0x7B); | |
midi.makeInputHandler(0xB0, 0x66, (channel, control, value, status, group) => { | |
if (value != 0) { LaunchpadProMK3.selectDeck(1); } | |
}); | |
LaunchpadProMK3.sendRGB(0x66, 0x1D, 0x46, 0x7B); // bright | |
// deck 2 | |
//LaunchpadProMK3.initMidiHigher(0x67, LaunchpadProMK3.selectDecks; deck), 0x7F, 0x58, 0x04); | |
midi.makeInputHandler(0xB0, 0x67, (channel, control, value, status, group) => { | |
if (value != 0) { LaunchpadProMK3.selectDeck(2); } | |
}); | |
LaunchpadProMK3.sendRGB(0x67, 0x7F, 0x58, 0x04); // bright | |
// deck 4 | |
//LaunchpadProMK3.initMidiHigher(0x68, LaunchpadProMK3.selectDeck; deck), 0x44, 0x60, 0x0D); | |
midi.makeInputHandler(0xB0, 0x68, (channel, control, value, status, group) => { | |
if (value != 0) { LaunchpadProMK3.selectDeck(4); } | |
}); | |
LaunchpadProMK3.sendRGB(0x68, 0x44, 0x60, 0x0D); // bright | |
// hotcue color switch next | |
midi.makeInputHandler(0xB0, 0x6A, (control, value, status, group) => { | |
var channel = LaunchpadProMK3.lastHotcueChannel; | |
if (typeof LaunchpadProMK3.lastHotcueChannel === "undefined") { return; } | |
script.toggleControl(channel,"hotcue_focus_color_next"); | |
}); | |
LaunchpadProMK3.sendRGB(0x6A, 0x7F, 0x20, 0x20); | |
// hotcue color switch prev | |
midi.makeInputHandler(0xB0, 0x6B, (control, value, status, group) => { | |
var channel = LaunchpadProMK3.lastHotcueChannel; | |
if (typeof LaunchpadProMK3.lastHotcueChannel === "undefined") { return; } | |
script.toggleControl(channel,"hotcue_focus_color_prev"); | |
}); | |
LaunchpadProMK3.sendRGB(0x6B, 0x20, 0x20, 0x7F); | |
// undo last hotcue | |
midi.makeInputHandler(0xB0, 0x62, (channel, control, value, status) => { | |
if (value != 0) { | |
LaunchpadProMK3.undoLastHotcue(); | |
} | |
}); | |
LaunchpadProMK3.sendRGB(0x62, 0x7F, 0x30, 0x7F); | |
// redo last hotcue | |
midi.makeInputHandler(0xB0, 0x61, (channel, control, value, status) => { | |
if (value != 0) { | |
LaunchpadProMK3.redoLastHotcue(); | |
} | |
}); | |
LaunchpadProMK3.sendRGB(0x61, 0x2F, 0x20, 0x7F); | |
// switch page WIP, bottom right | |
midi.makeInputHandler(0xB0, 0x6C, (channel, control, value, status) => { | |
if (value != 0) { | |
LaunchpadProMK3.switchPage(); | |
} | |
}); | |
LaunchpadProMK3.sendRGB(0x6C, 0x7F, 0x7F, 0x7F); | |
// shift | |
midi.makeInputHandler(0xB0, 0x01, (channel, control, value, status) => { | |
if (value !== 0) { | |
LaunchpadProMK3.shift = 1; | |
LaunchpadProMK3.sendRGB(0x01, 0x2F, 0x7F, 0x7F); | |
DEBUG("# shift on", C.G); | |
} else if (value == 0) { | |
LaunchpadProMK3.shift = 0; | |
LaunchpadProMK3.sendRGB(0x01, 0x0B, 0x0B, 0x0F) ; | |
DEBUG("# shift off", C.G); | |
} | |
}); | |
LaunchpadProMK3.sendRGB(0x01, 0x0B, 0x0B, 0x0F); | |
// create multiple hotcues | |
midi.makeInputHandler(0xB0, 0x03, (channel, control, value, status, group) => { | |
if (value != 0) { LaunchpadProMK3.create4LeadupDropHotcues(LaunchpadProMK3.selectedDeck, value); } | |
}); | |
LaunchpadProMK3.sendRGB(0x03, 0x7F, 0x7F, 0x7F); | |
}; | |
//// Deck constructor | |
LaunchpadProMK3.Deck = function (deckNumber) { | |
DEBUG("##### constructing deck: " + deckNumber, C.G, 2); | |
components.Deck.call(this, deckNumber); | |
this.deckOrderIndex = LaunchpadProMK3.mainpadLayout.indexOf(deckNumber); | |
this.deckMainSliceStartIndex = this.deckOrderIndex * 16; | |
this.deckPadAddresses = LaunchpadProMK3.mainpadAddresses.slice(this.deckMainSliceStartIndex, this.deckMainSliceStartIndex+16); | |
this.deckSideSliceStartIndex = this.deckOrderIndex * 4; | |
this.deckSidepadAddresses = LaunchpadProMK3.sidepadAddresses.slice(this.deckSideSliceStartIndex,this.deckSideSliceStartIndex+4); | |
this.deckColour = LaunchpadProMK3.deckColours[deckNumber-1]; | |
DEBUG("### deck objects instantiation: " + C.RE + "deckNumber " + C.O + deckNumber + C.RE + ", group " + C.O + this.currentDeck + C.RE + ", colour " + C.O + "#" + this.deckColour.toString(16).padStart(6, "0").toUpperCase(), C.O); | |
DEBUG("# deckPadAddresses" + C.RE + " for this deck: " + C.O + this.deckPadAddresses, C.O); | |
engine.makeConnection(`[Channel${deckNumber}]`, "track_loaded", LaunchpadProMK3.onTrackLoadedOrUnloaded); | |
DEBUG("# hotcue buttons init", C.G, 1); | |
//// Deck Main Hotcues | |
this.hotcueButtons = []; | |
for (i = 1; i <= 16; i+=1) { | |
color_object = ""; | |
this.i = i; | |
let padAddress = this.deckPadAddresses[i-1]; | |
let hotcueNum = i; | |
DEBUG("i " + C.O + i + C.RE + ", padAddress " + C.O + padAddress + C.RE + "/" +C.O+ "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase()); | |
LaunchpadProMK3.sendRGB2(padAddress, this.deckColour); | |
// Create hotcue button, using ComponentsJS objects/methods | |
this.hotcueButtons[i-1] = new components.HotcueButton({ | |
// Not using midi: because sysex is where it's at with this controller | |
//midi: [0x90, padAddress], | |
number: this.i, // This is the hotcue number | |
padAddress: padAddress, | |
input: midi.makeInputHandler(0x90, padAddress, (channel, control, value, status) => { | |
/// When the pad is being pressed | |
if (value != 0) { DEBUG("main pad press: " + C.RE + "loaded? " + C.O + engine.getValue(`${this.currentDeck}`,"track_loaded") + C.RE + " value: " + C.O + value + C.RE + " page: " + C.O + LaunchpadProMK3.currentPage, C.G, 1); } | |
// Check the deck is loaded with a track, that the page is right, that it's a button press not release | |
if (engine.getValue(`${this.currentDeck}`,"track_loaded") !== 1 || LaunchpadProMK3.currentPage !== 0 || value === 0) { return; } | |
if (LaunchpadProMK3.shift === 0) { | |
/// If shift not pressed: Hotcue Creation | |
DEBUG("no shift..", C.O); | |
DEBUG("deckNumber " + C.O + deckNumber + C.RE + "/" + C.O + this.currentDeck + C.RE + ", i " + C.O + i + C.RE + ", padAddress " + C.O + padAddress + C.RE + "/" + C.O + "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase() + C.RE + " hotcueNum " + C.O + hotcueNum); | |
// Helper function to trigger hotcue activation control value on then | |
// off the hotcue on press which either creates or jumps to the hotcue | |
script.triggerControl(this.currentDeck, "hotcue_" + hotcueNum + "_activate", 50); | |
// Set new last hotcue channel | |
LaunchpadProMK3.lastHotcueChannel = this.currentDeck; | |
// Add new entry to undo list | |
LaunchpadProMK3.lastHotcue.unshift( [ this.currentDeck, "hotcue_" + hotcueNum, padAddress, deckNumber ] ); | |
DEBUG("LaunchpadProMK3.lastHotcue: " + C.O + LaunchpadProMK3.lastHotcue); | |
} else { | |
/// If shift is pressed: Hotcue Deletion | |
DEBUG("shift"); | |
// Helper function to toggle hotcue clear control on then off | |
script.triggerControl(this.currentDeck, "hotcue_" + hotcueNum + "_clear", 50); | |
// Has to be full page refresh because a track could be on two decks | |
LaunchpadProMK3.updateHotcuePage(); | |
} | |
}), | |
sendRGB: function(color_obj) { | |
//DEBUG("currentPage: " + LaunchpadProMK3.currentPage) | |
//DEBUG(color_obj.toString()) | |
if (LaunchpadProMK3.currentPage === 0) { | |
LaunchpadProMK3.sendRGB(this.padAddress, color_obj.red>>1,color_obj.green>>1,color_obj.blue>>1); | |
} | |
} | |
//shutdown: undefined | |
}); | |
} | |
DEBUG("# ending mainpads init", C.R); | |
//// deck Sidepad Intro/Outro Hotcues | |
//DEBUG("## deck sidepads instantiation: deckNumber " + deckNumber, C.G, 1); | |
DEBUG("# sidepad button init:", C.G, 1); | |
this.sideButtons = []; | |
for (sidepad = 1; sidepad <= 4; sidepad+= 1) { | |
this.i = i; | |
let padAddress = this.deckSidepadAddresses[sidepad-1]; | |
let sidepadControlName = LaunchpadProMK3.sidepadNames[sidepad-1]; | |
rgb = LaunchpadProMK3.hexToRGB(0x0000FF); | |
DEBUG("deck " + C.O + deckNumber + C.RE + ", sidepad " + C.O + sidepad + C.RE + ", padAddress " + C.O + padAddress + C.RE + "/" + C.O + "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase() + ", sidepadControlName: " + C.O + sidepadControlName, C.O); | |
this.sideButtons[sidepad-1] = new components.Button({ | |
midi: [0xB0, padAddress], | |
padAddress: this.padAddress, // Get ready | |
// sendRGB: LaunchpadProMK3.sendRGB(this.sidepadAddress, 0x00, 0x00, 0xFF), | |
input: midi.makeInputHandler(0xB0, padAddress, (channel, control, value, status) => { | |
if (LaunchpadProMK3.currentPage === 0) { | |
DEBUG("side press: deck " + C.O + deckNumber +C.RE+" padAddress " + C.O + padAddress + C.RE + "/" + C.O + "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase() + C.RE + ", sidepadControlName: " +C.O+ sidepadControlName + "activate", C.G, 1); | |
script.triggerControl(`[Channel${deckNumber}]`, `${sidepadControlName}activate`, 50); | |
} | |
LaunchpadProMK3.lastHotcue.unshift( [ deckNumber, "hotcue_" + i, padAddress, deckNumber ] ); | |
}) | |
}); | |
engine.makeConnection(`[Channel${deckNumber}]`, `${sidepadControlName}enabled`, (value) => { | |
if (LaunchpadProMK3.currentPage === 0) { | |
LaunchpadProMK3.trackWithInOut(value, deckNumber, padAddress); | |
} | |
}); | |
} | |
DEBUG("# ending sidepads init", C.R); | |
DEBUG("# reconnect to group:", C.G, 1); | |
// Set the group properties of the above Components and connect their output callback functions | |
this.reconnectComponents(function (c) { | |
if (c.group === undefined) { | |
// 'this' inside a function passed to reconnectComponents refers to the ComponentContainer | |
// so 'this' refers to the custom Deck object being constructed | |
c.group = this.currentDeck; | |
} | |
DEBUG("reconnectComponents" + C.RE + " to current group if group undefined, group: " + C.O + c.group + C.RE + ", this.currentDeck " + C.O + this.currentDeck, C.O); | |
}); | |
}; | |
LaunchpadProMK3.Deck.prototype = new components.Deck(); | |
//// End of Deck object setup | |
//// Single pad light functions | |
// Helper function to convert RGB hex value to individual R, G, B values | |
LaunchpadProMK3.hexToRGB = function(hex) { | |
//DEBUG("hexToRGB #" + hex.toString(16)); | |
var r = (hex >> 16) & 0xFF; | |
var g = (hex >> 8) & 0xFF; | |
var b = hex & 0xFF; | |
//DEBUG([r, g, b]); | |
return [r, g, b]; | |
}; | |
// Send RGB values | |
LaunchpadProMK3.sendRGB = function(pad, r, g, b) { | |
LaunchpadProMK3.sendSysEx([0x03, 0x03, pad, r, g, b]); | |
}; | |
LaunchpadProMK3.sendRGB2 = function(pad, hex) { | |
var r = (hex >> 16) & 0xFF; | |
var g = (hex >> 8) & 0xFF; | |
var b = hex & 0xFF; | |
LaunchpadProMK3.sendSysEx([0x03, 0x03, pad, r, g, b]); | |
}; | |
// Turn off pad LEDs | |
LaunchpadProMK3.turnOffPad = function(pad) { | |
LaunchpadProMK3.sendRGB(pad, 0, 0, 0); | |
}; | |
// Turn off main LEDs for page change | |
LaunchpadProMK3.clearMain = function() { | |
//// main pads | |
DEBUG("//// clearing main and side pads:", C.O); | |
//colorSpecMulti = LaunchpadProMK3.mainpadAddresses.map(address => [0x03, address, 0,0,0]).flatmap(); | |
const colorSpecMulti = _.flatMap(LaunchpadProMK3.mainpadAddresses, (address) => [0x03, address, 0,0,0]); | |
LaunchpadProMK3.sendSysEx([0x03].concat(colorSpecMulti)); | |
//// sidepads | |
const colorSpecMultiSide = _.flatMap(LaunchpadProMK3.sidepadAddresses, (address) => [0x03, address, 0,0,0]); | |
LaunchpadProMK3.sendSysEx([0x03].concat(colorSpecMultiSide)); | |
DEBUG("/// end clearing main and side pads", C.R, 1, 2); | |
}; | |
// Turn off all LEDs for page change or shutdown | |
LaunchpadProMK3.clearAll = function() { | |
DEBUG("//// clearing all pads", C.G, 2); | |
ca = [0x03]; cb = [0x03]; | |
for (i = 0; i <= 0x3F; i+= 1) { ca = ca.concat([0x03, i, 0,0,0]); } LaunchpadProMK3.sendSysEx(ca); | |
for (i = 0x40; i <= 0x7F; i+= 1) { cb = cb.concat([0x03, i, 0,0,0]); } LaunchpadProMK3.sendSysEx(cb); | |
DEBUG("/// end clearing all pads", C.R); | |
}; | |
// Shutdown function that should be triggered by Mixxx on close | |
LaunchpadProMK3.shutdown = function() { | |
DEBUG("### SHUTTINGDOWN ###", C.O, 2, 3); | |
LaunchpadProMK3.clearAll; | |
}; | |
LaunchpadProMK3.undoLastHotcue = function() { | |
DEBUG("####################### undooooo", C.G, 1, 1); | |
// Check that a hotcue has been created | |
if (LaunchpadProMK3.lastHotcue[0] === undefined) { return; } | |
// Deserialise the hotcue to undo away | |
let popped = LaunchpadProMK3.lastHotcue.shift(); | |
DEBUG("## popped: " + popped, 1); | |
DEBUG("## LaunchpadProMK3.lastHotcue: " + LaunchpadProMK3.lastHotcue, C.O, 1); | |
DEBUG("## LaunchpadProMK3.redoLastDeletedHotcue: " + LaunchpadProMK3.redoLastDeletedHotcue); | |
let channel = popped[0]; | |
// Deserealise array | |
let control = popped[1]; | |
let padAddress = popped[2]; | |
let deckNumber = popped[3]; | |
DEBUG("### undoLastHotcue: cont; " + control + ", chan; " + channel + ", deck; " + deckNumber + ", pad;" + padAddress, C.O); | |
// Clear hotcue | |
// Common JS func to toggle a control on then off again | |
script.triggerControl(channel, control + "_clear", 64); | |
// Add undone hotcue to redo list, in case | |
LaunchpadProMK3.redoLastDeletedHotcue.unshift(popped); | |
DEBUG("## LaunchpadProMK3.redoLastDeletedHotcue: " + LaunchpadProMK3.redoLastDeletedHotcue); | |
// Reset pad colour to deck colour | |
LaunchpadProMK3.sendRGB2(padAddress, LaunchpadProMK3.deckColours[deckNumber-1]); | |
LaunchpadProMK3.updateHotcuePage(); | |
}; | |
LaunchpadProMK3.redoLastHotcue = function() { | |
DEBUG("REDO", C.R, 1, 1); | |
// Check if a hotcue has been undone | |
if (LaunchpadProMK3.redoLastDeletedHotcue[0] === undefined) { return; } | |
// Get the undone hotcue to redo | |
let unpopped = LaunchpadProMK3.redoLastDeletedHotcue.shift(); | |
DEBUG("## unpopped: " + unpopped, C.O, 1); | |
DEBUG("## LaunchpadProMK3.redoLastDeletedHotcue: " + LaunchpadProMK3.redoLastDeletedHotcue, C.O, 1); | |
// Deserialise the hotcue to redo | |
let channel = unpopped[0]; | |
let control = unpopped[1]; | |
let padAddress = unpopped[2]; | |
let deckNumber = unpopped[3]; | |
DEBUG("### redoLastHotcue: cont; " + control + ", chan; " + channel + ", deck; " + deckNumber + ", pad;" + padAddress); | |
// Activate the hotcue to undelete it | |
script.triggerControl(channel, control + "_activate", 64); | |
// Add redone hotcue back to undo stack again | |
LaunchpadProMK3.lastHotcue.unshift( [ channel, control, padAddress, deckNumber ] ); | |
DEBUG("## LaunchpadProMK3.lastHotcue: " + LaunchpadProMK3.lastHotcue); | |
LaunchpadProMK3.updateHotcuePage(); | |
}; | |
//// Multiple pad light functions | |
// Function to update pad lights for each hotcue | |
LaunchpadProMK3.updateHotcuePage = function(deck) { | |
DEBUG("### set/refresh hotcue page: " + deck, C.G, 1); | |
if (deck === undefined) { | |
DEBUG("## undefined = updating all decks..", C.O); | |
LaunchpadProMK3.updateHotcueLights(1); | |
LaunchpadProMK3.updateHotcueLights(2); | |
LaunchpadProMK3.updateHotcueLights(3); | |
LaunchpadProMK3.updateHotcueLights(4); | |
DEBUG("end updating decks", C.R, 0, 2); | |
} else { | |
DEBUG("## updating " + deck, C.G); | |
LaunchpadProMK3.updateHotcueLights(deck); | |
DEBUG("end updating deck", C.R, 1, 2); | |
} | |
}; | |
// Update pad lights for a specific deck | |
LaunchpadProMK3.updateHotcueLights = function(deck) { | |
if (LaunchpadProMK3.currentPage === 0) { | |
let colourSpecMulti = []; | |
let deckColour = LaunchpadProMK3.deckColours[deck-1]; | |
DEBUG("## update hotcue lights for " + C.RE + "deck " + C.O + deck + C.RE + " deckColour " + C.O + "#" + deckColour.toString(16).padStart(6, "0").toUpperCase(), C.G, 1); | |
for (i = 1; i < 16; i+=1) { | |
let hotcueEnabled = engine.getValue(`[Channel${deck}]`, `hotcue_${i + 1}_status`); | |
let hotcueColour = engine.getValue(`[Channel${deck}]`, `hotcue_${i + 1}_color`); | |
let padAddress = LaunchpadProMK3.decks[deck].deckPadAddresses[i]; | |
if (hotcueEnabled === 1) { | |
let rgb = LaunchpadProMK3.hexToRGB(hotcueColour); | |
DEBUG("i " + C.O + i + C.RE + " padAddress " + C.O + padAddress + C.RE + "/" + C.O + "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase() + C.RE + " deckColour " + C.O + "#" + deckColour.toString(16).padStart(6, "0").toUpperCase() + C.RE + " hotcueEnabled " + C.O + hotcueEnabled + C.RE + ", hotcueColour " + C.O + "#" + hotcueColour.toString(16).padStart(6, "0").toUpperCase()) | |
colourSpecMulti = colourSpecMulti.concat([ 0x03, padAddress, rgb[0]/2, rgb[1]/2, rgb[2]/2 ]); | |
} else { | |
// if no hotcue, set pad to deck colour | |
let rgb = LaunchpadProMK3.hexToRGB(deckColour); | |
//DEBUG("i " + C.O + i + C.RE + ", padAddress " + C.O + padAddress + C.RE + ", deckColour " + C.O + "#" + deckColour.toString(16).padStart(6, "0"), + C.RE + ", hotcueEnabled " + C.O + hotcueEnabled); | |
colourSpecMulti = colourSpecMulti.concat([ 0x03, padAddress, rgb[0], rgb[1], rgb[2] ]); | |
} | |
} | |
DEBUG("# finished creating pad address sysex msg, sending...", C.O); | |
LaunchpadProMK3.sendSysEx([0x03].concat(colourSpecMulti)); | |
DEBUG("end updating main pads", C.R, 0, 1); | |
} | |
// Turn a sidepad colour to blue or off | |
DEBUG("## update sidepad lights" + C.RE + " for deck " + C.O + deck, C.G); | |
for (i = 1; i < 5; i += 1) { | |
let sidepad = (deck) * 4 + i; | |
//let padAddress = LaunchpadProMK3.sidepadAddresses[sidepad]; | |
let padAddress = LaunchpadProMK3.decks[deck].deckSidepadAddresses[i-1]; | |
let sidepadControlName = LaunchpadProMK3.sidepadNames[i-1]; | |
let sidepadEnabled = engine.getValue(`[Channel${deck}]`, `${sidepadControlName}enabled`); | |
if (sidepadEnabled === 1) { | |
DEBUG("i " + C.O + i + C.RE + " deck " + C.O + deck + C.RE + " sidepad " + C.O + sidepad + C.RE + " padAddress " + C.O + padAddress + C.RE + "/" + C.O + "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase() + C.RE + " control " + C.G + sidepadControlName + "activate"); | |
LaunchpadProMK3.trackWithInOut(1, deck, padAddress); | |
} else { | |
DEBUG("i " + C.O + i + C.RE + " deck " + C.O + deck + C.RE + " sidepad " + C.O + sidepad + C.RE + " padAddress " + C.O + padAddress + C.RE + "/" + C.O + "0x" + padAddress.toString(16).padStart(2, "0").toUpperCase() + C.R + " not used"); | |
LaunchpadProMK3.trackWithInOut(0, deck, padAddress); | |
} | |
} | |
DEBUG("end updating sidepads", C.R, 0, 1); | |
}; | |
// Turn a sidepad colour to blue or off | |
LaunchpadProMK3.trackWithInOut = function(value, deckNumber, padAddress) { | |
//DEBUG("## trackWithInOut value " + value + ", padAddress " + padAddress); | |
if (value > 0) { | |
LaunchpadProMK3.sendRGB(padAddress, 0x00, 0x00, 0xFF); | |
} else { | |
LaunchpadProMK3.sendRGB(padAddress, 0x00, 0x00, 0x00); | |
} | |
}; | |
// Do things on track load/unload | |
LaunchpadProMK3.onTrackLoadedOrUnloaded = function(value, group) { | |
DEBUG((value === 1 ? `### Track loaded on ${group}` : `### Track unloaded from ${group}`), C.G, 2); | |
deck = group.match(/(\d+)/)[1]; | |
if (LaunchpadProMK3.currentPage === 0) { | |
LaunchpadProMK3.updateHotcueLights(deck); | |
} | |
}; | |
//// Further pages | |
// Handle switching pages, cycling or directly | |
LaunchpadProMK3.switchPage = function(page) { | |
//DEBUGextra = ""; | |
// find target page if none provided | |
if (page === undefined) { | |
page = (LaunchpadProMK3.currentPage+1) % 3; | |
DEBUG("## page undefined, switchPage setting page to " + C.O + page, C.O, 2); | |
} | |
DEBUGextra = C.RE + " to " + C.O + page; | |
DEBUG("#### " + C.RE + "switching page from " + C.O + LaunchpadProMK3.currentPage + DEBUGextra, C.G, 2); | |
LaunchpadProMK3.currentPage = page; | |
if (page === 0) { LaunchpadProMK3.updateHotcuePage(); } | |
else if (page === 1) { LaunchpadProMK3.updateExtrasPage(); } | |
else if (page === 2) { LaunchpadProMK3.updateLoopBeatPage(); } | |
else if (page === 3) { LaunchpadProMK3.updateHotcuePage(); } | |
}; | |
// Update second page | |
LaunchpadProMK3.updateExtrasPage = function() { | |
DEBUG("## updateExtrasPage", C.G, 2, 1); | |
LaunchpadProMK3.clearMain(); | |
let control = 0; | |
// LaunchpadProMK3.sendRGB(address, address, address*4, addr0ess*7) | |
for (let address of LaunchpadProMK3.extrasButtons) { | |
// add = LaunchpadProMK3.extrasButtons[addInd] | |
midi.makeInputHandler(0xB0, address, (channel, control, value, status, group) => { | |
DEBUG("track_loaded?", C.O); | |
if (LaunchpadProMK3.currentPage === 1 && value > 1) { | |
LaunchpadProMK3.extrasControls[control]; | |
DEBUG("extras " + address); | |
}; | |
}); | |
LaunchpadProMK3.sendRGB(address, 0x70, 0x73, 0x71) | |
control = control+1; | |
}; | |
}; | |
// Update third page | |
LaunchpadProMK3.updateLoopBeatPage = function() { | |
DEBUG("## updateLoopBeatPage", C.G, 2, 1); | |
LaunchpadProMK3.clearMain(); | |
const addresses = LaunchpadProMK3.mainpadAddresses | |
const controls = LaunchpadProMK3.loopbeatControls | |
const controlslen = controls.length | |
//const control = 0; | |
//DEBUG("outside loop " + addresses[control]) | |
//for (let control of controls) { | |
let i = 0 | |
while (i < controlslen) { | |
//DEBUG("outside loop " + addresses[i]) | |
//DEBUG(controlslen + " " + controls) | |
let address = addresses[i] | |
//DEBUG("inner loop " + addresses[i]) | |
//DEBUG(address) | |
//dbg(address) | |
midi.makeInputHandler(0xB0, address, (channel, control, value, status, group) => { | |
if (LaunchpadProMK3.currentPage === 2) { | |
DEBUG("loopbeat: " + control + ", addresss: " + address, C.O); | |
} | |
}); | |
let factor = i * 7; | |
let r = factor * 6 | |
let g = factor * 5 | |
let b = factor * 4 | |
LaunchpadProMK3.sendRGB(address, r, g, b) | |
//control = control+1; | |
i++ | |
}; | |
}; | |
//// Other hotcue helper functions | |
//LaunchpadProMK3.hotcue_last_delete = function(channel, control, value, status, group) { | |
// if (PioneerCDJ850.last_hotcue < 1 || PioneerCDJ850.last_hotcue > 4) | |
// return; | |
// key = "hotcue_" + PioneerCDJ850.last_hotcue + "_clear"; | |
// engine.setValue(group,key,value); | |
// LaunchpadProMK3.last_hotcue = 0; | |
//}; | |
// To add time between steps in multi hotcue function | |
LaunchpadProMK3.sleep = function(time) { | |
let then = Date.now(); | |
while (true) { | |
let now = Date.now(); | |
if (now - then > time) { | |
break; | |
}; | |
}; | |
}; | |
// Set selected deck and change LEDs | |
LaunchpadProMK3.selectDeck = function(deck) { | |
LaunchpadProMK3.selectedDeck = deck; | |
DEBUG("### selecting deck " + deck, C.G, 3); | |
// Easier identity | |
sdb = LaunchpadProMK3.switchDecksButtons; | |
// Brightness reduction factor | |
let b = 0.2 | |
// far left | |
LaunchpadProMK3.sendRGB(sdb[0][0],Math.round(sdb[0][1]*b),Math.round(sdb[0][2]*b),Math.round(sdb[0][3]*b)); | |
// near left | |
LaunchpadProMK3.sendRGB(sdb[1][0],Math.round(sdb[1][1]*b),Math.round(sdb[1][2]*b),Math.round(sdb[1][3]*b)); | |
// near right | |
LaunchpadProMK3.sendRGB(sdb[2][0],Math.round(sdb[2][1]*b),Math.round(sdb[2][2]*b),Math.round(sdb[2][3]*b)); | |
// far right | |
LaunchpadProMK3.sendRGB(sdb[3][0],Math.round(sdb[3][1]*b),Math.round(sdb[3][2]*b),Math.round(sdb[3][3]*b)); | |
if (deck === 3) { LaunchpadProMK3.sendRGB(sdb[0][0],sdb[0][1],sdb[0][2],sdb[0][3]); } | |
else if (deck === 1) { LaunchpadProMK3.sendRGB(sdb[1][0],sdb[1][1],sdb[1][2],sdb[1][3]); } | |
else if (deck === 2) { LaunchpadProMK3.sendRGB(sdb[2][0],sdb[2][1],sdb[2][2],sdb[2][3]); } | |
else if (deck === 4) { LaunchpadProMK3.sendRGB(sdb[3][0],sdb[3][1],sdb[3][2],sdb[3][3]); }; | |
}; | |
// Create a number of hotcues working back from playhead position | |
LaunchpadProMK3.create4LeadupDropHotcues = function (deck, value) { | |
DEBUG("Create hotcues for drop/breakdown, -16, -32, -64, deck " + deck + ", value " + value, C.G, 2); | |
if (value === 0 || value === undefined) return 0; | |
group = `[Channel${deck}]`; | |
for (let X = 1; X <= 36; X++) { | |
let hotcueVariable = "hotcue_" + X + "_status"; | |
if (engine.getValue(group, hotcueVariable) === 0) { | |
X += 3; | |
// Create main hotcue | |
hotcueFocus = "hotcue_"+X+"_set"; | |
engine.setValue(group,hotcueFocus, 1); | |
hotcueColourFocus = "hotcue_"+X+"_color"; | |
engine.setValue(group,hotcueColourFocus, 0x32be44); // green | |
engine.setValue(group, "beatjump_16_backward", 1); | |
LaunchpadProMK3.sleep(50) | |
; | |
// Create -16 | |
engine.setValue(group,"hotcue_"+(X-1)+"_set", 1); | |
engine.setValue(group,"hotcue_"+(X-1)+"_color", 0xf8d200); // yellow | |
engine.setValue(group, "beatjump_16_backward", 1); | |
LaunchpadProMK3.sleep(50); | |
// Create -32 | |
engine.setValue(group,"hotcue_"+(X-2)+"_set", 1); | |
engine.setValue(group,"hotcue_"+(X-2)+"_color", 0xf8d200); // yellow | |
engine.setValue(group, "beatjump_32_backward", 1); | |
LaunchpadProMK3.sleep(50); | |
// Create -64 | |
engine.setValue(group,"hotcue_"+(X-3)+"_set", 1); | |
engine.setValue(group,"hotcue_"+(X-3)+"_color", 0xf8d200); // yellow | |
engine.setValue(group,"beatjump_64_forward", 1); | |
LaunchpadProMK3.sleep(50); | |
// | |
engine.setValue(group, "intro_end_activate", 1); | |
return; | |
}; | |
}; | |
DEBUG("End multi hotcue creation", C.R, 0, 2) | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment