Morningstar MC6 Pro Project Script Integration

Hey everyone! :waving_hand:

I originally shared an early version of this script inside the AbleSet 3 Beta 19 thread, but since it has grown quite a bit and proven useful, it made sense to give it its own dedicated thread.

This is an extended version of the MC6 Pro project script that @leolabs originally shared, and that @joshcrosby later neatly adapted for the MC8 Pro.
Over the last few days I kept developing it and adding more features.

I’m sharing it here as a work in progress, in case it helps anyone integrating AbleSet + Ableton Live with a Morningstar MC6 Pro.


:white_check_mark: What this script does

1️⃣ Sends song & section names to the MC6 Pro (SysEx bank override)

The script sends the current song name (and temporarily the section name) to the MC6 Pro by overriding the bank name via SysEx.

This part was already present in Léo’s original script, but it’s now extended with:

  • Sanitizing names (no accents, emojis, or non-ASCII characters)
  • Safe trimming and padding for the MC6 display
  • Automatic fallback Logic:
    • Queued song > active song
    • Brief section display when sections are queued or become active

Result: the MC6 always shows meaningful, readable context while navigating a set.

2️⃣ Sends BPM to the MC6 Pro using CC (no MIDI Clock required)

The script sends the song tempo encoded as:

  • CC5 = MSB
  • CC6 = LSB

This allows the MC6 Pro to show the BPM without relying on MIDI Clock.

Why this matters:

  • In setups where MIDI over Bluetooth is involved.
  • MIDI Clock over BT is not always reliable.
  • Sending tempo as discrete CC values avoids drift and keeps the BPM display promptly updated.

:backhand_index_pointing_right: If you’re using a fully wired MIDI setup, you can safely rely on regular MIDI Clock and remove the BPM-related part of this script.

3️⃣ Keeps Play, Stop, Record and Loop states in sync

The script keeps the MC6 Pro footswitch LEDs/toggles perfectly in sync with AbleSet / Live — no matter where the action is triggered from (MC6, keyboard, mouse, etc.).

:repeat_button: Loop state

  • Listens to:
    /setlist/loopEnabled
  • Sends:
    • CC2 → Set Toggle
    • CC3 → Clear Toggle
  • Velocity = footswitch index (A–X → 0–23)

So whenever the loop bracket is enabled or disabled in AbleSet / Live, the corresponding MC6 toggle updates automatically.

In the example script, the loop is mapped to footswitch B, but you can change it to any other footswitch.


:play_button: Play / Stop state

  • Listens to:
    /global/isPlaying
  • Syncs a Play/Stop toggle on the MC6 (example: footswitch A)

This means:

  • Press Play from the keyboard → MC6 toggle turns ON
  • Press Stop from the UI → MC6 toggle turns OFF
  • Press Play/Stop from the MC6 → everything stays in sync

No guessing, the controller always reflects the real playback state.


:red_circle: Record state

  • Listens to:
    /global/isRecording
  • Syncs a Record ON/OFF toggle on the MC6 (example: footswitch E)

Important Logic here:

  • Whenever AbleSet is recording, it is also playing
  • But not every playback state implies recording

By keeping Record on its own dedicated toggle, the MC6 accurately reflects:

  • Playing but not recording
  • Playing and recording
  • Recording stopped while playback continues

Again, this works regardless of whether recording is started from the MC6 or directly from the AbleSet UI.


:level_slider: Tempo automation tip (important for reliable BPM fallback)

Whether you’re working:

  • In a single Live project with many songs on the same timeline, or
  • With a multi-file set

I strongly recommend this:

:backhand_index_pointing_right: Make sure each song has at least one tempo automation point, even if the tempo never changes.

A simple approach:

  • Add one tempo automation point after the end of each song, outside the audible part.

Why this matters:

  • When AbleSet triggers Re-Enable Automation, Live re-reads tempo automation.
  • If at least one tempo point exists, the script can resend the correct BPM to the MC6.
  • If a song has no tempo automation at all, Live still re-enables automation internally, but the MC6 won’t receive a refreshed BPM value.

🎛 Loop / footswitch mapping for MC6 Pro

For loop, play, and record state sync, the script uses Morningstar’s toggle Logic:

  • CC2 → Set Toggle
  • CC3 → Clear Toggle

Velocity selects the footswitch index:

  • 0 → A
  • 1 → B
  • 2 → C
  • 23 → X

To keep this user-friendly, the script lets you define footswitches by letter, not index.

Example:

const loopFootswitch = "L";

The script automatically converts that to the correct index internally.


:scroll: Script

Here's the full script
/**
 * MC6 PRO – Update bank name via temporary SysEx
 *
 * Sends the current song or section name to the MC6 Pro by temporarily
 * overriding the bank name via SysEx.
 */

const MIDI_DEVICE  = "YOUR MIDI DEVICE"; // Replace with your MIDI output device name
const MIDI_CHANNEL = 1;              // Example: MIDI channel 8 (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;
}

/**
 * Sanitize titles for the MC6 Pro:
 * - Remove accents/diacritics (áéíóúñ → aeioun, etc.)
 * - Strip emojis and any non-printable / non-ASCII characters
 * - Collapse extra spaces
 * - Limit to 32 characters
 */
function sanitizeForMC6Title(str) {
  if (!str) return "No Song";

  let s = String(str);

  // 1) Remove non-ASCII printable characters (this nukes emojis, etc.)
  //    Range 0x20–0x7E = space through "~"
  s = s.replace(/[^\x20-\x7E]/g, "");

  // 2) Collapse multiple spaces and trim ends
  s = s.replace(/\s+/g, " ").trim();

  if (!s) return "No Song";

  // 3) Limit to 32 characters for the MC6 display
  return s.slice(0, 32);
}

/**
 * Send a bank name update to the MC6 Pro.
 * - Normalizes to ASCII (no accents/emojis)
 * - Pads to 32 characters with spaces if shorter.
 */
function sendBankName(name) {
  const safeTitle = sanitizeForMC6Title(name ?? "No Song");
  const normalized = safeTitle.padEnd(32);
  const songBytes = makeAscii(normalized);

  const payload = [
    0x00, 0x21, 0x24, 0x06, 0x00, 0x70, 0x10,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
    ...songBytes
  ];

  payload.push(calculateChecksum(payload));
  sendMidiSysex(MIDI_DEVICE, payload);
}

/**
 * Send BPM to the MC6 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 MC6.
 *
 * Range is clamped between 20–300 BPM.
 */
function sendMC6Bpm(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(`MC6 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 MC6:
 * - 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 MC6.
 * - 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)) sendMC6Bpm(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 MC6.
 * - 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);

/* =======================================================================
   MC6 Pro Footswitches index (A to 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
 * User-friendly: choose the loop footswitch with a letter (A–X).
 * MC6 Pro's 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 → MC6 Pro
   - Whenever the loop bracket is enabled/disabled in AbleSet/Live,
     send a CC to the MC6 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 MC6.
 */
onOscChange("/global/tempo", ([t]) => {
  const n = Number(t);
  if (!Number.isNaN(n)) sendMC6Bpm(n);
}, true);

/* =======================================================================
   Handle loading-project-name so mouse selection behaves like arrows/MC6
   ======================================================================= */

/**
 * 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 MC6
 *   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)

/* =======================================================================
   Play/Stop + Recording state → MC6 Pro (toggle sync)
   ======================================================================= */

const playStopFootswitch = "A";
const playStopFootswitchIndex = footswitchLetterToIndex(playStopFootswitch);

const recordFootswitch = "E";
const recordFootswitchIndex = footswitchLetterToIndex(recordFootswitch);

// PLAYBACK toggle (A) – uses /global/isPlaying
onOscChange("/global/isPlaying", ([playing]) => {
  const isPlaying = Number(playing) === 1;

  if (isPlaying) {
    sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 2, playStopFootswitchIndex); // Set Toggle
  } else {
    sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 3, playStopFootswitchIndex); // Clear Toggle
  }

  log("Playback state changed →", isPlaying ? "PLAYING" : "STOPPED/PAUSED");
}, true);

onOscChange("/global/isRecording", ([recording]) => {
  const isRecording = Number(recording) === 1;

  if (isRecording) {
    sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 2, recordFootswitchIndex); // Set Toggle
  } else {
    sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 3, recordFootswitchIndex); // Clear Toggle
  }

  log("Recording state changed →", isRecording ? "RECORDING" : "NOT RECORDING");
}, true);

Just remember to:

  1. Replace "YOUR MIDI DEVICE" with your exact MIDI output device name
  2. Set MIDI_CHANNEL to the MIDI channel your MC6 Pro is listening on (1–16)

Feel free to try it out, adapt it to your setup, and build on top of it.
I’m happy to keep iterating on this as new ideas come up.

Agus

2 Likes

Hey man, really appreciate all the work here. Can you possibly explain how I could map the MC6 to give me feedback about Play/Pause so that for instance a toggle is in sync? I can’t quite make sense of it above, is this possible yet? Maybe I’m miss understanding haha

I currently have added what I understand to be the full script, and its displaying songs and switching through the set as expected but I’m still a little lost on getting toggle feedback. Any help would be so appreciated! Thanks.

Edit: Okay update, I’ve since wrapped my head around what part of the code needs to be changed I believe and I think I’m close! I’ve set up my play/pause footswitch on “B” to be a toggle with CC 2 and CC3. I’ve set it up as

const loopFootswitch = “A”;

and
const playStopFootswitch = “E”;

Mapped it in midi mapping. But I’m still not getting the toggle to sync if I play from spacebar.. What am I missing I wonder.

Edit number 2: Okay WOW I’ve figured it out. So proud of myself here. I’ve never understood code script at ALL. Last part of the puzzle was setting MC6 to receive on same specified MIDI channel in the editor. Wasn’t getting BPM information before either but now everything’s there.

Hey @kbwall,

Glad you got it working! :raising_hands:

Nice catch on the MIDI channel too, that’s a super common one to miss.

If you run into anything else, just let me know!

Could this be easily adapted to the MC4 Pro? I know it would mean losing some features, but during a show this station only needs Play/stop/next/prev. Thanks!

Hey @RScott,

Since you only need Play/Stop, Next and Prev during the show, you only have to map those three actions on the MC4 Pro Editor.
Next/Prev are momentary actions, so they don’t need any feedback from the script.
The only thing worth syncing back is the Play/Stop toggle state, plus showing the song/section titles and the BPM on the screen. That’s what the stripped-down version does.

I removed the loop and recording sync from the MC6 Pro script and updated the footswitch indexing for the MC4 Pro layout (A–P across your 4 pages instead of A–X).

One important caveat: I couldn’t find the MC4 Pro’s SysEx device ID in Morningstar’s documentation. Knowing that the MC6 Pro uses 0x06 and the MC8 uses 0x08, the MC4 Pro should be 0x04, so that’s what I used in the payload. If the titles don’t show up on the display, that byte is the first thing to check (it’s commented in the code so it’s easy to find).

/**
 * MC4 PRO – Update bank name via temporary SysEx
 *
 * Sends the current song/section name to the MC4 Pro by temporarily
 * overriding the bank name via SysEx. Also forwards BPM and keeps the
 * Play/Stop toggle in sync.
 */

const MIDI_DEVICE  = "YOUR_MIDI_DEVICE"; // Replace with your MIDI output device name
const MIDI_CHANNEL = 1;              // Set to your MC4 Pro's MIDI channel

/**
 * Calculate the checksum for the SysEx payload (Morningstar spec).
 */
function calculateChecksum(bytes) {
  return bytes.reduce((acc, cur) => (acc ^ cur), 0xF0) & 0x7F;
}

/**
 * Sanitize titles: strip accents/emojis, keep printable ASCII, max 32 chars.
 */
function sanitizeTitle(str) {
  if (!str) return "No Song";
  let s = String(str)
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "") // remove accent marks
    .replace(/[^\x20-\x7E]/g, "")    // remove emojis / non-ASCII
    .replace(/\s+/g, " ")
    .trim();
  return s ? s.slice(0, 32) : "No Song";
}

/**
 * Send a bank-name update to the MC4 Pro.
 * NOTE: byte 4 (0x04) is the assumed MC4 Pro device ID — change it here if
 * titles don't appear on the display.
 */
function sendBankName(name) {
  const songBytes = makeAscii(sanitizeTitle(name ?? "No Song").padEnd(32));
  const payload = [
    0x00, 0x21, 0x24, 0x04, 0x00, 0x70, 0x10,  // <-- 0x04 = assumed MC4 Pro ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
    ...songBytes
  ];
  payload.push(calculateChecksum(payload));
  sendMidiSysex(MIDI_DEVICE, payload);
}

/**
 * Send BPM via CC5 (MSB) + CC6 (LSB). Avoids MIDI Clock (unstable over BT).
 * Clamped 20–300 BPM.
 */
function sendBpm(bpm) {
  const b = Math.max(20, Math.min(300, Math.round(Number(bpm))));
  sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 5, Math.floor(b / 128)); // MSB
  sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, 6, b % 128);             // LSB
  log(`BPM → ${b}`);
}

/* ---------- Title display state ---------- */

let currentSong = osc("/setlist/activeSongName") ?? "No Song";
let queuedSong  = osc("/setlist/queuedName") ?? null;
let displayToken = 0; // prevents stale async flashes from overwriting newer titles

function displayPreferredTitle() {
  sendBankName(queuedSong ?? currentSong);
}

// Briefly flash a name (e.g. a section), then restore the preferred title.
async function flashTitle(name, duration = 2000) {
  const token = ++displayToken;
  sendBankName(name);
  await sleep(duration);
  if (token === displayToken) displayPreferredTitle();
}

// Active song changed → update title + push current tempo once.
onOscChange("/setlist/activeSongName", ([name]) => {
  currentSong = name ?? "No Song";
  displayPreferredTitle();
  const t = Number(osc("/global/tempo"));
  if (!Number.isNaN(t)) sendBpm(t);
}, true);

// Song/section queued → flash the section name, then fall back to title logic.
onOscChange("/setlist/queuedName", async ([song, section]) => {
  queuedSong = song || null;
  if (section) await flashTitle(section);
  else displayPreferredTitle();
}, true);

// Active section changed while playing → flash it for 2s.
onOscChange("/setlist/activeSectionName", async ([section]) => {
  if (section) await flashTitle(section);
}, true);

// Tempo changes (automation/manual) → forward to the MC4 Pro.
onOscChange("/global/tempo", ([t]) => {
  const n = Number(t);
  if (!Number.isNaN(n)) sendBpm(n);
}, true);

// Mouse-selecting a song exposes a "Loading: ..." name — show it cleanly.
onOscChange("/global/loadingProjectName", ([loadingName]) => {
  if (!loadingName) return;
  sendBankName(loadingName.endsWith(".als") ? loadingName.slice(0, -4) : loadingName);
}, true);

/* ---------- Play/Stop toggle sync ---------- */

/**
 * Convert a footswitch letter (A–P) into its index (0–15).
 * MC4 Pro: 4 switches × 4 pages = presets A–P.
 */
function footswitchLetterToIndex(letter) {
  if (!letter || typeof letter !== "string") return null;
  const code = letter.trim().toUpperCase().charCodeAt(0);
  if (code < 65 || code > 80) { // 'A'=65 .. 'P'=80
    log(`Invalid footswitch letter: "${letter}". Must be A–P.`);
    return null;
  }
  return code - 65;
}

const playStopFootswitch = "A"; // the preset where your Play/Stop toggle lives
const playStopIndex = footswitchLetterToIndex(playStopFootswitch);

// Keep the Play/Stop toggle in sync with Live's playback state.
// CC2 = Set Toggle, CC3 = Clear Toggle; velocity = footswitch index.
onOscChange("/global/isPlaying", ([playing]) => {
  const isPlaying = Number(playing) === 1;
  sendMidiCc(MIDI_DEVICE, MIDI_CHANNEL, isPlaying ? 2 : 3, playStopIndex);
  log("Playback →", isPlaying ? "PLAYING" : "STOPPED/PAUSED");
}, true);

Quick setup notes:

  • Set MIDI_DEVICE and MIDI_CHANNEL to match your MC4 Pro.
  • Set playStopFootswitch to whichever preset (A–P) holds your Play/Stop toggle.
  • Just map Prev/Next on the controller to send the commands to AbleSet.

Paste it into Settings → MIDI Mapping, OSC & Scripting → Project Script and give it a run!!

Let me know how this works out on your end!

1 Like

@augustinvolpe you are too kind! This is above and beyond any response expected. The MC4 hasn’t arrived yet, but I’ll be sure to let you know how this works out. Cheers

1 Like

hey. first of all thanks for that great work. but in my case its not running and I get some error messages. one error was my fault because I did not used the ““ when in replaced “YOUR_MIDI_DEVICE”. Then the whole introductory comment on the very top was interpreted as command instead of a comment. (I changed that). the only error messages left are now:

project-script-editor.mjs3 of 3 problems

Property ‘endsWith’ does not exist on type ‘never’.(2339)

Property ‘slice’ does not exist on type ‘never’.(2339)

Argument of type ‘number | null’ is not assignable to parameter of type ‘number’.

Type ‘null’ is not assignable to type ‘number’.(2345)

how can I fix that?

Hey @andypenn, welcome to the forum!

Those three remaining messages are type hints from the script editor’s checker, not runtime errors — they won’t stop the script from running, so you can safely ignore them.

The more likely culprit is the MIDI setup at the top of the script:

const MIDI_DEVICE  = "YOUR MIDI DEVICE";
const MIDI_CHANNEL = 1;
  • MIDI_DEVICE has to match your MIDI output exactly as AbleSet lists it, including spaces and capitalization. You can check the exact name under Settings → MIDI Mapping, OSC & Scripting → Edit MIDI Mapping (you’ll see all connected devices listed there).
  • MIDI_CHANNEL has to match the channel your MC6 Pro is set to receive on. The comment in the script says channel 8 as an example, set it to whatever your MC6 uses.

To confirm the script is running at all, add this at the very top and watch the console in the script editor:

log("Project Script loaded");

If that line shows up but the MC6 still doesn’t react, let me know the exact MIDI output name you’re using and which channel the MC6 is listening on, and we’ll take it from there.

I’m looking forward to your reply!

hey Augustin!

thanks, now the BPM display works and also the start/stop synced display. The only thing missing is the display of the current song/section. how can I fix this?

Hey @andypenn,

Good to hear the BPM and the play/stop sync are working. That actually narrows things down quite a bit: those two go out as regular CC messages, while the song/section title is the only part that goes out as SysEx (the bank name override). So the issue is somewhere in that SysEx path rather than in the MIDI connection itself.

Are you using an MC6 Pro, or a different Morningstar controller like the MC8 Pro or MC4? The script’s SysEx payload includes a device ID that’s specific to the MC6 Pro, so on another model the message would simply be ignored while the CC messages keep working exactly as you’re seeing.

If it is an MC6 Pro, could you paste your full script here so I can see how you have it set up? A couple of extra details would help too:

  • How is the controller connected — USB or Bluetooth?
  • Which firmware version is it running?

I’m looking forward to your reply!

I’m using the MC4pro connected via USB with firmware: 3.14

Hey @andypenn,

The MC4 Pro’s Device Model ID is 0x09. The script sends 0x06 (MC6 Pro), so your controller was rejecting the SysEx message with a “wrong model ID” error. CC messages don’t carry a model ID, which is exactly why the BPM and the play/stop sync kept working.

Replace sendBankName() with this:

const MODEL_ID = 0x09; // MC4 Pro (MC6 Pro = 0x06)

function sendBankName(name) {
  const safeTitle = sanitizeForMC6Title(name ?? "No Song");
  const normalized = safeTitle.padEnd(32);
  const songBytes = makeAscii(normalized);

  const payload = [
    0x00, 0x21, 0x24, MODEL_ID, 0x00, 0x70, 0x10,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
    ...songBytes
  ];

  payload.push(calculateChecksum(payload));
  sendMidiSysex(MIDI_DEVICE, payload);
}

Everything else in the script should stay as it is.

One thing to keep in mind if the name still doesn’t show up: the controller also rejects messages whose payload size doesn’t match its own bank name limit. The padEnd(32) here matches the MC6 Pro’s 32-character limit, I wasn’t able to find the MC4 Pro’s limits, so if it’s different you’d need to adjust that number along with the slice(0, 32) in sanitizeForMC6Title().

Hope that helps, let me know if it shows up now!

hey Augustin! unfortunately its still not working the error message also says:

Cannot find name ‘sanitizeForMC6Title’. Did you mean ‘sanitizeTitle’?(2552)

project-script-editor.mjs(24, 10): ‘sanitizeTitle’ is declared here.

I’m also not sure if I did it right by replacing the whole paragraph (the one which is about the sendBankName function) with your new script.

I was also wondering what I would need to do to implement a synched Loop On/Off display. Because I’ve maped the loop on/off function to the button (preset) B. Shouldn’t be that somehow quite similar to the start/stop solution?

Hey Augustin! I saw you answered but I cannot find it any more. can you send it again?

Hey @andypenn,

Since this thread is about the MC6 Pro script, I replied to you via DM instead to keep things tidy here. Should be in your DMs, let me know if you still can’t find it!

strangely I cannot find the DM anymore.