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
Video
Diff: MIDI_BLEMIDI_Parser.h
- Revision:
- 0:244f1d0a3810
diff -r 000000000000 -r 244f1d0a3810 MIDI_BLEMIDI_Parser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MIDI_BLEMIDI_Parser.h Tue Aug 09 12:57:23 2016 +0000 @@ -0,0 +1,473 @@ +/* + * Original work Copyright (c) 2015 Francois Best + * + * 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. + * + */ + + +/* +* Modified work by Samuele Cornell. +* The following code is adapted from Francois Best's Arduino MIDI Library v4.2 +* (see https://github.com/FortySevenEffects/arduino_midi_library) +*/ + + + +namespace MIDI { + +#define SysExMaxSize 128 +#define Use1ByteParsing 0 + +typedef uint8_t byte ; // byte isn't defined in mbed OS + +typedef uint8_t StatusByte; +typedef uint8_t DataByte; +typedef uint8_t Channel; + + + +/*! Enumeration of MIDI types */ +enum MidiType +{ + InvalidType = 0x00, ///< For notifying errors + NoteOff = 0x80, ///< Note Off + NoteOn = 0x90, ///< Note On + AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch + ControlChange = 0xB0, ///< Control Change / Channel Mode + ProgramChange = 0xC0, ///< Program Change + AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Pitch Bend + SystemExclusive = 0xF0, ///< System Exclusive + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + TuneRequest = 0xF6, ///< System Common - Tune Request + Clock = 0xF8, ///< System Real Time - Timing Clock + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset +}; + + +struct MidiMessage +{ + /*! The maximum size for the System Exclusive array. + */ + static const unsigned sSysExMaxSize = SysExMaxSize; // this + + /*! The MIDI channel on which the message was recieved. + \n Value goes from 1 to 16. + */ + Channel channel; + + /*! The type of the message + (see the MidiType enum for types reference) + */ + MidiType type; + + /*! The first data byte. + \n Value goes from 0 to 127. + */ + DataByte data1; + + /*! The second data byte. + If the message is only 2 bytes long, this one is null. + \n Value goes from 0 to 127. + */ + DataByte data2; + + /*! System Exclusive dedicated byte array. + \n Array length is stocked on 16 bits, + in data1 (LSB) and data2 (MSB) + */ + DataByte sysexArray[sSysExMaxSize]; + + /*! This boolean indicates if the message is valid or not. + There is no channel consideration here, + validity means the message respects the MIDI norm. + */ + bool valid; + + inline unsigned getSysExSize() const + { + const unsigned size = unsigned(data2) << 8 | data1; + return size > sSysExMaxSize ? sSysExMaxSize : size; + } + + MidiMessage() { // initialize + + channel=0; + type= InvalidType ; + data1= 0 ; + data2= 0 ; + valid =false ; + } + + +}; + + + StatusByte mRunningStatus_RX; + StatusByte mRunningStatus_TX; + Channel mInputChannel= 1 ; // default 1 + uint8_t mPendingMessage[3]; + uint8_t mPendingMessageExpectedLenght = 0; + uint8_t mPendingMessageIndex= 0; + + + + MidiMessage mMessage; + + + +inline void resetInput() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + +MidiType getTypeFromStatusByte(uint8_t inStatus) +{ + if ((inStatus < 0x80) || + (inStatus == 0xf4) || + (inStatus == 0xf5) || + (inStatus == 0xf9) || + (inStatus == 0xfD)) + { + // Data bytes and undefined. + return InvalidType; + } + if (inStatus < 0xf0) + { + // Channel message, remove channel nibble. + return MidiType(inStatus & 0xf0); + } + + return MidiType(inStatus); +} + +/*! \brief Returns channel in the range 1-16 + */ + + inline Channel getChannelFromStatusByte(uint8_t inStatus) + { + return (inStatus & 0x0f) + 1; + } + + + bool isChannelMessage(MidiType inType) + { + return (inType == NoteOff || + inType == NoteOn || + inType == ControlChange || + inType == AfterTouchPoly || + inType == AfterTouchChannel || + inType == PitchBend || + inType == ProgramChange); + } + + + +bool MIDI_to_BLEMIDI_Parser (void ) { + + + if (UART.readable() == 0) + // No data available. + return false; + + // Parsing algorithm: + // Get a byte from the serial buffer. + // If there is no pending message to be recomposed, start a new one. + // - Find type and channel (if pertinent) + // - Look for other bytes in buffer, call parser recursively, + // until the message is assembled or the buffer is empty. + // Else, add the extracted byte to the pending message, and check validity. + // When the message is done, store it. + + const uint8_t extracted = UART.getc(); + + if (mPendingMessageIndex == 0) + { + // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + if (isChannelMessage(getTypeFromStatusByte(mRunningStatus_RX))) + { + // Only these types allow Running Status + + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, + // so the running status does not apply here. + // It will be updated upon completion of this message. + } + + switch (getTypeFromStatusByte(mPendingMessage[0])) + { + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //resetInput(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + // The message can be any lenght + // between 3 and MidiMessage::sSysExMaxSize bytes + mPendingMessageExpectedLenght = MidiMessage::sSysExMaxSize; + mRunningStatus_RX = InvalidType; + mMessage.sysexArray[0] = SystemExclusive; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + resetInput(); + return false; + break; + } + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + { + // Reception complete + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } + else + { + // Waiting for more data + mPendingMessageIndex++; + } + + if (Use1ByteParsing==1) + { + // Message is not complete. + return false; + } + else + { + // Call the parser recursively + // to parse the rest of the message. + return MIDI_to_BLEMIDI_Parser(); + } + } + else + { + // First, test if this is a status byte + if (extracted >= 0x80) + { + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) + { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. + + mMessage.type = (MidiType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xf7: + if (mMessage.sysexArray[0] == SystemExclusive) + { + // Store the last byte (EOX) + mMessage.sysexArray[mPendingMessageIndex++] = 0xf7; + mMessage.type = SystemExclusive; + + // Get length + mMessage.data1 = mPendingMessageIndex & 0xff; // LSB + mMessage.data2 = mPendingMessageIndex >> 8; // MSB + mMessage.channel = 0; + mMessage.valid = true; + + resetInput(); + return true; + } + else + { + // Well well well.. error. + resetInput(); + return false; + } + + break; + default: + break; + } + } + + // Add extracted data byte to pending message + if (mPendingMessage[0] == SystemExclusive) + mMessage.sysexArray[mPendingMessageIndex] = extracted; + else + mPendingMessage[mPendingMessageIndex] = extracted; + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + { + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit + // the buffer. If this happens, try increasing MidiMessage::sSysExMaxSize. + if (mPendingMessage[0] == SystemExclusive) + { + resetInput(); + return false; + } + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + + if (isChannelMessage(mMessage.type)) + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + else + mMessage.channel = 0; + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) + { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else + { + // Then update the index of the pending message. + mPendingMessageIndex++; + + if (Use1ByteParsing==1) + { + // Message is not complete. + return false; + } + else + { + // Call the parser recursively to parse the rest of the message. + return MIDI_to_BLEMIDI_Parser(); + } + } + } +} + + +} // end namespace \ No newline at end of file