This program enables a RedBearlab nRF51822 to be used as a "bridge device" with full bi-directional communication between MIDI through standard MIDI cables and BLE-MIDI. MIDI cables are connected to the UART. This allows for example to send MIDI data to synthesizers directly from a computer or a tablet via BLE-MIDI and vice-versa, "wires-free" . The Midi Manufacturers Association BLE-MIDI Specification is used. This project is inspired by Matthias Frick's "blidino" project which implements a USB-MIDI to BLE-MIDI bridge with the nRF51822 and is available at https://github.com/sieren/blidino. I owe to him all the BLE-MIDI to MIDI parsing part.

Dependencies:   BLE_API BufferedSerial mbed nRF51822

/media/uploads/popcornell/wp_20160713_22_30_15_rich.jpg

Video

Revision:
0:244f1d0a3810
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BLEMIDI_MIDI_Parser.h	Tue Aug 09 12:57:23 2016 +0000
@@ -0,0 +1,534 @@
+/*
+ *  Copyright (c) 2014 Matthias Frick   
+ *  
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in all
+ *  copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ *  SOFTWARE.
+ *
+ */
+
+#ifndef ____BLE_MIDI_Parser__
+#define ____BLE_MIDI_Parser__
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+
+
+
+
+
+    const int kMaxBufferSize=256; // max lenght for sysex buffer size 
+
+    uint8_t midiBuffer[3];
+    uint8_t sysExBuffer[kMaxBufferSize];
+    uint8_t alterSysExBuffer[kMaxBufferSize];
+    int midiBufferPtr ;   // int midiBufferPtr = 0;
+    int sysExRecBufferPtr ;  // int sysExRecBufferPtr = 0;
+    int sysExBufferPtr ;     // int sysExBufferPtr = 0; 
+
+
+    // MIDI event messages, state & stamps
+    int midiEventKind;
+    int midiEventNote;
+    int midiEventVelocity;
+    int midiState;   //int midiState = MIDI_STATE_TIMESTAMP;
+    int timestamp;
+
+
+    bool useTimestamp ; // bool useTimestamp = true;
+    
+    int lastTimestamp;
+    long lastTimestampRecorded ;  //  long lastTimestampRecorded = 0; 
+    int zeroTimestampCount;     //   int zeroTimestampCount = 0; 
+    //Receiver *midiRecv;
+
+
+
+
+////////////////////////////////////
+
+
+  static int MIDI_STATE_TIMESTAMP = 0;
+  static int MIDI_STATE_WAIT = 1;
+  static int MIDI_STATE_SIGNAL_2BYTES_2 = 21;
+  static int MIDI_STATE_SIGNAL_3BYTES_2 = 31;
+  static int MIDI_STATE_SIGNAL_3BYTES_3 = 32;
+  static int MIDI_STATE_SIGNAL_SYSEX = 41;
+
+  // for Timestamp
+  static int MAX_TIMESTAMP = 8192;
+  static int BUFFER_LENGTH_MILLIS = 10;
+
+
+  // for RPN/NRPN messages
+  static int PARAMETER_MODE_NONE = 0;
+  static int PARAMETER_MODE_RPN = 1;
+  static int PARAMETER_MODE_NRPN = 2;
+  int parameterMode = 0;
+  int parameterNumber = 0x3fff;
+  int parameterValue = 0x3fff;
+
+    void addByteToMidiBuffer(uint8_t midiEvent)
+    {
+      midiBuffer[midiBufferPtr] = midiEvent;
+      midiBufferPtr++;
+    }
+
+    void addByteToSysExBuffer(uint8_t midiEvent)
+    {
+      sysExBuffer[sysExBufferPtr] = midiEvent;
+      sysExBufferPtr++;
+    }
+
+    uint8_t replaceLastByteInSysExBuffer(uint8_t midiEvent)
+    {
+      sysExBufferPtr--;
+      uint8_t lastEvt = sysExBuffer[sysExBufferPtr];
+      sysExBuffer[sysExBufferPtr] = midiEvent;
+      sysExBufferPtr++;
+      return lastEvt;
+    }
+
+    void sendSysex()
+    {
+      
+      for(int i = 0 ; i<=sysExBufferPtr; i++ ) { // send sysex message on the UART 
+      UART.putc(sysExBuffer[i]) ; 
+      }
+      //midiRecv->SendSysEx(sysExBuffer, sysExBufferPtr, 0);
+    }
+
+    void createSysExRecovery()
+    {
+      sysExRecBufferPtr = sysExBufferPtr;
+      memcpy(alterSysExBuffer, sysExBuffer, sysExBufferPtr);
+    }
+
+    void sendSysexRecovery()
+    {
+     
+     for(int i = 0 ; i<=sysExRecBufferPtr; i++ ) { 
+      UART.putc(alterSysExBuffer[i]) ; 
+      }
+     // midiRecv->SendSysEx(alterSysExBuffer, sysExRecBufferPtr, 0);
+    }
+
+    uint8_t replaceLastByteInRecoveryBuffer(uint8_t midiEvent)
+    {
+      sysExRecBufferPtr--;
+      uint8_t lastEvt = alterSysExBuffer[sysExRecBufferPtr];
+      alterSysExBuffer[sysExRecBufferPtr] = midiEvent;
+      sysExRecBufferPtr++;
+      return lastEvt;
+    }
+
+    void addByteToRecoveryBuffer(uint8_t midiEvent)
+    {
+      alterSysExBuffer[sysExRecBufferPtr] = midiEvent;
+      sysExRecBufferPtr++;
+    }
+
+    void resetMidiBuffer()
+    {
+      memset(&midiBuffer[0], 0, sizeof(midiBuffer));
+      midiBufferPtr = 0;
+    }
+
+    void resetSysExBuffer()
+    {
+      memset(&sysExBuffer[0], 0, kMaxBufferSize);
+      sysExBufferPtr = 0;
+    }
+
+    void resetRecoveryBuffer()
+    {
+      memset(&alterSysExBuffer[0], 0, sizeof(alterSysExBuffer));
+      sysExRecBufferPtr = 0;
+    }
+
+    void sendMidi(uint8_t size)  // send MIDI Message on the UART 
+    {
+      for(int i = 0 ; i<=size ; i++ ) { 
+      UART.putc(midiBuffer[i]) ; 
+      }
+      
+    }
+
+ 
+
+    void parseMidiEvent(uint8_t header, const uint8_t event)
+    {
+      uint8_t midiEvent = event & 0xff;
+    
+      // printf((char*)midiEvent);
+      if (midiState == MIDI_STATE_TIMESTAMP)
+      {
+       // printf("Timestamp");
+        if ((midiEvent & 0x80) == 0)
+        {
+          // running status
+          midiState = MIDI_STATE_WAIT;
+        }
+
+        if (midiEvent == 0xf7)
+        {
+          // make sure this is the end of sysex
+          // and send alternative recovery stream
+          if (sysExRecBufferPtr > 0)
+          {
+            uint8_t removed = replaceLastByteInRecoveryBuffer(midiEvent);
+            sendSysexRecovery();
+            resetRecoveryBuffer();
+          }
+          midiState = MIDI_STATE_TIMESTAMP;
+          return;
+        }
+        else
+        {
+          // reset alternative sysex stream
+           resetRecoveryBuffer();
+        }
+      } // end of timestamp
+
+      if (midiState == MIDI_STATE_TIMESTAMP)
+      {
+        timestamp = ((header & 0x3f) << 7) | (midiEvent & 0x7f);
+        midiState = MIDI_STATE_WAIT;
+      }
+      else if (midiState == MIDI_STATE_WAIT)
+      {
+        switch (midiEvent & 0xf0) {
+          case 0xf0: {
+            switch (midiEvent) {
+              case 0xf0:
+                resetRecoveryBuffer();
+                resetSysExBuffer();
+                addByteToSysExBuffer(midiEvent);
+                midiState = MIDI_STATE_SIGNAL_SYSEX;
+                break;
+              case 0xf1:
+              case 0xf3:
+                // 0xf1 MIDI Time Code Quarter Frame. : 2bytes
+                // 0xf3 Song Select. : 2bytes
+                midiEventKind = midiEvent;
+                addByteToMidiBuffer(midiEvent);
+                midiState = MIDI_STATE_SIGNAL_2BYTES_2;
+                break;
+              case 0xf2:
+                // 0xf2 Song Position Pointer. : 3bytes
+                midiEventKind = midiEvent;
+                addByteToMidiBuffer(midiEvent);
+                midiState = MIDI_STATE_SIGNAL_3BYTES_2;
+                break;
+              case 0xf6:
+                // 0xf6 Tune Request : 1byte
+                addByteToMidiBuffer(midiEvent);
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xf8:
+                // 0xf8 Timing Clock : 1byte
+                //#pragma mark send timeclock // no on mbed OS 
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xfa:
+                // 0xfa Start : 1byte
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xfb:
+                // 0xfb Continue : 1byte
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xfc:
+                // 0xfc Stop : 1byte
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xfe:
+                // 0xfe Active Sensing : 1byte
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xff:
+                // 0xff Reset : 1byte
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+
+              default:
+                break;
+            }
+          }
+          break;
+          case 0x80:
+          case 0x90:
+          case 0xa0:
+          case 0xb0:
+          case 0xe0:
+            // 3bytes pattern
+            midiEventKind = midiEvent;
+            midiState = MIDI_STATE_SIGNAL_3BYTES_2;
+            break;
+          case 0xc0: // program change
+          case 0xd0: // channel after-touch
+            // 2bytes pattern
+            midiEventKind = midiEvent;
+            midiState = MIDI_STATE_SIGNAL_2BYTES_2;
+            break;
+          default:
+            // 0x00 - 0x70: running status
+            if ((midiEventKind & 0xf0) != 0xf0) {
+              // previous event kind is multi-bytes pattern
+              midiEventNote = midiEvent;
+              midiState = MIDI_STATE_SIGNAL_3BYTES_3;
+            }
+            break;
+        }
+      }
+      else if (midiState == MIDI_STATE_SIGNAL_2BYTES_2)
+      {
+        switch (midiEventKind & 0xf0)
+        {
+            // 2bytes pattern
+          case 0xc0: // program change
+            midiEventNote = midiEvent;
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0xd0: // channel after-touch
+            midiEventNote = midiEvent;
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0xf0:
+          {
+            switch (midiEventKind)
+            {
+              case 0xf1:
+                // 0xf1 MIDI Time Code Quarter Frame. : 2bytes
+                midiEventNote = midiEvent;
+                addByteToMidiBuffer(midiEventNote);
+                sendMidi(2);
+                resetMidiBuffer();
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              case 0xf3:
+                // 0xf3 Song Select. : 2bytes
+                midiEventNote = midiEvent;
+                addByteToMidiBuffer(midiEventNote);
+                sendMidi(2);
+                resetMidiBuffer();
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+              default:
+                // illegal state
+                midiState = MIDI_STATE_TIMESTAMP;
+                break;
+            }
+          }
+            break;
+          default:
+            // illegal state
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+        }
+      }
+      else if (midiState == MIDI_STATE_SIGNAL_3BYTES_2)
+      {
+        switch (midiEventKind & 0xf0)
+        {
+          case 0x80:
+          case 0x90:
+          case 0xa0:
+          case 0xb0:
+          case 0xe0:
+          case 0xf0:
+            // 3bytes pattern
+            midiEventNote = midiEvent;
+            midiState = MIDI_STATE_SIGNAL_3BYTES_3;
+            break;
+          default:
+            // illegal state
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+        }
+      }
+      else if (midiState == MIDI_STATE_SIGNAL_3BYTES_3)
+      {
+        switch (midiEventKind & 0xf0)
+        {
+            // 3bytes pattern
+          case 0x80: // note off
+           
+            midiEventVelocity = midiEvent;
+            addByteToMidiBuffer(midiEventKind);
+            addByteToMidiBuffer(midiEventNote);
+            addByteToMidiBuffer(midiEventVelocity);
+            sendMidi(3);
+            resetMidiBuffer();
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0x90: // note on
+            midiEventVelocity = midiEvent;
+            //timeToWait = calculateTimeToWait(timestamp);
+            
+            addByteToMidiBuffer(midiEventKind);
+            addByteToMidiBuffer(midiEventNote);
+            addByteToMidiBuffer(midiEventVelocity);
+            sendMidi(3);
+            resetMidiBuffer();
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0xa0: // control polyphonic key pressure
+            midiEventVelocity = midiEvent;
+            addByteToMidiBuffer(midiEventKind);
+            addByteToMidiBuffer(midiEventNote);
+            addByteToMidiBuffer(midiEventVelocity);
+            sendMidi(3);
+            resetMidiBuffer();
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0xb0: // control change
+            midiEventVelocity = midiEvent;
+            switch (midiEventNote & 0x7f)
+            {
+              case 98:
+                // NRPN LSB
+                parameterNumber &= 0x3f80;
+                parameterNumber |= midiEventVelocity & 0x7f;
+                parameterMode = PARAMETER_MODE_NRPN;
+                break;
+              case 99:
+                // NRPN MSB
+                parameterNumber &= 0x007f;
+                parameterNumber |= (midiEventVelocity & 0x7f) << 7;
+                parameterMode = PARAMETER_MODE_NRPN;
+                break;
+              case 100:
+                // RPN LSB
+                parameterNumber &= 0x3f80;
+                parameterNumber |= midiEventVelocity & 0x7f;
+                parameterMode = PARAMETER_MODE_RPN;
+                break;
+              case 101:
+                // RPN MSB
+                parameterNumber &= 0x007f;
+                parameterNumber |= (midiEventVelocity & 0x7f) << 7;
+                parameterMode = PARAMETER_MODE_RPN;
+                break;
+              case 38:
+                // data LSB
+                parameterValue &= 0x3f80;
+                parameterValue |= midiEventVelocity & 0x7f;
+
+                if (parameterNumber != 0x3fff) {
+                  if (parameterMode == PARAMETER_MODE_RPN)
+                  {
+                    addByteToMidiBuffer(midiEventKind);
+                    addByteToMidiBuffer(parameterNumber);
+                    addByteToMidiBuffer(parameterValue);
+                    sendMidi(3);
+                    resetMidiBuffer();
+                  }
+                  else if (parameterMode == PARAMETER_MODE_NRPN)
+                  {
+                    addByteToMidiBuffer(midiEventKind);
+                    addByteToMidiBuffer(parameterNumber);
+                    addByteToMidiBuffer(parameterValue);
+                    sendMidi(3);
+                    resetMidiBuffer();
+                  }
+                }
+                break;
+              case 6:
+                // data MSB
+                parameterValue &= 0x007f;
+                parameterValue |= (midiEventVelocity & 0x7f) << 7;
+
+                if (parameterNumber != 0x3fff)
+                {
+                  if (parameterMode == PARAMETER_MODE_RPN)
+                  {
+                    addByteToMidiBuffer(midiEventKind);
+                    addByteToMidiBuffer(parameterNumber);
+                    addByteToMidiBuffer(parameterValue);
+                    sendMidi(3);
+                    resetMidiBuffer();
+                  }
+                  else if (parameterMode == PARAMETER_MODE_NRPN)
+                  {
+                    addByteToMidiBuffer(midiEventKind);
+                    addByteToMidiBuffer(parameterNumber);
+                    addByteToMidiBuffer(parameterValue);
+                    sendMidi(3);
+                    resetMidiBuffer();
+                  }
+                }
+                break;
+              default:
+                // do nothing
+                break;
+            }
+            addByteToMidiBuffer(midiEventKind);
+            addByteToMidiBuffer(midiEventNote);
+            addByteToMidiBuffer(midiEventVelocity);
+            sendMidi(3);
+            resetMidiBuffer();
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0xe0: // pitch bend
+            midiEventVelocity = midiEvent;
+            addByteToMidiBuffer(midiEventKind);
+            addByteToMidiBuffer(midiEventNote);
+            addByteToMidiBuffer(midiEventVelocity);
+            sendMidi(3);
+            resetMidiBuffer();
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          case 0xf0: // Song Position Pointer.
+            midiEventVelocity = midiEvent;
+            addByteToMidiBuffer(midiEventKind);
+            addByteToMidiBuffer(midiEventNote);
+            addByteToMidiBuffer(midiEventVelocity);
+            sendMidi(3);
+            resetMidiBuffer();
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+          default:
+            // illegal state
+            midiState = MIDI_STATE_TIMESTAMP;
+            break;
+        }
+      }
+      else if (midiState == MIDI_STATE_SIGNAL_SYSEX)
+      {
+        if (midiEvent == 0xf7)
+        {
+          uint8_t repEvt = replaceLastByteInSysExBuffer(midiEvent);
+
+          resetRecoveryBuffer();
+          createSysExRecovery();
+          replaceLastByteInRecoveryBuffer(repEvt);
+          addByteToRecoveryBuffer(midiEvent);
+          sendSysex();
+          resetSysExBuffer();
+          midiState = MIDI_STATE_TIMESTAMP;
+        }
+        else
+        {
+          addByteToSysExBuffer(midiEvent);
+        }
+
+      }
+    }
+     
+
+#endif /* defined(____BLEParser__) */
\ No newline at end of file