Skip to content

Instantly share code, notes, and snippets.

@lukaville
Last active April 12, 2026 22:13
Show Gist options
  • Select an option

  • Save lukaville/75cd4ef155cdbfdcb333738b3057af4a to your computer and use it in GitHub Desktop.

Select an option

Save lukaville/75cd4ef155cdbfdcb333738b3057af4a to your computer and use it in GitHub Desktop.
Synthesia littlefoot script for ROLI Piano

Installation

Synthesia settings

  • Enable MIDI output in Settings:
image
  • Choose finger-based channel for 'Key Lights':
image

Uninstalling

Open ROLI Dashboard and click Device - Factory reset

/*
<metadata description="Synesthesia Lights" target="LUMI Keys" tags="MIDI;Light;" insertModes="false">
</metadata>
*/
const int numKeys = 49;
int keyColors[numKeys];
const int colorOff = 0xFF000000;
const int colorBlue = 0xFF0000FF; // Left Hand
const int colorLightBlue = 0xFF4080FF; // Left Thumb
const int colorGreen = 0xFF00FF00; // Right Hand
const int colorLightGreen = 0xFF80FF00; // Right Thumb
const int colorDefault = 0xFF00FF00; // Unknown / Default (Green)
const int colorOrange = 0xFFFF8000; // Pressed (Active)
// Hardcoded start note for 49-key setup (C2)
const int startNote = 36;
int getBaseNoteNumber() {
return startNote;
}
int getKey(int note) {
int base = getBaseNoteNumber();
if (note < base || note >= base + numKeys) {
return -1;
}
return note - base;
}
void initialise() {
for (int i = 0; i < numKeys; i++) {
keyColors[i] = colorOff;
}
}
// MIDI Constants
const int midiNoteOff = 0x8;
const int midiNoteOn = 0x9;
const int midiCC = 0xB;
// Key State for Output
int channel[numKeys];
int pressed[numKeys];
bool pendingOff[numKeys]; // Track if note off received while pressed
int getNote(int index) {
return getBaseNoteNumber() + index;
}
void keyStrike(int index, int z, int vz) {
pressed[index] = 1;
int note = getNote (index);
int chan = assignChannel (note);
channel[index] = chan;
sendNoteOn (chan, note, vz);
}
void keyPress(int index, int z, int vz) {
int note = getNote (index);
int chan = channel[index];
sendAftertouch (chan, note, z);
}
void keyLift(int index, int z, int vz) {
pressed[index] = 0;
// If a Note Off was pending (received while we were holding the key), apply it now
if (pendingOff[index]) {
keyColors[index] = colorOff;
pendingOff[index] = false;
}
int note = getNote (index);
int chan = channel[index];
sendNoteOff (chan, note, vz);
deassignChannel (note, chan);
}
int getColorForChannel(int channel)
{
// Channel is 0-indexed here (0-15)
// Synesthesia Protocol:
// 0: Unknown (Default)
// 1: Left Thumb
// 2-5: Left Hand
// 6: Right Thumb
// 7-10: Right Hand
// 11: Left Hand (Unknown finger)
// 12: Right Hand (Unknown finger)
if (channel == 1) return colorLightBlue;
if (channel >= 2 && channel <= 5) return colorBlue;
if (channel == 11) return colorBlue;
if (channel == 6) return colorLightGreen;
if (channel >= 7 && channel <= 10) return colorGreen;
if (channel == 12) return colorGreen;
return colorDefault;
}
void resetAll() {
for (int i = 0; i < numKeys; i++) {
keyColors[i] = colorOff;
pendingOff[i] = false;
}
}
void handleMIDI(int byte0, int byte1, int byte2) {
int channel = byte0 & 0x0F;
int status = (byte0 >> 4) & 0x0F;
int note = byte1;
int velocity = byte2;
// Handle CC message
if (status == midiCC) {
// CC 123: All Notes Off, CC 121: Reset All Controllers
if (byte1 == 123 || byte1 == 121) {
resetAll();
}
return;
}
int keyIndex = getKey(note);
if (keyIndex == -1) return;
if (status == midiNoteOn && velocity > 0) {
keyColors[keyIndex] = getColorForChannel(channel);
pendingOff[keyIndex] = false; // New note on cancels any pending off
}
else if (status == midiNoteOff || (status == midiNoteOn && velocity == 0)) {
if (pressed[keyIndex]) {
// If key is physically pressed, don't turn off light yet.
// Mark it as pending so it turns off when released.
pendingOff[keyIndex] = true;
} else {
keyColors[keyIndex] = colorOff;
pendingOff[keyIndex] = false;
}
}
}
void repaint() {
for (int i = 0; i < numKeys; i++) {
int drawColor = keyColors[i];
// If pressed, dim the light to show interaction, but keep it on
if (pressed[i] && drawColor != colorOff) {
drawColor = colorOrange;
}
fillPixel(drawColor, i, 0);
}
}
@victorazzo

Copy link
Copy Markdown

Hey @lukaville — thanks for this great code. You might find the changes in my fork interesting:
https://gist.github.com/victorazzo/ebc47cf3946afe439b448135fcef59e5

I added two major features using the 5 ROLI piano buttons (as explained in more detail in the updated README):

  • The ability to change octaves at any time while playing in Synthesia
  • The option to overlay Major, Minor, Pentatonic, and Blues scales, including real‑time scale mode and root‑note changes

@Boscop

Boscop commented Apr 12, 2026

Copy link
Copy Markdown

@lukaville @victorazzo Thanks for the script! Does this only work with the 49-key ROLI Piano, or also with the ROLI Piano M (two octaves)?

I'm trying to find a MIDI keyboard with illuminated keys that lets me set the keys' RGB color via real-time data from my custom music live performance application (similar use case as Synthesia, but for LED-aided live performance with the keyboard highlighting the current chord etc.).
So far the only confirmed candidate I have is the Novation 61SL MKIII (or its 49-key version), which allows setting the RGB colors in real-time via SysEx.
But I it's probably suboptimal to have the LEDs above the keys instead of inside the keys, because it's more indirect and thus requires more mental processing / higher cognitive load while playing (also, with small LEDs above the keys, there is less visual distinction between neighboring black & white keys, a LED could probably be more easily mistaken to mean a neighboring key).
Do you guys have an opinion on this?
(I don't have any experience with LED MIDI keyboards, these are just my pre-purchase thoughts.)

So I was looking for a keyboard that actually illuminates the keys, not just small LEDs above the keys, and it seems like the ROLI Piano (M) is the only one (?)

Now everything hinges on the question whether I can set its keys RGB LED color values in real-time via MIDI input that I send from my application on the computer via USB-MIDI.
Does it work flawlessly with this script, at extremely low latency?

I would really appreciate a reply from you guys!
Thanks 🙏

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