Skip to content

Instantly share code, notes, and snippets.

@m1no
Last active November 20, 2025 18:25
Show Gist options
  • Select an option

  • Save m1no/90c5776df3f1c06e067076d14477ef43 to your computer and use it in GitHub Desktop.

Select an option

Save m1no/90c5776df3f1c06e067076d14477ef43 to your computer and use it in GitHub Desktop.
MOTU Ultralite-mk5 API Bridge
// MOTU Ultralite-mk5 API Bridge
// Author: mino
// Date: 2023-02-22
// License: MIT
//
// Description:
// ------------
// I was looking for a easy way to mute and umute my main speakers via a Stream Deck button on the MOTU Ultralite-mk5.
// Unfortunately, Motu does not provide an HTTP API for this series, this feature is only available on the AVB devices.
// But with a bit of tinkering and reverse engineering of the Motu CueMix 5 app, it is possible to understand the used protocols and to send arbitrary commands.
// A quick and dirty program was written to allow the user to toggle the mute state of the main output of the device with automation in mind (e.g. Stream Deck).
//
// Installation:
// ------------
// Install Node.js and the depdenencies "ws" and "http" with "npm install ws http".
//
// Usage:
// ------
// Start the server with "node motu-bridge.js" and then call the server with a GET request to the endpoint /toggleMuteMain to toggle the mute state of the main output of the device.
//
// Reverse engineering of the Motu Mk5 API:
// ----------------------------------------
// MOTU provides at this time for the Ultralite-mk5 only a Windows application called "CueMix 5".
// This app is based on Electron and uses in the background a WebSocket connection to communicate with the device over its own network interface.
// By further looking into the installed source code of the app, as the app is not obfuscated, it is possible to run the index.html in a browser with enabled developer tools.
// In the developer tools, the network tab can be used to debug the WebSocket traffic between the app and the device and obtain the binary codes that are sent to the device for the different actions.
let motu_device_ip = '169.254.23.124';
let motu_device_port = '1280' // On my Windows machine, but another user had to change this to 1281 on his Mac
const http = require('http');
const WebSocket = require('ws');
let messageIndex = 0;
const server = http.createServer((req, res) => {
if (req.url === '/toggleMuteMain' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
messageIndex = (messageIndex + 1) % 2;
let muteToggle = getMuteToggle(messageIndex);
sendToMotu(muteToggle);
res.end();
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.write('Not found.\n');
res.end();
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000');
console.log('[GET] ToggleMute Endpoint: http://localhost:3000/toggleMuteMain');
});
function sendToMotu(binaryCode) {
const ws = new WebSocket(`ws://${motu_device_ip}:${motu_device_port}`);
ws.binaryType = 'arraybuffer';
ws.on('open', () => {
console.log('WebSocket connection opened.');
const buffer = hexStringToBuffer(binaryCode);
const arrayBuffer = bufferToArrayBuffer(buffer);
ws.send(arrayBuffer, { binary: true });
});
ws.on('close', () => {
console.log('WebSocket connection closed.');
});
return ws;
}
function hexStringToBuffer(hexString) {
return Buffer.from(hexString, 'hex');
}
// Convert a Buffer object to an ArrayBuffer
function bufferToArrayBuffer(buffer) {
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
}
function getMuteToggle(index) {
// 03fb0012000101 mute main output
// 03fb0012000100 unmute main output
return index === 0 ? '03fb0012000100' : '03fb0012000101';
}
@platypython
Copy link

FWIW I have to add my serial number to the URL for it to work.
Ultralite Mk5
Cuemix 5: 2.0.0+96480 (2)
Firmware: 2.0.8+2568:24
MacOS: 15.5

So my URL looks like this ws://127.0.0.1:1281/UL5EFF****"
I was pleased to discover that the server is listening on localhost, even though it has a 169 self-assign address.

If I leave the serial number off I get a 404 error.

Really appreciate the work here. I've been wanting to be able to mute my speakers without muting my headphones by assigning a hotkey, so using your discovery here I wrote a command line program that I can assign. Before I found this I was analyzing packets in wireshark, and that was going to take a very long time. Thank you!

@thetooth
Copy link

Btw, if you're looking for control of the headphone volume via a remote the commands are also fairly simple:

RX: 13 88 00 0a 32 : Left channel, -50db
RX: 13 88 00 0b 32 : Right channel, -50db

Same as the mutes, except for the missing length parameter, command, index, value

TX: 13 88 00 0a 00 01 32
TX: 13 88 00 0b 00 01 32

or

TX: 13 88 00 0a 00 01 32 13 88 00 0b 00 01 32

The L/R can be combined into a single message, either work and the balance of the phones can be controlled if you like.

Whenever you write a new volume and when you initially connect to the server the volume is echoed back to the client, so a controller can work asynchronously with the existing software and hardware controls on the device.

@Lenny-v13
Copy link

Hi,

I was just struggling with this.
To be honest I might be in over my head but I want to make a midi bridge and I've been trying (with a little support of chatGPT) to send commands to change faders.
Managed to get the MAIN masterfader to move but the Mic 1 fader will not budge.
Any insights tha might steer me in the correct path?

Also how do you get the cuemix to function inside a browser? I only get a virtual running.

Best,
Lennaert

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment