- Download synthesia.littlefoot file
- Drag-n-drop it to the piano picture in ROLI Dashboard
- Enable MIDI output in Settings:
- Choose finger-based channel for 'Key Lights':
Open ROLI Dashboard and click Device - Factory reset
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); | |
| } | |
| } |
@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 🙏
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):