MIDI BLE Tutorial

Pages
Contributors: MTaylor
Favorited Favorite 3

Using the FortySevenEffects MIDI Library

The gold standard for Arduino MIDI Libraries is the Arduino MIDI library written by GitHub user FortySevenEffects.

From the MIDI Tutorial,

  • It can use hardware or software serial ports (leaving the hardware serial port for printing debug messages!).
  • Incoming messages can be received by polling, or callbacks for specific messages can be installed.
  • The library can filter for messages on a specific midi channel, or receive on all channels.
  • It implements an optional "soft thru" port that can be configured to echo the input back to the output port.

It also has detailed documentation in doxygen format.

For some practical examples that demonstrate this library, take a look at the hookup guide for the SparkFun MIDI Shield.

The library will be configured in this way for the rest of this tutorial:

  • Hardware Serial Port
  • Messages Will be Received by Polling
  • Device Receives OMNI Mode
  • Soft Thru is Disabled

Adapting the library to operate on the nRF52832 is fairly straightforward. The HardwareSerial object can be passed to the MIDI_CREATE_INSTANCE function as normal and the library operates over the serial port. However, the baud rate is not correctly set. The nRF52832 board package only allows the standard discrete baud rates, so the port must be finagled after calling MIDI.begin()

Use the following code snippet to bend the port into 31250 baud.

language:c
#include "nrf52.h"

    ...

    MIDI.begin(MIDI_CHANNEL_OMNI);

    ...

    // The nRF52832 converts baud settings to the discrete standard rates.
    // Use the nrf52.h names to write a custom value, 0x7FFC80 after beginning midi
    NRF_UARTE_Type * myUart;
    myUart = (NRF_UARTE_Type *)NRF_UART0_BASE;
    myUart->BAUDRATE = 0x7FFC80;

The sketch "midi-lib-starter.ino" is an expanded version of the previous example, "ble-starter.ino", with the MIDI library included and configured. In the example, a note-on note-off pair of messages is sent out the serial port when the program starts in order to prove the system is working. During runtime, the nRF52832's user button can be pressed to send data out the port.

A function called parseMIDIonDIN() is called periodically when the BLE connection is valid. Inside, MIDI.read() is checked to see if new data is available. If so, the red LED is flashed but nothing else occurs. This is where MIDI data will be decoded and dealt with.

language:c
#include <MIDI.h>
#include "nrf52.h"
#include <BLEPeripheral.h>

#define LED_PIN    7 // LED on pin 7
#define RED_STAT_PIN    11 // LED on pin 7
#define GREEN_STAT_PIN    12 // LED on pin 7
#define BTN_PIN    6

uint8_t msgBuf[5];

unsigned long msOffset = 0;
#define MAX_MS 0x01FFF //13 bits, 8192 dec

// create peripheral instance, see pinouts above
//const char * localName = "nRF52832 MIDI";
BLEPeripheral blePeripheral;
BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 );
BLEDescriptor descriptor = BLEDescriptor("2902", 0);

MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI);

void setup() {
    delay(1000);

    //Setup diag leds
    pinMode(LED_PIN, OUTPUT);
    pinMode(RED_STAT_PIN, OUTPUT);
    pinMode(GREEN_STAT_PIN, OUTPUT);
    pinMode(BTN_PIN, INPUT_PULLUP);
    digitalWrite(LED_PIN, 1);
    digitalWrite(RED_STAT_PIN, 1);
    digitalWrite(GREEN_STAT_PIN, 1);

    setupBLE();

    // Initiate MIDI communications, listen to all channels
    MIDI.begin(MIDI_CHANNEL_OMNI);
    MIDI.turnThruOff();

    // The nRF52832 converts baud settings to the discrete standard rates.
    // Use the nrf52.h names to write a custom value, 0x7FFC80 after beginning midi
    NRF_UARTE_Type * myUart;
    myUart = (NRF_UARTE_Type *)NRF_UART0_BASE;
    myUart->BAUDRATE = 0x7FFC80;

    //Write data to the serial output pin to make sure the serial output is working.
    //Sometimes serial output only allows 1 byte out then hangs.  Resetting the
    //nRF52832 resolves the issue
    digitalWrite(RED_STAT_PIN, 0);
    MIDI.sendNoteOn(42, 66, 1);
    delay(500);
    MIDI.sendNoteOff(42, 66, 1); 
    digitalWrite(RED_STAT_PIN, 1);

}


void loop()
{
    BLECentral central = blePeripheral.central();
    //Send midi data by the press of the button to test while running.
    if(digitalRead(BTN_PIN) == 0){
        digitalWrite(GREEN_STAT_PIN, 0);
        MIDI.sendNoteOn(0x45, 80, 1);
        delay(100);
        MIDI.sendNoteOff(0x45, 80, 1);
        digitalWrite(GREEN_STAT_PIN, 1);
    }
    if (central) {
        while (central.connected()) {
            digitalWrite(GREEN_STAT_PIN, 0);
            //If connected, send midi data by the button here
            if(digitalRead(BTN_PIN) == 0){
                digitalWrite(GREEN_STAT_PIN, 0);
                MIDI.sendNoteOn(0x45, 80, 1);
                delay(100);
                MIDI.sendNoteOff(0x45, 80, 1);
                digitalWrite(GREEN_STAT_PIN, 1);
            }
            //Check if data exists coming in from BLE
            if (characteristic.written()) {
                digitalWrite(RED_STAT_PIN, 0);
                processPacket();
                digitalWrite(RED_STAT_PIN, 1); 
            }
            //Check if data exists coming in from the serial port
            parseMIDIonDIN();
        }

    }
    digitalWrite(LED_PIN, 1);
    digitalWrite(GREEN_STAT_PIN, 1);
    delay(500);
}

void processPacket()
{
    //Receive the written packet and parse it out here.
    uint8_t * buffer = (uint8_t*)characteristic.value();
    uint8_t bufferSize = characteristic.valueLength();
    //hang to give the LED time to show (not necessary if routines are here)
    delay(10);
}

void parseMIDIonDIN()
{
    if (  MIDI.read())
    {
        digitalWrite(RED_STAT_PIN, 0);
        //hang to give the LED time to show (not necessary if routines are here)
        delay(10);
        digitalWrite(RED_STAT_PIN, 1);
    }
}

void setupBLE()
{
    blePeripheral.setLocalName("MIDI BLE Starter"); //local name sometimes used by central
    blePeripheral.setDeviceName("MIDI BLE Starter"); //device name sometimes used by central
    //blePeripheral.setApperance(0x0000); //default is 0x0000, what should this be?
    blePeripheral.setAdvertisedServiceUuid(service.uuid()); //Advertise MIDI UUID

    // add attributes (services, characteristics, descriptors) to peripheral
    blePeripheral.addAttribute(service);
    blePeripheral.addAttribute(characteristic);
    blePeripheral.addAttribute(descriptor);

    // set initial value
    characteristic.setValue(0);

    // set event handlers - Alternate ways of checking for BLE activity
    //characteristic.setEventHandler(BLEWritten, BLEWrittenCallback);
    //characteristic.setEventHandler(BLESubscribed, BLESubscribedCallback);
    //characteristic.setEventHandler(BLEUnsubscribed, BLEUnsubscribedCallback);

    blePeripheral.begin();
}

This code is useful if as a framework for using both the BLE MIDI configuration as well as the FortySevenEffects MIDI configuration. The next section will fill parseMIDIonDIN() with code which translates incoming MIDI messages into BLE characteristic messages.