Hey fellow AbleSetters! I was able to successfully adapt the Project Script @leolabs and @agustinvolpe shared over in this thread to work really well with the Morningstar MC8 Pro. I also modified it to where the Song Name would display on the second screen and the Section Name would display on the third screen. It does this by writing the current Section Name as the Preset Long Name on the MC8 Pro to all presets (A-H).
Just like the other script, just set the “loopFootswitch” parameter to whichever one you use for looping and that will stay in sync, and you can program your switches to do whatever you want! I’ll also drop the script that was originally shared that is simply modified to work with the MC8 Pro below that if you prefer to view Song and Section Names on the second screen and keep your Preset Long Names intact.
Project Script that displays Song and Section Names Separately (2nd and 3rd Screen)
/**
* MC8 PRO – Update bank name and preset name via temporary SysEx
*
* Displays the current song name as the MC8 Pro Bank Name and the current
* section name as the MC8 Pro Preset Name.
*/
const MIDI_DEVICE = "Morningstar MC8 Pro Port 1"; // Replace with your MIDI output device name
const MIDI_CHANNEL = 1; // Example: MIDI channel 1 (change as needed)
/**
* Calculate checksum for the SysEx payload according to Morningstar's spec.
*/
function calculateChecksum(bytes) {
return bytes.reduce((acc, cur) => (acc ^ cur), 0xF0) & 0x7F;
}
/**
* Send a bank name update to the MC8 Pro.
* Normalizes the string to 32 characters (padded with spaces if shorter).
*/
function sendBankName(name) {
const normalized = (name ?? "No Song").slice(0, 32).padEnd(32);
const songBytes = makeAscii(normalized);
const payload = [
0x00, 0x21, 0x24, 0x08, 0x00, 0x70, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
...songBytes
];
payload.push(calculateChecksum(payload));
sendMidiSysex(MIDI_DEVICE, payload);
}
/**
* Send preset long name update to all 8 presets (A-H) on the MC8 Pro.
* Normalizes the string to 32 characters (padded with spaces if shorter).
*
* Uses op2: 03h (Update Preset Long Name)
* Sends to all 8 presets (indexes 0-7 = A-H).
*/
function sendPresetLongName(name) {
const normalized = (name ?? "").slice(0, 32).padEnd(32);
const nameBytes = makeAscii(normalized);
// Update all 8 presets (A through H)
for (let presetIndex = 0; presetIndex < 8; presetIndex++) {
const payload = [
0x00, 0x21, 0x24, 0x08, 0x00, 0x70, 0x03, // op2: 03h = Update Preset Long Name
presetIndex, // op3: Preset Number (0 = A, 1 = B, ..., 7 = H)
0x00, // op4: 00 = temporary override (not saved to memory)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...nameBytes
];
payload.push(calculateChecksum(payload));
sendMidiSysex(MIDI_DEVICE, payload);
}
}
/**
* Send BPM to the MC8 Pro by encoding it into CC5 (MSB) + CC6 (LSB).
*
* This avoids using MIDI Clock (which can be unstable over Bluetooth)
* while still allowing a numeric tempo readout on the MC8.
*
* Range is clamped between 20–300 BPM.
*/
function sendMC8Bpm(bpm) {
const b = Math.max(20, Math.min(300, Math.round(Number(bpm))));
const msb = Math.floor(b / 128);
const lsb = b % 128;
sendOsc("/midi/send/cc", MIDI_DEVICE, MIDI_CHANNEL, 5, msb);
sendOsc("/midi/send/cc", MIDI_DEVICE, MIDI_CHANNEL, 6, lsb);
log(`MC8 BPM Update → ${b} (MSB=${msb}, LSB=${lsb})`);
}
/**
* State tracking for current song and section.
*/
let currentSong = osc("/setlist/activeSongName") ?? "No Song";
let currentSection = osc("/setlist/activeSectionName") ?? "";
/**
* Update the MC8 display with current song and section.
*/
function updateDisplay() {
sendBankName(currentSong);
sendPresetLongName(currentSection);
}
/**
* Initial display when the script loads.
*/
updateDisplay();
/**
* When Live loads a new active song.
* - Update internal state.
* - Refresh the display on the MC8.
* - Send the current tempo once for this song.
*/
onOscChange("/setlist/activeSongName", ([name]) => {
currentSong = name ?? "No Song";
log("Got active song:", name);
updateDisplay();
const t = Number(osc("/global/tempo"));
if (!Number.isNaN(t)) sendMC8Bpm(t);
}, true);
/**
* When the active section changes.
* - Update internal state.
* - Refresh the display on the MC8.
*/
onOscChange("/setlist/activeSectionName", ([section]) => {
currentSection = section ?? "";
log("Got active section:", section);
sendPresetLongName(currentSection);
}, true);
/* =======================================================================
Footswitch letter → Morningstar index (A–X)
======================================================================= */
/**
* Convert a footswitch letter (A–X) into its Morningstar index (0–23).
* Example: 'A' → 0, 'B' → 1, ..., 'X' → 23.
*/
function footswitchLetterToIndex(letter) {
if (!letter || typeof letter !== "string") return null;
const l = letter.trim().toUpperCase();
const code = l.charCodeAt(0);
// 'A' = 65, 'X' = 88
if (code < 65 || code > 88) {
log(`Invalid footswitch letter: "${letter}". Must be A–X.`);
return null;
}
return code - 65;
}
/**
* Loop state mapping
* Choose the loop footswitch with a letter (A–X).
* MC8 Pro MIDI Implementation:
* - CC number selects the action (e.g. 2 = Set Toggle, 3 = Clear Toggle).
* - Velocity selects the footswitch index:
* 0 = A, 1 = B, 2 = C, ..., 23 = X.
*/
const loopFootswitch = "F"; // Example: "B" → index 1
const loopFootswitchIndex = footswitchLetterToIndex(loopFootswitch);
/* =======================================================================
Loop state → MC8 Pro
- Whenever the loop bracket is enabled/disabled in AbleSet/Live,
send a CC to the MC8 Pro so it can update a Set/Clear Toggle state.
======================================================================= */
onOscChange("/setlist/loopEnabled", ([enabled]) => {
const isEnabled = Number(enabled) === 1;
if (isEnabled) {
// Loop ON → send "Set Toggle" (example: CC2) for the chosen footswitch
sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 2, loopFootswitchIndex);
} else {
// Loop OFF → send "Clear Toggle" (example: CC3) for the chosen footswitch
sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 3, loopFootswitchIndex);
}
log("Loop state changed →", isEnabled ? "ENABLED" : "DISABLED");
}, true);
/**
* Live tempo changes.
* - Whenever the global tempo changes (automation, manual, etc.),
* forward the updated BPM to the MC8.
*/
onOscChange("/global/tempo", ([t]) => {
const n = Number(t);
if (!Number.isNaN(n)) sendMC8Bpm(n);
}, true);
/* =======================================================================
Handle loading-project-name so mouse selection behaves like arrows/MC8
======================================================================= */
/**
* Remove trailing ".als" from a project name (if present).
*/
function cleanAlsName(str) {
if (!str) return null;
return str.endsWith(".als") ? str.slice(0, -4) : str;
}
/**
* When clicking on any other song with the mouse:
* - AbleSet will expose a "Loading: ..." project name.
* - We strip "Loading:" on AbleSet's side and ".als" here so the MC8
* always shows a clean song title, even when selecting with the mouse.
*/
onOscChange("/global/loadingProjectName", ([loadingName]) => {
if (!loadingName) return;
const clean = cleanAlsName(loadingName);
log("Loading project:", clean);
// Show the title directly (no "Loading:" and no ".als")
sendBankName(clean);
}, true);
Project Script that displays Song and Section Names Together (2nd Screen)
/**
* MC8 PRO – Update bank name via temporary SysEx
*
* Sends the current song or section name to the MC8 Pro by temporarily
* overriding the bank name via SysEx.
*/
const MIDI_DEVICE = "Morningstar MC8 Pro Port 1"; // Replace with your MIDI output device name
const MIDI_CHANNEL = 1; // Example: MIDI channel 1 (change as needed)
/**
* Calculate checksum for the SysEx payload according to Morningstar's spec.
*/
function calculateChecksum(bytes) {
return bytes.reduce((acc, cur) => (acc ^ cur), 0xF0) & 0x7F;
}
/**
* Send a bank name update to the MC8 Pro.
* Normalizes the string to 32 characters (padded with spaces if shorter).
*/
function sendBankName(name) {
const normalized = (name ?? "No Song").slice(0, 32).padEnd(32);
const songBytes = makeAscii(normalized);
const payload = [
0x00, 0x21, 0x24, 0x08, 0x00, 0x70, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
...songBytes
];
payload.push(calculateChecksum(payload));
sendMidiSysex(MIDI_DEVICE, payload);
}
/**
* Send BPM to the MC8 Pro by encoding it into CC5 (MSB) + CC6 (LSB).
*
* This avoids using MIDI Clock (which can be unstable over Bluetooth)
* while still allowing a numeric tempo readout on the MC8.
*
* Range is clamped between 20–300 BPM.
*/
function sendMC8Bpm(bpm) {
const b = Math.max(20, Math.min(300, Math.round(Number(bpm))));
const msb = Math.floor(b / 128);
const lsb = b % 128;
sendOsc("/midi/send/cc", MIDI_DEVICE, MIDI_CHANNEL, 5, msb);
sendOsc("/midi/send/cc", MIDI_DEVICE, MIDI_CHANNEL, 6, lsb);
log(`MC8 BPM Update → ${b} (MSB=${msb}, LSB=${lsb})`);
}
/**
* State tracking for current and queued songs.
*/
let currentSong = osc("/setlist/activeSongName") ?? "No Song";
let queuedSong = osc("/setlist/queuedName") ?? null;
/**
* Decide which title should be shown on the MC8:
* - If a song is queued, prefer that name.
* - Otherwise, show the currently active song.
*/
function displayPreferredTitle() {
const title = queuedSong ?? currentSong;
sendBankName(title);
}
/**
* Initial display when the script loads.
* (onOscChange handlers with `true` will also fire once, so this mostly
* ensures we send something immediately at startup.)
*/
displayPreferredTitle();
/**
* When Live loads a new active song.
* - Update internal state.
* - Refresh the title on the MC8.
* - Send the current tempo once for this song.
*/
onOscChange("/setlist/activeSongName", ([name]) => {
currentSong = name ?? "No Song";
log("Got active song:", name);
displayPreferredTitle();
const t = Number(osc("/global/tempo"));
if (!Number.isNaN(t)) sendMC8Bpm(t);
}, true);
/**
* When a song or section is queued.
*
* AbleSet sends [songName, sectionName] on /setlist/queuedName.
* - If a section is queued, briefly show its name on the MC8.
* - Then fall back to the normal title logic (queued > active).
*/
onOscChange("/setlist/queuedName", async ([song, section]) => {
queuedSong = song || null;
log("Got queued song/section:", song, section);
if (section) {
// Show the section name briefly when it is queued
sendBankName(section);
await sleep(1000);
}
// Restore the preferred title after the brief section display
displayPreferredTitle();
}, true);
/**
* When the active section changes while playing.
* - Show the section name for 1 second.
* - Then restore the preferred title (queued > active).
*/
onOscChange("/setlist/activeSectionName", async ([section]) => {
if (!section) return;
sendBankName(section);
await sleep(1000);
displayPreferredTitle();
}, true);
/* =======================================================================
Footswitch letter → Morningstar index (A–X)
======================================================================= */
/**
* Convert a footswitch letter (A–X) into its Morningstar index (0–23).
* Example: 'A' → 0, 'B' → 1, ..., 'X' → 23.
*/
function footswitchLetterToIndex(letter) {
if (!letter || typeof letter !== "string") return null;
const l = letter.trim().toUpperCase();
const code = l.charCodeAt(0);
// 'A' = 65, 'X' = 88
if (code < 65 || code > 88) {
log(`Invalid footswitch letter: "${letter}". Must be A–X.`);
return null;
}
return code - 65;
}
/**
* Loop state mapping
* Choose the loop footswitch with a letter (A–X).
* MC8 Pro MIDI Implementation:
* - CC number selects the action (e.g. 2 = Set Toggle, 3 = Clear Toggle).
* - Velocity selects the footswitch index:
* 0 = A, 1 = B, 2 = C, ..., 23 = X.
*/
const loopFootswitch = "B"; // Example: "B" → index 1
const loopFootswitchIndex = footswitchLetterToIndex(loopFootswitch);
/* =======================================================================
Loop state → MC8 Pro
- Whenever the loop bracket is enabled/disabled in AbleSet/Live,
send a CC to the MC8 Pro so it can update a Set/Clear Toggle state.
======================================================================= */
onOscChange("/setlist/loopEnabled", ([enabled]) => {
const isEnabled = Number(enabled) === 1;
if (isEnabled) {
// Loop ON → send "Set Toggle" (example: CC2) for the chosen footswitch
sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 2, loopFootswitchIndex);
} else {
// Loop OFF → send "Clear Toggle" (example: CC3) for the chosen footswitch
sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 3, loopFootswitchIndex);
}
log("Loop state changed →", isEnabled ? "ENABLED" : "DISABLED");
}, true);
/**
* Live tempo changes.
* - Whenever the global tempo changes (automation, manual, etc.),
* forward the updated BPM to the MC8.
*/
onOscChange("/global/tempo", ([t]) => {
const n = Number(t);
if (!Number.isNaN(n)) sendMC8Bpm(n);
}, true);
/* =======================================================================
Handle loading-project-name so mouse selection behaves like arrows/MC8
======================================================================= */
/**
* Remove trailing ".als" from a project name (if present).
*/
function cleanAlsName(str) {
if (!str) return null;
return str.endsWith(".als") ? str.slice(0, -4) : str;
}
/**
* When clicking on any other song with the mouse:
* - AbleSet will expose a "Loading: ..." project name.
* - We strip "Loading:" on AbleSet's side and ".als" here so the MC8
* always shows a clean song title, even when selecting with the mouse.
*/
onOscChange("/global/loadingProjectName", ([loadingName]) => {
if (!loadingName) return;
const clean = cleanAlsName(loadingName);
log("Loading project:", clean);
// Show the title directly (no "Loading:" and no ".als")
sendBankName(clean);
}, true);