Forbidden Planet “Krell” Display – MIDI Notes

This is my first attempt at some kind of MIDI related visualisation for my Forbidden Planet “Krell” Display. I thought I’d start easy and simply map MIDI notes onto the segments and create a simple display. That turned out to be an awful lot more involved than I imagined…

In fact, this is quite sub-optimal – it largely works but does suffer still from the occasional missed MIDI message, which will almost certainly lead to stuck notes at some point.

In this post I talk through what the issue is and the various mitigations I’ve put in place to attempt to minimise the problem. Finally I present some alternative ideas that I’ll follow up in future posts.

Fundamentally, these days there are much better options for this than an Arduino Nano or Uno.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

If you are new to Arduino, see the Getting Started pages.

Parts list

I’ve started with an Arduino Nano, but as we’ll see later, choosing a single-core, 8-bit microcontroller is certainly playing this game on “hard mode”.

I’ve written up what I have for my own interest as it is an interesting problem to solve and interesting to me to see how far I can push it, but if you’re after a more useful solution it is probably worth waiting for a later post where I can show how to use a more suitable microcontroller for this task 🙂

The Circuit

As detailed in my previous post I can just about get away with powering my 8 LED rings from the Arduino provided I keep the power levels down and don’t drive the too much.

I’ve actually used a ready-made MIDI module with a hardware MIDI THRU, again for reasons that will hopefully become clear as I talk about the performance later on.

Using THRU allows me to send data into the Arduino and use that same data to drive a sound source too.

The Code

Once again, I’m using the LED “mapping” technique from my original Forbidden Planet “Krell” Display test code, so that side of things is largely unchanged.

I’m adding in basic MIDI handling using a NoteOn and NoteOff parser to indicate that the LEDs need turning on or off. In this first version I’m just using simple logic as follows:

void handleNoteOn(byte channel, byte pitch, byte velocity) {
if (velocity == 0) {
// Handle this as a "note off" event
handleNoteOff(channel, pitch, velocity);
return;
}

int led = pitch - MIDI_NOTE_START;
ledOn (led);
}

void handleNoteOff(byte channel, byte pitch, byte velocity) {
int led = pitch - MIDI_NOTE_START;
ledOff (led);
}

There is some bounds checking added too, but that is essentially it. The downside of this approach is that if several NoteOn messages are received, the first NoteOff message will turn the LED off even if another note is still hanging on.

By default I’ve set it to listen to all channels, but a single channel can be set at the top of the function if required, along with the range of notes to recognise.

#define MIDI_CHANNEL MIDI_CHANNEL_OMNI  // 1 to 16 or MIDI_CHANNEL_OMNI
#define MIDI_NOTE_START 48 // C3
#define MIDI_NOTE_END MIDI_NOTE_START+39

I experimented with some simple note counting and that would work if the MIDI reception was reliable, but there is a fundamental problem with this code on an 8-bit, single-core, Arduino. The WS2812 LEDs require very precise timing and the Adafruit library has some very hand-crafted assembler running with all interrupts disabled, to achieve it. The longer the string of LEDs, the more time is spent with all interrupts disabled, and so no storing up of, for example, data received over the serial port (MIDI).

This means that I need to ensure as much MIDI handling is done as possible outside of the LED updating, and try to ensure that the actual writing to the LED strip is done as sparingly as possible.

The techniques I’m using to do this include:

  • Only allowing writes to the LED strip once every 16 or more so passes through the Arduino’s loop() function, but calling MIDI handling every time (eventually I went for once every 256 scans).
  • Ensure that data is only written out to the LEDs if something has actually changed. This means that the Arduino isn’t spending time updating the LED string with the same data, stopping it receiving the next set of MIDI messages.
  • Run the MIDI library with Use1ByteParsing = false. This means that each call to MIDI.read() will only return either if there is no data or if the MIDI library has a complete message. Without this, a single NoteOn message requires three calls to MIDI.read() before the message is complete.
  • Ensure MIDI THRU is turned off. For serial MIDI, by default, the library provides software-driven MIDI THRU handling which means processor (and serial port) time is spent sending out all MIDI data received. This can be turned off on initialisation using MIDI.turnThruOff().
  • Choose an IO pin that wasn’t part of PORTD to ensure no inadvertent clashes with the UART (which is PORTD 0,1). So that means using something in the range D8-D13 (which map onto PORTB for the ATmega328). This probably makes no difference, tbh, but also doesn’t hurt either.
  • Add a global inactivity timer to turn all LEDs off after any period with no MIDI activity to catch any stuck notes.

I added a timing GPIO pin to attempt to work out how much time was spent updating the LEDs. It takes just under 2mS to update the strip of 56 (7*8 – there are 7 pixels in each ring, and 8 rings, even though I’m only using 5 per ring myself). Interrupts are disabled for most of that time (from what I can see of the code in the Adafruit library’s show routine). Note, this is quite hand-wavy and the exact timings will depend on the data sent. I believe it is something like 1.2-1.3 uS per bit, so for a single 24-bit pixel (three colour values) that is ~30-35uS per pixel. For a string of 56 pixels that is around 1.7mS in total just for the data. There are some reset and rest times too, so that is where my “almost 2mS” comes from.

MIDI runs at 31250 baud, which I believe means receiving one bit over the serial port every 1/31250 or every 32 uS or so. A complete single byte in MIDI therefore takes around 320uS (there are 8 data bits, one start and one stop bit). So a typical three-byte message is just under 1mS. I don’t know for sure, but it seems like disabling all interrupts long enough to have received two complete three-byte MIDI messages doesn’t seem like a good thing to be doing…

I don’t really know much about the hardware buffer of the UART on the ATMega328 but from conversations on Mastodon (thanks Rue) and having a dig into the USART section of the datasheet, there seems to be a single character buffer, so a 2mS pause would definitely be enough time for 4 or 5 characters to be completely dropped.

This is about as far as I’ve gone using the Adafruit_NeoPixel library as is, with a single string of 40 pixels and MIDI. I have a few thoughts on where to go next, so I’ll explore those in a future post.

Find it on GitHub here.

Closing Thoughts

This works ok for relatively simple cases and is passable for more complex ones, as long as I’m after effect not accuracy 🙂

Some other things to try include:

  • Splitting the string up and using different IO pins on the Arduino to drive the separate elements. A single 7-pixel ring probably takes around 300uS to update – which is comparable with the time taken to receive a single MIDI byte.
  • Use a different library (e.g. FastLED) or even hard-code the updates myself (there is an excellent discussion here on how to do that).
  • Use a more modern microcontroller. The Raspberry Pi Pico’s PIO is perfect for things like driving WS2812 LEDs for example. Alternatively a dual-core Pico or ESP32 would allow me to split out the LED updating from the MIDI reception.
  • Put a microcontroller in each display unit and string them together with MIDI IN/THRU rather than as a single LED string.

That latter option is quite tempting anyway – a Nano (or even a Micro) equivalent is pretty cheap these days. I could possibly even using something like an ATtiny. This would be an excellent approach if I built a microcontroller PCB for the displays and used those mounting posts I added to the physical design. I could keep using cheap pixel rings or really go for it and mount LEDS on the PCB.

But to be honest, for what I’m doing, I could even get away with a non-addressable LED if I’m adding a microcontroller to each unit…

Kevin

Leave a comment