LED MIDI-Feedback using MIDIUSB-Library

I wrote the code below for a DIY-MIDI-Controller. It receives MIDI-Feedback for 4 LEDs and works fine when I use a Teensy Microcontroller.

Now I want to modify it to work on an Arduino Micro. In this case the MIDIUSB-Library is needed, but I have been struggeling for hours without any success. Does anybody know how to do that?

byte midiCh = 2; //* MIDI channel to read
const int LED_NR = 4; //Number of LEDs
const int LED_PINS[LED_NR] = {10, 11, 12, 13}; //Pins with LEDs
const int LED_MIDI_NOTES[LED_NR] = {64, 65, 66, 67}; //MIDI-Notes for LEDs

void setup() {
  for (int i = 0; i < LED_NR; i++) {
    pinMode(LED_PINS[i], OUTPUT);
  }
  usbMIDI.setHandleNoteOff(OnNoteOff);
  usbMIDI.setHandleNoteOn(OnNoteOn); 
}

void loop() {
  usbMIDI.read(midiCh); 
}

void OnNoteOn(byte channel, byte note, byte velocity) {
  if (velocity > 0) {
    for (int i = 0; i < LED_NR; i++) {
      if (note == LED_MIDI_NOTES[i]) {
        digitalWrite(LED_PINS[i],HIGH);
        break;
      }
    }
  } 
}

void OnNoteOff(byte channel, byte note, byte velocity) {
  for (int i = 0; i < LED_NR; i++) {
    if (note == LED_MIDI_NOTES[i]) {
      digitalWrite(LED_PINS[i],LOW);
      break;
    }
  }
}

You need to hash include that library first and then initialise it. See the examples inside the usbMIDI library for examples of how to do this.

Well, this is what I tried. I had a look at this example and tried to somehow merge it with my code. I attached my approach below - it compiles, but it does not work. I also do not know where to put the variable midiCh to assign the midi-channel to be read from. Can you tell me what I am doing wrong?

#include "MIDIUSB.h"

byte midiCh = 2; // MIDI channel to read
const int LED_NR = 4; //Number of LEDs
const int LED_PINS[LED_NR] = {10, 11, 12, 13}; //Pins with LEDs
const int LED_MIDI_NOTES[LED_NR] = {64, 65, 66, 67}; //MIDI-Notes for LEDs

void setup() {
  for (int i = 0; i < LED_NR; i++) {
    pinMode(LED_PINS[i], OUTPUT);
  }
//  usbMIDI.setHandleNoteOff(OnNoteOff);
//  usbMIDI.setHandleNoteOn(OnNoteOn); 
}



void loop() {
//  usbMIDI.read(); 

  midiEventPacket_t rx;
  do {
    rx = MidiUSB.read();
    if (rx.header != 0) {
      Serial.print("Received: ");
      Serial.print(rx.header, HEX);
      Serial.print("-");
      Serial.print(rx.byte1, HEX);
      Serial.print("-");
      Serial.print(rx.byte2, HEX);
      Serial.print("-");
      Serial.println(rx.byte3, HEX);
    }
  } while (rx.header != 0);
  
}

void OnNoteOn(byte channel, byte note, byte velocity) {
  if (velocity > 0) {
    for (int i = 0; i < LED_NR; i++) {
      if (note == LED_MIDI_NOTES[i]) {
        digitalWrite(LED_PINS[i],HIGH);
        break;
      }
    }
  } 
}

void OnNoteOff(byte channel, byte note, byte velocity) {
  for (int i = 0; i < LED_NR; i++) {
    if (note == LED_MIDI_NOTES[i]) {
      digitalWrite(LED_PINS[i],LOW);
      break;
    }
  }
}

// Arduino micro midi functions MIDIUSB Library

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

void controlChange(byte channel, byte control, byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
}

You seem to be using the MIDIUSB library but the code seems to be trying to use the usbMIDI library.

#include <USB-MIDI.h>

// Simple tutorial on how to receive and send MIDI messages.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.

USBMIDI_CREATE_DEFAULT_INSTANCE();

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    MIDI.begin(4);                      // Launch MIDI and listen to channel 4
}

void loop()
{
    if (MIDI.read())                    // If we have received a message
    {
        digitalWrite(LED_BUILTIN, HIGH);
        MIDI.sendNoteOn(42, 127, 1);    // Send a Note (pitch 42, velo 127 on channel 1)
        delay(1000);		            // Wait for a second
        MIDI.sendNoteOff(42, 0, 1);     // Stop the note
        digitalWrite(LED_BUILTIN, LOW);
    }
}

Note that MIDI in code uses channel numbers that are +1 of what MIDI instruments use. This is to protect musicians from the concept of channel zero.

So in the above code the code this line is wrong and should read:-

MIDI.sendNoteOn(42, 127, 0);    // Send a Note (pitch 42, velo 127 on channel 1)

The same applies to other such channel numbers.

Well, there might be a misunderstanding and some confusion about the different kinds of usb/midi-librarys.

usbMIDI (Teensy)
There is no library for the command usbMIDI. But you can use it without a library on a Teensy as it gots native support for usbMIDI. I used this in the original code.

MIDIUSB
In my second approach I included the MIDIUSB-Library which is needed for an Arduino Micro. In the code I uncommented all references to usbMIDI, so there is actually no usbMIDI used in this code.

There are more libraries out there, Grumpy_Mike referred to this one: USB-MIDI

Here is an other one: USBMIDI

There are even more, quite confusing, isn't it?

But I would like to use the MIDIUSB-Library as I plan to add the final code to a setup which is already using the MIDIUSB-Library

Correct, but the Teensy is a different class of processor so it is not bound by the same libraries. There are common ones but not every Arduino library works, because of the difference in hardware.

Ok, so i have just active looked at that recently.

#include "MIDIUSB.h"

#define LED 3

// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
  MidiUSB.flush();
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
  MidiUSB.flush();
}

void setup() {
  //Serial.begin(115200);
  pinMode(LED, OUTPUT);
  analogWrite(LED, 20);
  delay(1000);
  analogWrite(LED, 0);
}

// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void controlChange(byte channel, byte control, byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
  MidiUSB.flush();
}


void loop() {
  static uint32_t moment = millis();
  static bool noteon = true;

  midiEventPacket_t rx;

  if (millis() - moment > 2000) {   // every 2 seconds
    moment = millis();
    if (noteon) noteOn(0, 48, 100);  // ping C2
    else noteOff(0, 48, 64);  // the 3rd parameter is redundant in noteOff
    noteon = !noteon; // change the note.
  }


  do {
    rx = MidiUSB.read();
    if (rx.header != 0) {
      uint8_t type = rx.byte1 >> 4;
      uint8_t channel = rx.byte1 & 0xF;
      uint8_t value1 = rx.byte2;
      uint8_t value2 = rx.byte3;
      if (type == 0x9) analogWrite(LED, value2 * 2);
      if (type == 0x8) analogWrite(LED, 0);
      }
  } while (rx.header != 0);

  // controlChange(0, 10, 65); // Set the value of controller 10 on channel 0 to 65
}

and this should do the trick, although it is listening on any (the Omni) channel, and responds to any noteOn the led on pin 3 to it's velocity. This is the example i have.

Personally i don't really like the do {} while (),
but that is easy enough to change if you want to.

Now I got it ALMOST working. The following code works, but noteOff is not send correctly. It is possible to switch the leds on, but then I can not switch them off again. I tried to put MidiUSB.flush(); in several places, but no success so far. Anybody gots some advice?

#include <MIDIUSB.h>

byte midiCh = 2; // MIDI channel to read
const int LED_NR = 4; //Number of LEDs
const int LED_PINS[LED_NR] = {10, 11, 12, 13}; //Pins with LEDs
const int LED_MIDI_NOTES[LED_NR] = {64, 65, 66, 67}; //MIDI-Notes for LEDs

void setup() {
  for (int i = 0; i < LED_NR; i++) {
    pinMode(LED_PINS[i], OUTPUT);
  }
}

void handleNoteOn(byte channel, byte note, byte velocity) {
  if (velocity > 0) {
    for (int i = 0; i < LED_NR; i++) {
      if (note == LED_MIDI_NOTES[i]) {
        digitalWrite(LED_PINS[i],HIGH);
        break;
      }
    }
  } 
}

void handleNoteOff(byte channel, byte note, byte velocity) {
  for (int i = 0; i < LED_NR; i++) {
    if (note == LED_MIDI_NOTES[i]) {
      digitalWrite(LED_PINS[i],LOW);
      break;
    }
  }
}

void loop() {

 midiEventPacket_t rx = MidiUSB.read();

  if (rx.header != 0) {
    if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 > 0) {
      handleNoteOn(midiCh, rx.byte2, rx.byte3);
    } else if ((rx.byte1 & 0xF0) == 0x80) {
      handleNoteOff(midiCh, rx.byte2, rx.byte3);
    }
  } 

// midiEventPacket_t rx;
//    do {
//      rx = MidiUSB.read();
//        if (rx.header != 0) {
//          Serial.print("Unhandled MIDI message: ");
//          Serial.print(rx.byte1 & 0xF0, DEC);
//          Serial.print("-");
//          Serial.print(rx.byte1, DEC);
//          Serial.print("-");
//          Serial.print(rx.byte2, DEC);
//          Serial.print("-");
//          Serial.println(rx.byte3, DEC);
//        }
//  
//    } while (rx.header != 0);
  
}

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

In a lot of cases noteOff is send as a noteOn with velocity == 0
so :

void handleNoteOn(byte channel, byte note, byte velocity) {

  for (int i = 0; i < LED_NR; i++) {
    if (note == LED_MIDI_NOTES[i]) {
      if (velocity > 0) {
        digitalWrite(LED_PINS[i],HIGH);
        break;
      }
      else {
        digitalWrite(LED_PINS[i],LOW);
      }
    }
  } 
}

Deva_Rishi, you are AWSOME! Thank you so much for this hint, now it is finally working! Here is the final code:

#include <MIDIUSB.h>

byte midiCh = 2; // MIDI channel to read
const int LED_NR = 4; //Number of LEDs
const int LED_PINS[LED_NR] = {10, 11, 12, 13}; //Pins with LEDs
const int LED_MIDI_NOTES[LED_NR] = {64, 65, 66, 67}; //MIDI-Notes for LEDs

void setup() {
  for (int i = 0; i < LED_NR; i++) {
    pinMode(LED_PINS[i], OUTPUT);
  }
}

void handleNoteOn(byte channel, byte note, byte velocity) {
  for (int i = 0; i < LED_NR; i++) {
    if (note == LED_MIDI_NOTES[i]) {
      if (velocity > 0) {
        digitalWrite(LED_PINS[i],HIGH);
        break;
      }
      else {
        digitalWrite(LED_PINS[i],LOW);
      }
    }
  } 
}

void handleNoteOff(byte channel, byte note, byte velocity) {
  for (int i = 0; i < LED_NR; i++) {
    if (note == LED_MIDI_NOTES[i]) {
      digitalWrite(LED_PINS[i],LOW);
      break;
    }
  }
}

void loop() {

   midiEventPacket_t rx = MidiUSB.read();
    if (rx.header != 0) {
      if (rx.header == 0x09) {
        handleNoteOn(midiCh, rx.byte2, rx.byte3);
      } else if (rx.header == 0x08) {
        handleNoteOff(midiCh, rx.byte2, rx.byte3);
      }
    } 

// midiEventPacket_t rx;
//    do {
//      rx = MidiUSB.read();
//        if (rx.header != 0) {
//          Serial.print("Unhandled MIDI message: ");
//          Serial.print(rx.byte1 & 0xF0, DEC);
//          Serial.print("-");
//          Serial.print(rx.byte1, DEC);
//          Serial.print("-");
//          Serial.print(rx.byte2, DEC);
//          Serial.print("-");
//          Serial.println(rx.byte3, DEC);
//        }
//  
//    } while (rx.header != 0);
  
}

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

Compared to usbMIDI on a Teensy, this MIDIUSB-Library is a real pain in the !&$"*

yes but this final issue is caused by the midi player, the DAW.

Sure, but my rant was aimed at working with the MIDIUSB-Library and all this weird stuff you have to do in the loop() instead of just calling usbMIDI.read(midiCh);

I find it quite confusing to handle things like 0x90, 0x08 or 0xF and I still do not understand all of it. Is there a difference between 0x90 and 0x09? What does 0xF stand for? I found the documentation and examples not very helpful.

I tested the reading of MIDI-Signals with the following Code in the loop()

 midiEventPacket_t rx;
    do {
      rx = MidiUSB.read();
        if (rx.header != 0) {
          Serial.print("Received: ");
          Serial.print(" A:");
          Serial.print(rx.header, DEC);
          Serial.print(" B:");
          Serial.print(rx.byte1 & 0xF0, DEC);
          Serial.print(" C:");
          Serial.print(rx.byte1, DEC);
          Serial.print(" D:");
          Serial.print(rx.byte2, DEC);
          Serial.print(" E:");
          Serial.println(rx.byte3, DEC);
        }
  
    } while (rx.header != 0);

When sending a MIDI-Message with channel:2, note:67 and velocity:127 I get the following output in the Serial Monitor:

Received: A:9 B:144 C:151 D:67 E:127

So I understand that rx.byte2 sends the MIDI-note and rx.byte3 the velocity. But what do the other variables stand for and how can I get the MIDI-channel? From an other code I got the snippet Serial.print(rx.byte1 & 0xF0, DEC); which should transmit the MIDI-channel, but this does obviously not work in my setup and I even do not understand what 0xF0 stands for.

Can someone enlighten me?

That is the most significant byte of the MIDI message. The real message would be like 0x90, 0x80 or 0xF0. In the case of the first two the trailing zero is the channel number. So 0x90 is the MIDI message that tells you this is note on for MIDI channel 1.

0x91 is the MIDI message that tells you this is note on for MIDI channel 2.
0x92 is the MIDI message that tells you this is note on for MIDI channel 3.
0x93 is the MIDI message that tells you this is note on for MIDI channel 4.
and so on until you reach the maximum number of MIDI channels
0x9F is the MIDI message that tells you this is note on for MIDI channel 16.

Do you get what it happening?

It is the first nibble (half a byte) for system messages. the following nibble defines what sort of system message it is. Note there are therefore 16 different types of system messages, although a few are reserved and not used.

Well it is very useless printing it out in decimal, change that to HEX, because in decimal you can't see the patterns.

The other bytes are other sorts of messages.

Note off, Note on, Aftertouch (often not implemented in sound modules), Control Change (things like volume see part of the list below), Program Change (the sound of the instrument), Channel Pressure, and Pitch change, or pitch bend.

Then read it and re-read it. If you are still puzzled then ask specifically about what is puzzling you.

There are a lot of MIDI tutorials on line all waiting to be found.

List of some of the control messages, there are free (undefined) messages so you can design your own.

The scope of the MIDIUSB library is very limited. It just provides the USB descriptors to be discovered as a MIDI USB device by the computer, and provides functions to send low-level MIDI packets over the bus. It does not aim to provide a high-level MIDI API or any parsing of the MIDI packets, you just get the raw data as it's transmitted over USB.

To parse the packets to actual MIDI messages, you should use a different library (e.g. Control Surface or USB-MIDI).

If you want to understand how to interpret the raw MIDI packets yourself, you should look at the MIDI and USB-MIDI specifications:

Hey, thanks a lot for this helpful Feedback, guys!

Also realized, that my last code was receiving from all Midi-Channels and not only the one I specified with midiCh. I solved it by checking the midi-channel with an if-statement and got it working now.

Tried HEX instead of DEC, but I got completely different values then. So I used the following debug setup:

    midiEventPacket_t rx;
      do {
        rx = MidiUSB.read();
        if (rx.header != 0) {
          Serial.print("Received: ");
          Serial.print(" A:");
          Serial.print(rx.byte1 & 0xF, DEC);
          Serial.print(" B:");
          Serial.print(rx.byte2, DEC);
          Serial.print(" C:");
          Serial.println(rx.byte3, DEC);
        }
      } while (rx.header != 0);

Then I sent a Midi-Mesasage with channel:8 / note: 65 / velocity: 127 and got this output in the Serial-Monitor

Received: 7-65-127

So I got the correct output (with midi-channel minus 1). Now I would just like to understand how & why the midi-channel is transmitted by (rx.byte1 & 0xF, DEC)

No it was the same value but expressed in a different number base, it is the same value. Anyway you don't send data with a print statement, you just use it to look at numbers. By using decimal representation you can't see the pattern.
For example to send a control change on channel 4 for control byte 7 (channel volume) to a mid value that would be in hex the three bytes
0xB3, 0x07, 0x40
That is the B for a control change, 3 for the MIDI channel number, 7 for the volume control and finally 40 for half volume (half way between 7F and 0)
But in decimal this would read
179, 7, 64
Almost impossible to look at and see what it is sending.

The thing is, it isn't, which explains why you don't understand it

You have something wrong here because there is no MIDI message that can possibly start with the most significant bit as zero. The lowest valid MIDI message is 0x80 or 127.

Well let me break it down for you.
The first byte of a midi command consists of 2 parts. The most significant 4 bits contain the type of command, and the least significant 4 bits contain the channel. By using a bitwise 'AND' which is written as '&' you can mask certain bits, and in this case the 'mask' is 0xF or maybe it is a bit more clear when written 0x0F also known as 15 or in binary written as 0b00001111. Now by doing a bitwise AND only the bits that are present in both terms are part of the result, and in this way you can mask out any other bits which in this case means that you are removing the type of command from the byte and are left with a value between 0 - 15 which is the midi channel -1
if you want to receive the filter out the type of command you could do the same by doing & 0xF0 but since that would leave you with a value that is starting at 16 up 240 and taking steps of 16 you would want to bitshift that to the right 4 bits, and because that would anyway get rid of the least significant 4 bits, the masking becomes obsolete, and so to get the type of command you can just do the bitshift >> 4 to get the result you want.

Serial.print(rx.byte1 >> 4, DEC);

and if you use a hexadecimal representation you might find that easier to read, in fact if you use hexdecimals you can always represent a byte with 2 characters where 4 bits are represented with a single character from 0 to F
Learn to understand HEX and it will help you a lot.