Controlling Addressable LED Strip with MIDI - Fade without blocking?

Greetings.

I have been working on a sketch with the goal of controlling an APA102 strip using MIDI messages via the serial port.

When a note is played and released, a corresponding LED on the strip should fade in and out accordingly. The color and brightness target will be determined by the note’s velocity but I have not yet fully implemented that function.

At the moment, the fading is being handled by a for loop. While this does work, it blocks the CPU for the duration of each fade, which means that only one note/LED can be triggered at a time. This is a problem because I can not effectively play a piece of music one note at a time. :stuck_out_tongue:

I have attached both the first and second revisions of the code in hopes that it might better demonstrate what I am trying to accomplish. The first revision allows any number of notes/LEDs to be triggered simultaneously but does not fade them, which is not as pretty.

Revision 0.1

#include <FastLED.h>
#include <MIDI.h>
 
 
MIDI_CREATE_DEFAULT_INSTANCE();
 
// -----------------------------------------------------------------------------
 
// This sketch is designed to work in conjunction with Hairless MIDI
// but may also be used with a MIDI shield.
// It can be used to control LEDs or other hardware using MIDI messages
 
#define LED 13                   // LED test pin
#define CLOCK_PIN 5
#define DATA_PIN 6
#define NUM_LEDS 50
#define BRIGHTNESS 255
#define LED_TYPE APA102
#define COLOR_ORDER BGR
CRGB leds[NUM_LEDS];
int type, note, velocity, channel, d1, d2; //Making MIDI data variables globally accessible
int mapped;
 
void programLEDs() {
  mapped = map(note, 48, 79, 0, NUM_LEDS); //Somewhat clumsily maps MIDI controller keys to LED numbers
  leds[mapped] = CRGB::Red; //Set all to red.
      FastLED.show();
   
}
 
 
void programLEDs2() {
  mapped = map(note, 48, 79, 0, NUM_LEDS); //Somewhat clumsily maps MIDI controller keys to LED numbers
  leds[mapped] = CRGB::Black; //Set all to black.
      FastLED.show();
   
}
 
void setup()
{
    pinMode(LED, OUTPUT); // Set LED pin output.
    MIDI.begin(); // Initialize MIDI, listening to channel 1.
    Serial.begin(9600); // Initialize serial with 9600 baud.  Ensure Hairless MIDI setting matches.
 
    FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness ( BRIGHTNESS );
}
 
void loop()
{
   
    if (MIDI.read()) { // MIDI message received?
      byte type = MIDI.getType(); // Message type?
      switch (type) {
       
        case midi::NoteOn: // Note has been played
         note = MIDI.getData1();
         velocity = MIDI.getData2();
         channel = MIDI.getChannel();
         programLEDs();
         break;
 
         case midi::NoteOff: // Note has been released
         note = MIDI.getData1();
         velocity = MIDI.getData2();
         channel = MIDI.getChannel();
         programLEDs2();
         break;
         
         default: // Debug data for serial monitor
         d1 = MIDI.getData1();
         d2 = MIDI.getData2();
         Serial.println(String("Message, type=") + type + ", data = " + d1 + " " + d2);
      }
     
     
}
}

Revision 0.2

#include <FastLED.h>
#include <MIDI.h>
 
 
MIDI_CREATE_DEFAULT_INSTANCE();
 
// -----------------------------------------------------------------------------
 
// This sketch is designed to work in conjunction with Hairless MIDI
// but may also be used with a MIDI shield.
// It can be used to control LEDs or other hardware using MIDI messages
 
#define LED 13                   //LED test pin
#define CLOCK_PIN 5
#define DATA_PIN 6
#define NUM_LEDS 50
#define BRIGHTNESS 255
#define LED_TYPE APA102
#define COLOR_ORDER BGR
CRGB leds[NUM_LEDS];
int type, note, velocity, channel, d1, d2; //Making MIDI data variables globally accessible
int mappedNote; //Map played note number to LED number
int mappedVhue; //Map played note velocity to (H)SV
int mappedVval; //Map played note velocity to HS(V)
int currentBrightness;
 
void programLEDs() {
  mappedNote = map(note, 48, 79, 0, NUM_LEDS); //Somewhat clumsily maps MIDI controller keys to LED numbers
  mappedVhue = map(velocity, 0, 127, 0, 255); //Somewhat clumsily maps MIDI controller velocity to LED numbers
  mappedVval = map(velocity, 0, 127, 0, 255); //Somewhat clumsily maps MIDI controller velocity to LED numbers
  for(int brightness=0; brightness<mappedVval; brightness++){ //Increment 'brightness' from 0 to 255
      leds[mappedNote].setHSV(0, 0, brightness); //Set played note to brightness value
      currentBrightness = brightness; //Update most recent brightness
      FastLED.show(); //Update LED display
    }
 
   
}
 
 
void programLEDs2() {
  mappedNote = map(note, 48, 79, 0, NUM_LEDS); //Somewhat clumsily maps MIDI controller keys to LED numbers
  mappedVhue = map(velocity, 0, 127, 0, 255); //Somewhat clumsily maps MIDI controller velocity to LED numbers
  mappedVval = map(velocity, 0, 127, 0, 255); //Somewhat clumsily maps MIDI controller velocity to LED numbers
    for(int brightness=currentBrightness; brightness>=0; brightness--){ //Decrement brightness from most recent to 0
      leds[mappedNote].setHSV(0, 0, brightness); //Set played note to brightness value
      FastLED.show(); //Update LED display
    }
 
   
}
 
void setup()
{
    pinMode(LED, OUTPUT); // Set LED pin output.
    MIDI.begin(); // Initialize MIDI, listening to channel 1.
    Serial.begin(9600); // Initialize serial with 9600 baud.  Ensure Hairless MIDI setting matches.
 
    FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness ( BRIGHTNESS );
}
 
void loop()
{
   
    if (MIDI.read()) { // MIDI message received?
      byte type = MIDI.getType();
      switch (type) {
       
        case midi::NoteOn:
         note = MIDI.getData1();
         velocity = MIDI.getData2();
         channel = MIDI.getChannel();
         programLEDs();
         break;
 
         case midi::NoteOff:
         note = MIDI.getData1();
         velocity = MIDI.getData2();
         channel = MIDI.getChannel();
         programLEDs2();
         break;
         
         default:
         d1 = MIDI.getData1();
         d2 = MIDI.getData2();
         Serial.println(String("Message, type=") + type + ", data = " + d1 + " " + d2);
      }
     
     
}
}

Any advice at all would be grately appreciated.

Thanks!

You can't use any kind of blocking mechanism, if you want to play multiple notes at the same time, and make the corresponding LEDs fade at the same time.

Google state machine.

Thank you for the input.

While I am somewhat struggling to comprehend state machines (I'm sure that it will click eventually), I have come up with another method of fading which shouldn't block the CPU.

What if I were to set a state variable for the note status, like this:

if (MIDI.read()) { // MIDI message received?
byte type = MIDI.getType();
switch (type) {

case midi::NoteOn:
note = MIDI.getData1();
velocity = MIDI.getData2();
channel = MIDI.getChannel();
NoteState = ON
break;

case midi::NoteOff:
note = MIDI.getData1();
velocity = MIDI.getData2();
channel = MIDI.getChannel();
NoteState = OFF
break;

And then use a switch statement within the main loop to increment or decrement the LED brightness depending upon the state of the NoteState variable?

switch (NoteState) {
case ON:
[IF STATEMENT USING MILLIS FOR INTERVAL]
break;
case OFF:
[IF STATEMENT USING MILLIS FOR INTERVAL]
break;
default:
break;
}

Perhaps I'm missing something?

Perhaps I’m missing something?

The actual code that goes in place of [IF STATEMENT USING MILLIS FOR INTERVAL] and [IF STATEMENT USING MILLIS FOR INTERVAL], for one thing.

Yes, that is certainly true. What I eventually wrote for the switch was:

     switch (NoteState) {
 
     
 
      case 1: //NoteState = ON
        currentMillis = millis();
        if(currentMillis - previousMillis > interval && brightness < mappedVval) {
        previousMillis = currentMillis;
        brightness++;
        leds[mappedNote].setHSV(0, 0, brightness);
        currentBrightness = brightness;
        FastLED.show();
        }
        break;
       
      case 0: //NoteState = OFF
        currentMillis = millis();
        if(currentMillis - previousMillis > interval && brightness > 0) {
        previousMillis = currentMillis;
        brightness--;
        leds[mappedNote].setHSV(0, 0, brightness);
        FastLED.show();
        }
        break;
 
      default:
      break;
     }

As with my previous attempt, it works for individual notes but not simultaneous ones. I don’t quite understand why this is the case.

If you are going to deal with multiple notes, don’t you suppose that you need more than two states? If one state is note 1 on, and the other state is note 1 off, how can you expect to deal with the second note?

Create a visualNote class that takes a note & time as a input to a start() method. start(int note,float time); or something similar. Also, give it an idleTime() method. Set up an array of these objects, say 10 of them. When a MIDI note comes in, grab the first inactive instance from your array, start it by jamming in the note & time and basically forget it. In your loop(), call idleTime(); on every visualNote instance letting them write to your lights. After they all get time to draw, call show(); and do it all over again.

In idleTime() you only worry about one the note you're dealing with. Look at the time now, look at the time the note started, decide how your light should be set, do what you need to do and get out. Do NOT use delay();

Later you can add brightness to the LED to be a function of the volume of the note played.

The neat thing is, you only have to think about one note and the structure multiplies this out into multiple notes. Or voices. Heck different instruments can even have different colors.

Hope this helps.

-jim lee