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

main.cpp

Committer:
popcornell
Date:
2016-08-09
Revision:
0:244f1d0a3810

File content as of revision 0:244f1d0a3810:

/*  
 *  Copyright (c) 2016 Samuele Cornell  
 *
 *  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.
 *
 */
 
 /*
 *   Thanks to Matthias Frick and his project blidino https://github.com/sieren/blidino 
 *   which also uses RedBearslab nRF51822 and implements a USB-MIDI to BLE-MIDI bridge. 
 *   His work made this possible and served as the main inspiration for this project . 
 *   The content of BLEMIDI_MIDI_Parser.h file is for the most part taken from his blidino project.  
 */

#include "mbed.h"

#include "ble/BLE.h"

#include "BufferedSerial.h"

#include "config.h"

BufferedSerial UART(UART_TX_PIN,UART_RX_PIN,BUFSERIAL_LENGHT) ; //UART_RX_PIN,BUFSERIAL_LENGHT  

#if ONLY_MIDI_to_BLEMIDI==0 

#include "BLEMIDI_MIDI_Parser.h" 

#endif

#if ONLY_BLEMIDI_to_MIDI==0   

#include "MIDI_BLEMIDI_Parser.h"

#endif



#define TXRX_BUF_LEN                    20 

#define RX_BUF_LEN                      256 


#if LIGHT_SHOW==1
DigitalOut redled(RX_LED);  // sets the two LEDS on the MIDI Shield as outputs, these will be used as a visual feedback for debug 
DigitalOut greenled(TX_LED); //
#endif 


/******************************************************************************************************************************
*INITIALIZE VARIABLES, BUFFERS and BLE-MIDI Service and Characteristic
******************************************************************************************************************************/

Ticker sendBLEMIDI_Ticker ; 
Ticker sendData_Ticker ; 
 
Timer t; // timer used for BLE-MIDI timestamps 

bool isConnected; 


////////////////////////////////////////////
bool sendBLEMIDI_flag = false ; 

////////////


BLEDevice ble; // BLE_API  

static Gap::ConnectionParams_t connectionParams;


// MIDI BLE Service and Characteristic UUID ( see Apple BLE-MIDI Spec. and Midi manufacter Association BLE-MIDI Spec.) 

static const uint8_t service_uuid[] = {0x03, 0xB8, 0x0E, 0x5A, 0xED, 0xE8, 0x4B, 0x33, 0xA7, 0x51, 0x6C, 0xE3, 0x4E, 0xC4, 0xC7, 0};
static const uint8_t characteristic_uuid[]   = {0x77, 0x72, 0xE5, 0xDB, 0x38, 0x68, 0x41, 0x12, 0xA1, 0xA9, 0xF2, 0x66, 0x9D, 0x10, 0x6B, 0xF3};

static const uint8_t service_uuid_rev[] = {0, 0xC7, 0xC4, 0x4E, 0xE3, 0x6C, 0x51, 0xA7, 0x33, 0x4B, 0xE8, 0xED, 0x5A, 0x0E, 0xB8, 0x03};

uint8_t txPayload[TXRX_BUF_LEN] = {0,};


GattCharacteristic  midiCharacteristic(characteristic_uuid, txPayload, 0, 20,GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE |
                                                   GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE |
                                                                   GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY |
                                                                     GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);

GattCharacteristic *midiChars[] = {&midiCharacteristic};
GattService BLEMIDIService(service_uuid, midiChars,sizeof(midiChars) / sizeof(GattCharacteristic *));
 
 
                                
/*********************************************************************************************************************
MIDI to BLE-MIDI  
*********************************************************************************************************************/

                                
#if ONLY_BLEMIDI_to_MIDI==0     
               

void sendBLEMIDI_Callback (void)
{

    sendBLEMIDI_flag = true ; 
    
    /**** This callback is called within an ISR with frequency set by the sendBLEMIDI_Ticker, because it is called in an Interrupt context it is preferably 
    doing the MIDI to BLEMIDI task in the main, the callback only set a flag which would be checked in the main loop. 
    This is because all the BLEMIDI to MIDI conversion is already performed within Interrupt context. ( thus as a consequence BLEMIDI to MIDI has higher priority )  
    *****/
}



void sendBLEMIDI(void) 
{

 
// MIDI::MIDI_to_BLEMIDI_Parser() parses incoming MIDI events from the UART (see MIDI_BLEMIDI_Parser.h) 
    if(isConnected == true && MIDI::MIDI_to_BLEMIDI_Parser()==true) {

        // a valid MIDI message has been parsed from the UART buffer

#if LIGHT_SHOW==1
        greenled = !(greenled);
#endif

        uint8_t BLEmidi_out[SysExMaxSize] ;
        uint8_t SysEx_Array_lenght ;

        uint16_t ticks = t.read_us() & 0x1fff; // read timer for timestamps 

        if(MIDI::mMessage.sysexArray[0] == MIDI::SystemExclusive)   { // message is  SysEx


            SysEx_Array_lenght = MIDI::mMessage.getSysExSize() ; // get SysEx message lenght

            uint8_t position = 0; // position for SysexArray 

            // header
           
            BLEmidi_out[position++] = 0x80 | ((ticks >> 7) & 0x3f); // header & timestampHigh
            BLEmidi_out[position++] = 0x80 | (ticks & 0x7f); // timestampLow

            for (int i = 0; i < SysEx_Array_lenght; i++) {
                if (i == SysEx_Array_lenght - 1) {
                    // modify last byte
                    BLEmidi_out[position++] = 0x80 | (ticks & 0x7f);

                    if (position == 20) {
                        
                        
                        ble.updateCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(), BLEmidi_out, 20);

                        position = 0;
                        // header
                        BLEmidi_out[position++] = 0x80 | (ticks >> 7) & 0x3f;
                    }
                }
                BLEmidi_out[position++] = MIDI::mMessage.sysexArray[i];
                if (position == 20) {
                  ble.updateCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(), BLEmidi_out, 20);
                
                    position = 0;
                    // header
                    BLEmidi_out[position++] = 0x80 | (ticks >> 7) & 0x3f;
                }

                ticks = t.read_us() & 0x1fff;
            }

            if (position > 0) {
                // send remains
             ble.updateCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(), BLEmidi_out, position);
             
            }
            
           
            MIDI::mMessage.sysexArray[0]= 0 ; // reset

        } 


        // message is not SysEx
        
        
        else {
            
            
            if(MIDI::mMessage.data1 == 0 ) { // no data1 only status 
            
            BLEmidi_out[0] = 0x80 | ((ticks >> 7) & 0x3f);
            BLEmidi_out[1] = 0x80 | (ticks & 0x7f);
            BLEmidi_out[2] = MIDI::mMessage.channel+MIDI::mMessage.type;
        
             ble.updateCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(), BLEmidi_out , 3);
            
            }
            
            if(MIDI::mMessage.data2 == 0 ) { // no data2 
            
            BLEmidi_out[0] = 0x80 | ((ticks >> 7) & 0x3f);
            BLEmidi_out[1] = 0x80 | (ticks & 0x7f);
            BLEmidi_out[2] = MIDI::mMessage.channel+MIDI::mMessage.type;
            BLEmidi_out[3] = MIDI::mMessage.data1 ;

            ble.updateCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(), BLEmidi_out , 4);
            
            } 
            
            if(MIDI::mMessage.data2 != 0 ) { 
            
            BLEmidi_out[0] = 0x80 | ((ticks >> 7) & 0x3f);
            BLEmidi_out[1] = 0x80 | (ticks & 0x7f);
            BLEmidi_out[2] = MIDI::mMessage.channel+MIDI::mMessage.type;
            BLEmidi_out[3] = MIDI::mMessage.data1 ;
            BLEmidi_out[4] = MIDI::mMessage.data2 ;

            ble.updateCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(), BLEmidi_out , 5);
            
            } 

        }// end else

    }// outer if

// invalid message or not connected

}

#endif 


/******************************************************************************************************************************
* BLE CALLBACKS 
******************************************************************************************************************************/
 
                                
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) 
{
    //device disconnected 
    
    isConnected = false ; 
    
#if ONLY_BLEMIDI_to_MIDI==0   
   sendBLEMIDI_Ticker.detach() ; // stop Ticker to save energy 
#endif
    
    ble.startAdvertising(); // start advertising
}


void connectionCallback(const Gap::ConnectionCallbackParams_t* params) {


isConnected=true ; 

// try update conn.parameters 

connectionParams.minConnectionInterval        = Config::minConnectionInterval;
connectionParams.maxConnectionInterval        = Config::maxConnectionInterval;
connectionParams.slaveLatency                 = Config::slaveLatency;
connectionParams.connectionSupervisionTimeout = Config::supervisionTimeout;

ble.updateConnectionParams(params->handle ,&connectionParams);


//start timers here
#if ONLY_BLEMIDI_to_MIDI==0    
sendBLEMIDI_Ticker.attach( sendBLEMIDI_Callback, SENDBLEMIDI_INTERVAL); // every SENDBLEMIDI_INTERVAL seconds calls sendBLEMIDI_Callback (ISR)   

t.start();  // start the timer used for BLEMIDI timestamps  

#endif 

} 


/****************************************************************************************************************************
BLE-MIDI to MIDI 
****************************************************************************************************************************/


#if ONLY_MIDI_to_BLEMIDI==0 

void parseIncoming(uint8_t *buffer, uint16_t bytesRead) {  // parse BLE-MIDI Events that have been written on the MIDI Characteristic 
  for (int i = 1; i < bytesRead; i++)
  {
    parseMidiEvent(buffer[0], buffer[i]); // parse and send through UART the MIDI Events received through BLE (see BLE_MIDI_Parser.h)
  }
}
                                


void onDataWritten(const GattWriteCallbackParams *Handler) // this functions is called within an ISR every time data has been written on nRF51822 GATT Server MIDI Characteristic   
{

#if LIGHT_SHOW==1
    redled = !(redled) ;
#endif

    uint8_t buf[TXRX_BUF_LEN];
    uint16_t bytesRead;
    if (Handler->handle == midiCharacteristic.getValueAttribute().getHandle()) {
        ble.readCharacteristicValue(midiCharacteristic.getValueAttribute().getHandle(),
                                    buf, &bytesRead);
        parseIncoming(buf, bytesRead);

    }

}
  
#endif  


/**************************************
MAIN 
***************************************/

int main(void)
{


#if LIGHT_SHOW==1
    redled = 1;
    greenled = 1;
#endif



    UART.baud(31250) ; // set UART baud rate to 31250 (MIDI standard)

    ble.init();
    ble.onDisconnection(disconnectionCallback);
    ble.onConnection(connectionCallback) ;

#if ONLY_MIDI_to_BLEMIDI==0
    ble.onDataWritten(onDataWritten);
#endif
    
    //conn. parameters ( rejected on iOS/OSX  see Apple BLE peripheral design guidelines but can work on Android ) 
    
    connectionParams.minConnectionInterval        = Config::minConnectionInterval;
    connectionParams.maxConnectionInterval        = Config::maxConnectionInterval;
    connectionParams.slaveLatency                 = Config::slaveLatency;
    connectionParams.connectionSupervisionTimeout = Config::supervisionTimeout;
    ble.setPreferredConnectionParams(&connectionParams);
    ble.getPreferredConnectionParams(&connectionParams);
    
    
    /* setup advertising */
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME,
                                     (const uint8_t *)"nRF51", sizeof("nRF51") - 1);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
                                     (const uint8_t *)service_uuid_rev, sizeof(service_uuid_rev));

    ble.accumulateScanResponse(GapAdvertisingData::SHORTENED_LOCAL_NAME,
                               (const uint8_t *)"nRF51", sizeof("nRF51") - 1);
    ble.accumulateScanResponse(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,(const uint8_t *)service_uuid_rev, sizeof(service_uuid_rev));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);

    /* 100ms; in multiples of 0.625ms. */
    ble.setAdvertisingInterval(160);

    // set adv_timeout, in seconds
    ble.setAdvertisingTimeout(0);
    ble.addService(BLEMIDIService);

    //Set Device Name
    ble.setDeviceName((const uint8_t *)"nRF51");

    ble.startAdvertising();
    
  
    while(1)   { //main loop

#if ONLY_BLEMIDI_to_MIDI==0    
            if(sendBLEMIDI_flag == true ) { // check if the flag is set 

            sendBLEMIDI_flag=false ;
            sendBLEMIDI() ; // parse MIDI Events from UART and send them over BLE

        }
#endif

        ble.waitForEvent(); // sleep 

    } // end main loop

} // end main