Leon Chu
Published © GPL3+

BLE MIDI Puck for Musical Application Control

Mini gesture MIDI controller that hides in the palm of your hand for manipulating musical and interactive light.

IntermediateWork in progress2 hours226

Things used in this project

Hardware components

RSL10-SENSE-GEVK
ON Semiconductor RSL10-SENSE-GEVK
×1

Software apps and online services

Windows 10
Microsoft Windows 10

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Rubber band

Story

Read more

Custom parts and enclosures

Button Cover

Dimension 32.0mm x 30.3 mm x 9.6mm

Enclosure

Dimension 36.3mm x 32.8mm x 11.0 mm

Error uploading file to Sketchfab.

Schematics

Midi Data Flow

Code

Main.c

C/C++
Main code
//-----------------------------------------------------------------------------
// Copyright (c) 2018 Semiconductor Components Industries LLC
// (d/b/a "ON Semiconductor").  All rights reserved.
// This software and/or documentation is licensed by ON Semiconductor under
// limited terms and conditions.  The terms and conditions pertaining to the
// software and/or documentation are available at
// http://www.onsemi.com/site/pdf/ONSEMI_T&C.pdf ("ON Semiconductor Standard
// Terms and Conditions of Sale, Section 8 Software") and if applicable the
// software license agreement.  Do not use this software and/or documentation
// unless you have carefully read and you agree to the limited terms and
// conditions.  By using this software and/or documentation, you agree to the
// limited terms and conditions.
//-----------------------------------------------------------------------------

/* Ble Midipuck  by @Chuartdo */

#include <BDK.h>
#include <BSP_Components.h>
#include <BLE_Components.h>
#include <ansi_color.h>
#include <SEGGER_RTT.h>

#include <stdio.h>
#include "BLEMIDI.h"


/*************** Parameter Configuration ***********************/
#define BLE_DEVICE_NAME "Chuartdo MIDIPuck"
#define SENSOR_SAMPLE_RATE 30

// Midi Continuous controller fuction for each channel
#define CC_FUNC_1 1
#define CC_FUNC_2 4
#define CC_FUNC_3 7




/* Prints message into serial terminal when button is pressed / released. */
void PB_TransitionEvent(void *arg);



/* Copies measurement data for printing of status messages. */
void BHY_OrientationCallback(bhy_data_generic_t *data, bhy_virtual_sensor_t sensor);


int32_t noa1305_status = -1;

int32_t bme680_status = -1;
struct BME680_ENV_Data bme680_output = { 0 };

int32_t bhy_status = -1;
bhy_data_vector_t bhy_orientation;


int32_t dmic_value = 0;
int32_t dmic_max = 0;
int32_t dmic_min = INT32_MAX;
int midi_msg_mode = 1;

double mapRange(double a1,double a2,double b1,double b2,double s)
{
	return b1 + (s-a1)*(b2-b1)/(a2-a1);
}

void setMinMax() {


}

uint8_t prevNotes[6];

void sendMultiChannelNotes(int8_t c, int8_t note) {
	// Turn off previous note in channel c

        if ( note != prevNotes[c] ) {
            	sendNoteOff(c,prevNotes[c], 0);

                sendNoteOn(c,note,120);
                prevNotes[c] = note;
        }
}


int main(void)
{   isConnected = 1;
	timestamp = 0;
    int32_t retval = 0;

    /* Initialize BDK library, set system clock (default 8MHz). */
    BDK_InitializeFreq(HAL_CLK_CONF_8MHZ);
    HAL_I2C_SetBusSpeed(HAL_I2C_BUS_SPEED_FAST);

    /* Initialize all LEDs */
    LED_Initialize(LED_RED);
    LED_Initialize(LED_GREEN);
    LED_Initialize(LED_BLUE);

    /* Test LEDs are working */
    LED_On(LED_GREEN);
    LED_On(LED_RED);
    HAL_Delay(500);

    LED_Off(LED_RED);
    LED_On(LED_BLUE);

    /* Initialize BLE stack */
    BDK_BLE_Initialize();
    BDK_BLE_SetLocalName(BLE_DEVICE_NAME);
    BLE_ICS_Initialize(NULL);

    /* Initialize Button to call callback function when pressed or released. */
    BTN_Initialize(BTN0);
    BTN_Initialize(BTN1);

    /* AttachScheduled -> Callback will be scheduled and called by Kernel Scheduler. */
    /* AttachInt -> Callback will be called directly from interrupt routine. */
    BTN_AttachScheduled(BTN_EVENT_TRANSITION, &PB_TransitionEvent, (void*)BTN0, BTN0);
    BTN_AttachScheduled(BTN_EVENT_TRANSITION, &PB_TransitionEvent, (void*)BTN1, BTN1);

    /** Initialize NOA1305. */
    retval = NOA1305_ALS_Initialize();
    if (retval == NOA1305_OK)
    {
        retval = NOA1305_ALS_StartContinuous(0, NULL);
        if (retval == NOA1305_OK)
        {
            noa1305_status = 0;
        }
        else
        {
            noa1305_status = 2;
        }
    }
    else
    {
        noa1305_status = 1;
    }



    /** Initialize BHI160 + load BMM150 RAM patch */
    retval = BHI160_NDOF_Initialize();
    if (retval == BHY_SUCCESS)
    {
        retval = BHI160_NDOF_EnableSensor(BHI160_NDOF_S_ORIENTATION, BHY_OrientationCallback, SENSOR_SAMPLE_RATE);
        if (retval == BHY_SUCCESS)
        {
            bhy_status = 0;
        }
        else
        {
            bhy_status = 2;
        }
    }
    else
    {
        bhy_status = 1;
    }



    LED_Off(LED_RED);  LED_Off(LED_GREEN);  LED_Off(LED_BLUE);
    printf("APP: Entering main loop.\r\n");

    uint8_t note = 0, lastNote = 0;

    uint8_t patch = 0, lastPatch = 0;
    uint16_t pitch =0, lastPitch = 0;

    float min_p = INT32_MAX, max_p = INT32_MIN;
    int32_t  lastTime = 0;

    while (1)
    {
        /* Execute any events that have occurred & refresh Watchdog timer. */
        BDK_Schedule();

     if (timestamp - lastTime > 500) {
    	 lastTime = timestamp;
    	 LED_Off(LED_RED);
    	 LED_Off(LED_BLUE);
    	 LED_Off(LED_GREEN);
     }

        /* Print status information every second */
        if (HAL_TIME_ELAPSED_SINCE(timestamp) >= ( 1000 / SENSOR_SAMPLE_RATE) )
        {
            timestamp = HAL_Time();

            //printf(RTT_CTRL_CLEAR "Test status:\r\n");

           // printf("NOA1305 initialization: %s\r\n", noa1305_status == 0 ? COLORIZE("OK", GREEN) : COLORIZE("ERROR", RED));
            if (noa1305_status == 0)
            {
                uint32_t lux = 0;
                NOA1305_ALS_ReadLux(&lux);
               // printf("NOA1305 measured value: " COLORIZE("%lu", YELLOW) " lux\r\n\n", lux);

                // Light Sensor value mapping to
                if (lux > 60)
        			sendNoteOn(0, (int) mapRange(60,1000,60,127,lux),120);


            }



            if (bhy_status == 0)
            {
                float h = bhy_orientation.x / 32768.0f * 360.0f;
                float p = bhy_orientation.y / 32768.0f * 360.0f;
                float y = bhy_orientation.z / 32768.0f * 360.0f;



                if (midi_msg_mode == 0) {   // Pitch and Mod wheel
                	if (h < min_p )
                	   min_p = h;
                	if (h > max_p)
                		max_p = h;
                 }
                else if (midi_msg_mode == 2) {

                	// Send orientation data as MIDI Continuous Controller messages
                	LED_On(LED_GREEN);LED_On(LED_RED);
                	sendMidiControllerMessages(0, CC_FUNC_1,  (uint8_t)mapRange(0,360,0,127,h),
												  CC_FUNC_2,  (uint8_t)mapRange(0,360,0,127,p),
												  CC_FUNC_3,  (uint8_t)mapRange(0,360,0,127,y));
                }
                else if (midi_msg_mode == 4) {


						LED_On(LED_RED);
						patch = p;

						if (patch != lastPatch) {
							sendProgramChange(1, (uint8_t)mapRange(min_p,max_p,0,127,patch));
							lastPatch = patch;
						}

					/*
					else if (h > 200) {
						LED_On(LED_GREEN);
						pitch = bhy_orientation.y;
						if (pitch != lastPitch) {
							sendPitchWheel(1,(uint8_t) mapRange(0,32768, 0,16383,pitch ));
							lastPitch = pitch;
						}
					}

					else if (h > 40) {
						LED_On(LED_BLUE);

						sendNoteOff(1,lastNote, 0);
						//note = ((int)p) % 127;
						note = (int) mapRange(41,360,0,127,p);
						if (note != lastNote ) {
							sendNoteOn(1,note,120);
							lastNote = note;
						}
					}
					*/

                }
                // Mode 2
                else if (midi_msg_mode == 1) {
                	// Send different notes based on accelerometer direction to different channels
                	LED_On(LED_BLUE);
                	sendMultiChannelNotes(0, (int) mapRange(0, 180,0,127,h));
                	sendMultiChannelNotes(1, (int) mapRange(0, 180,40,96,p));
                	sendMultiChannelNotes(9, (int) mapRange(0, 180,35,81,y));  // Drum map

                }


                // Mode 3
                else if (midi_msg_mode == 3) {
                	LED_On(LED_GREEN);
					pitch = bhy_orientation.y;
					if (pitch != lastPitch) {
						sendPitchWheel(1,(uint8_t) mapRange(0,32768, 0,16383,pitch ));
						lastPitch = pitch;
					}
                }

            }


        }

        SYS_WAIT_FOR_INTERRUPT;
    }

    return 0;
}

void PB_TransitionEvent(void *arg)
{
    ButtonName btn = (ButtonName)arg;
    int lastMode = midi_msg_mode;
    switch (btn)
    {
    case BTN0:

    	if ( BTN_Read(btn)) {
    		// Calibrate value
    		LED_On(LED_RED);
    		midi_msg_mode  = 0;

    	}else
    		LED_Off(LED_RED);
    		midi_msg_mode = 1;
        break;

    case BTN1:

        midi_msg_mode += 1;
        if (midi_msg_mode > 3) {
        	midi_msg_mode = 1;
        }

        // Flash LED color based on mode
        switch (midi_msg_mode)
        {
			case 0:
				break;
			case 1:
				LED_Toggle(LED_RED);
				break;
			case 2:
				LED_Toggle(LED_GREEN);
				break;
			case 3:
				LED_Toggle(LED_BLUE);
				break;
        }



        break;
    default:
        return;
    }

    if (midi_msg_mode != lastMode)
    	Save_Settings();

}


void BHY_OrientationCallback(bhy_data_generic_t *data, bhy_virtual_sensor_t sensor)
{
    memcpy(&bhy_orientation, &data->data_vector, sizeof(bhy_data_vector_t));
}

void DMIC_OUT_OD_IN_IRQHandler(void)
{
    dmic_value = (int32_t)AUDIO->DMIC0_DATA;

    if (dmic_max < dmic_value)
    {
        dmic_max = dmic_value;
    }
    else
    {
        if (dmic_min > dmic_value)
        {
            dmic_min = dmic_value;
        }
    }
}



// Saving Settings
#define EEPROM_I2C_ADDR       (0x50)
#define EEPROM_PAGE_SIZE      (4)

 /*  EEPROM Storage  */

int32_t eeprom_status = -1;
I2CEeprom eeprom;
uint8_t eeprom_ref[4];
const uint8_t eeprom_ref_length = sizeof(eeprom_ref);

void Save_Settings() {
	// Store Mode Settings
	int32_t retval = 0;

	retval = I2CEeprom_Initialize(EEPROM_I2C_ADDR, EEPROM_PAGE_SIZE, &eeprom);
	if (retval == I2C_EEPROM_OK)
	{
		memcpy(&midi_msg_mode,  eeprom_ref, 4);
		retval = I2CEeprom_Write(0, (uint8_t*)eeprom_ref, eeprom_ref_length, &eeprom);
		if (retval == I2C_EEPROM_OK)
		{
			return;
		}
	}
}

void Read_Settings() {
    int32_t retval = 0;

    retval = I2CEeprom_Initialize(EEPROM_I2C_ADDR, EEPROM_PAGE_SIZE, &eeprom);
	if (retval == I2C_EEPROM_OK)
	{
			uint8_t eeprom_readback[sizeof(eeprom_ref)];

			retval = I2CEeprom_Read(0, eeprom_readback, eeprom_ref_length, &eeprom);
			if (retval == I2C_EEPROM_OK)
			{
				midi_msg_mode = *eeprom_ref;
				return;
			}

	}
}

BLEMIDI.h

C Header File
Simple MIDI functions header
/* Copyright (c) 2014 mbed.org, MIT License
 *
 * 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 __BLEMIDI_H__
#define __BLEMIDI_H__

#ifdef __cplusplus
extern "C"
{
#endif    /* ifdef __cplusplus */

//#include "BLEDevice.h"
#include <BDK.h>
#include <BSP_Components.h>
#include <BLE_Components.h>

#include <ansi_color.h>
#include <SEGGER_RTT.h>

#include <stdio.h>
    uint8_t isConnected;

    uint16_t sysExBufferPos;
    uint8_t sysExBuffer[128];


    uint8_t midiEventKind;
    uint8_t midiEventNote;
    uint8_t midiEventVelocity;

    uint32_t timestamp;

    enum MIDI_STATE {
        MIDI_STATE_TIMESTAMP = 0,
        MIDI_STATE_WAIT,
        MIDI_STATE_SIGNAL_2BYTES_2,
        MIDI_STATE_SIGNAL_3BYTES_2,
        MIDI_STATE_SIGNAL_3BYTES_3,
        MIDI_STATE_SIGNAL_SYSEX
    };

    enum MIDI_STATE midiState;

    void (*onTuneRequest)();
    void (*onTimingClock)();
    void (*onStart)();
    void (*onContinue)();
    void (*onStop)();
    void (*onActiveSensing)();
    void (*onReset)();
    void (*onProgramChange)(uint8_t, uint8_t);
    void (*onChannelAftertouch)(uint8_t, uint8_t);
    void (*onTimeCodeQuarterFrame)(uint8_t);
    void (*onSongSelect)(uint8_t);
    void (*onNoteOff)(uint8_t, uint8_t, uint8_t);
    void (*onNoteOn)(uint8_t, uint8_t, uint8_t);
    void (*onPolyphonicAftertouch)(uint8_t, uint8_t, uint8_t);
    void (*onControlChange)(uint8_t, uint8_t, uint8_t);
    void (*onPitchWheel)(uint8_t, uint16_t);
    void (*onSongPositionPointer)(uint16_t);
    void (*onSystemExclusive)(uint8_t *, uint16_t, bool);

    void sendMidiMessage(uint8_t data0);
    void sendMidiMessage2(uint8_t data0, uint8_t data1);
    void sendMidiMessage3(uint8_t data0, uint8_t data1, uint8_t data2);
    void sendMidiControllerMessages(uint8_t channel, uint8_t cc1, uint8_t val1,
    		                                       uint8_t cc2 ,uint8_t val2,
    											   uint8_t cc3, uint8_t val3 );

    void BLE_CustomServiceSend(uint8_t *data, uint8_t data_len);


//    void dataWrittenCallback(const GattCharacteristicWriteCBParams *params);

    uint8_t midi[20];

//    BLEDevice *device;
//    GattCharacteristic *midiCharacteristic;
 //    uint16_t tick;

/**
 * A class to communicate a BLE MIDI device
 */

    /**
     * Constructor
     */
 //   BLEMIDI(BLEDevice *device);

    /**
     * Constructor with device name
     */
//    BLEMIDI(BLEDevice *dev, char *deviceName);

    /**
     * Check if a BLE MIDI device is connected
     *
     * @returns true if a midi device is connected
     */


//    bool connected();

    /**
     * Attach a callback called when the `Tune Request` event is received
     *
     * @param ptr function pointer
     *   prototype: void onTuneRequest();
     */
    inline void attachTuneRequest(void (*fn)()) {
        onTuneRequest = fn;
    }

    /**
     * Attach a callback called when the `Timing Clock` event is received
     *
     * @param ptr function pointer
     *   prototype: void onTimingClock();
     */
    inline void attachTimingClock(void (*fn)()) {
        onTimingClock = fn;
    }

    /**
     * Attach a callback called when the `Start` event is received
     *
     * @param ptr function pointer
     *   prototype: void onStart();
     */
    inline void attachStart(void (*fn)()) {
        onStart = fn;
    }

    /**
     * Attach a callback called when the `Continue` event is received
     *
     * @param ptr function pointer
     *   prototype: void onContinue();
     */
    inline void attachContinue(void (*fn)()) {
        onContinue = fn;
    }

    /**
     * Attach a callback called when the `Stop` event is received
     *
     * @param ptr function pointer
     *   prototype: void onStop();
     */
    inline void attachStop(void (*fn)()) {
        onStop = fn;
    }

    /**
     * Attach a callback called when the `Active Sensing` event is received
     *
     * @param ptr function pointer
     *   prototype: void onActiveSensing();
     */
    inline void attachActiveSensing(void (*fn)()) {
        onActiveSensing = fn;
    }

    /**
     * Attach a callback called when the `Reset` event is received
     *
     * @param ptr function pointer
     *   prototype: void onReset();
     */
    inline void attachReset(void (*fn)()) {
        onReset = fn;
    }

    /**
     * Attach a callback called when the `Program Change` event is received
     *
     * @param ptr function pointer
     *   prototype: void onProgramChange(uint8_t channel, uint8_t program);
     */
    inline void attachnProgramChange(void (*fn)(uint8_t, uint8_t)) {
        onProgramChange = fn;
    }

    /**
     * Attach a callback called when the `Channel Aftertouch` event is received
     *
     * @param ptr function pointer
     *   prototype: void onChannelAftertouch(uint8_t channel, uint8_t pressure);
     */
    inline void attachChannelAftertouch(void (*fn)(uint8_t, uint8_t)) {
        onChannelAftertouch = fn;
    }

    /**
     * Attach a callback called when the `Time Code Quarter Frame` event is received
     *
     * @param ptr function pointer
     *   prototype: void onTimeCodeQuarterFrame(uint8_t timing);
     */
    inline void attachTimeCodeQuarterFrame(void (*fn)(uint8_t)) {
        onTimeCodeQuarterFrame = fn;
    }

    /**
     * Attach a callback called when the `Song Select` event is received
     *
     * @param ptr function pointer
     *   prototype: void onSongSelect(uint8_t song);
     */
    inline void attachSongSelect(void (*fn)(uint8_t)) {
        onSongSelect = fn;
    }

    /**
     * Attach a callback called when the `Note Off` event is received
     *
     * @param ptr function pointer
     *   prototype: void onNoteOff(uint8_t channel, uint8_t note, uint8_t velocity);
     */
    inline void attachNoteOff(void (*fn)(uint8_t, uint8_t, uint8_t)) {
        onNoteOff = fn;
    }

    /**
     * Attach a callback called when the `Note On` event is received
     *
     * @param ptr function pointer
     *   prototype: void onNoteOn(uint8_t channel, uint8_t note, uint8_t velocity);
     */
    inline void attachNoteOn(void (*fn)(uint8_t, uint8_t, uint8_t)) {
        onNoteOn = fn;
    }

    /**
     * Attach a callback called when the `Polyphonic Aftertouch` event is received
     *
     * @param ptr function pointer
     *   prototype: void onPolyphonicAftertouch(uint8_t channel, uint8_t note, uint8_t pressure);
     */
    inline void attachPolyphonicAftertouch(void (*fn)(uint8_t, uint8_t, uint8_t)) {
        onPolyphonicAftertouch = fn;
    }

    /**
     * Attach a callback called when the `Control Change` event is received
     *
     * @param ptr function pointer
     *   prototype: void onControlChange(uint8_t channel, uint8_t function, uint8_t value);
     */
    inline void attachControlChange(void (*fn)(uint8_t, uint8_t, uint8_t)) {
        onControlChange = fn;
    }

    /**
     * Attach a callback called when the `Pitch Wheel` event is received
     *
     * @param ptr function pointer
     *   prototype: void onPitchWheel(uint8_t channel, uint16_t amount);
     */
    inline void attachPitchWheel(void (*fn)(uint8_t, uint16_t)) {
        onPitchWheel = fn;
    }

    /**
     * Attach a callback called when the `Song Position Pointer` event is received
     *
     * @param ptr function pointer
     *   prototype: void onSongPositionPointer(uint16_t position);
     */
    inline void attachSongPositionPointer(void (*fn)(uint16_t)) {
        onSongPositionPointer = fn;
    }

    /**
     * Attach a callback called when the `System Exclusive` event is received
     *
     * @param ptr function pointer
     *   prototype: void onSystemExclusive(uint8_t *sysex, uint16_t length, bool hasNextData);
     */
    inline void attachSystemExclusive(void (*fn)(uint8_t *, uint16_t, bool)) {
        onSystemExclusive = fn;
    }

    /**
     * Send a `Tune Request` event
     */
    void sendTuneRequest();

    /**
     * Send a `Timing Clock` event
     */
    void sendTimingClock();

    /**
     * Send a `Start` event
     */
    void sendStart();

    /**
     * Send a `Continue` event
     */
    void sendContinue();

    /**
     * Send a `Stop` event
     */
    void sendStop();

    /**
     * Send a `Active Sensing` event
     */
    void sendActiveSensing();

    /**
     * Send a `Reset` event
     */
    void sendReset();

    /**
     * Send a `Program Change` event
     *
     * @param channel 0-15
     * @param program 0-127
     */
    void sendProgramChange(uint8_t channel, uint8_t program);

    /**
     * Send a `Channel Aftertouch` event
     *
     * @param channel 0-15
     * @param pressure 0-127
     */
    void sendChannelAftertouch(uint8_t channel, uint8_t pressure);

    /**
     * Send a `Time Code Quarter Frame` event
     *
     * @param timing 0-127
     */
    void sendTimeCodeQuarterFrame(uint8_t timing);

    /**
     * Send a `Song Select` event
     *
     * @param song 0-127
     */
    void sendSongSelect(uint8_t song);

    /**
     * Send a `Note Off` event
     *
     * @param channel 0-15
     * @param note 0-127
     * @param velocity 0-127
     */
    void sendNoteOff(uint8_t channel, uint8_t note, uint8_t velocity);

    /**
     * Send a `Note On` event
     *
     * @param channel 0-15
     * @param note 0-127
     * @param velocity 0-127
     */
    void sendNoteOn(uint8_t channel, uint8_t note, uint8_t velocity);

    /**
     * Send a `Polyphonic Aftertouch` event
     *
     * @param channel 0-15
     * @param note 0-127
     * @param pressure 0-127
     */
    void sendPolyphonicAftertouch(uint8_t channel, uint8_t note, uint8_t pressure);

    /**
     * Send a `Control Change` event
     *
     * @param channel 0-15
     * @param function 0-127
     * @param value 0-127
     */
    void sendControlChange(uint8_t channel, uint8_t function, uint8_t value);

    /**
     * Send a `Pitch Wheel` event
     *
     * @param channel 0-15
     * @param amount 0-8192(center)-16383
     */
    void sendPitchWheel(uint8_t channel, uint16_t amount);

    /**
     * Send a `Song Position Pointer` event
     *
     * @param position 0-16383
     */
    void sendSongPositionPointer(uint16_t position);

    /**
     * Send a `System Exclusive` event
     *
     * @param sysex the data starts with `0xf0` and ends with `0xf7`
     * @param length
     */
    void sendSystemExclusive(uint8_t * sysex, uint16_t length);

    /**
     * Notifies BLE disconnection to this BLE MIDI instance
     */
//    void onBleDisconnection(Gap::Handle_t handle, Gap::DisconnectionReason_t reason);

    /**
     * Notifies BLE connection to this BLE MIDI instance
     */
//    void onBleConnection(Gap::Handle_t handle, Gap::addr_type_t type, const Gap::address_t addr, const Gap::ConnectionParams_t *params);

#ifdef __cplusplus
}
#endif    /* ifdef __cplusplus */
#endif    /* APP_H */

BLEMIDI.c

C/C++
Simple MIDI Message functions
/* Copyright (c) 2014 mbed.org, MIT License
 *
 * 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.
 */


#include "BLEMIDI.h"

void BLE_CustomServiceSend(uint8_t *data, uint8_t data_len)
	{
	//printf("data len %d ", &data_len);
	uint32_t stat =  BLE_ICS_Notify( data,  data_len);
	}


void sendMidiMessage(uint8_t data0) {
    if (isConnected) {
        uint8_t ticks=timestamp & 0x1fff;
        midi[0] = 0x80 | ((ticks >> 7) & 0x3f);
        midi[1] = 0x80 | (ticks & 0x7f);
        midi[2] = data0;

        BLE_CustomServiceSend( midi, 3);
    }
}

void sendMidiMessage2(uint8_t data0, uint8_t data1) {
    if (isConnected) {
    	uint8_t ticks=timestamp & 0x1fff;
        midi[0] = 0x80 | ((ticks >> 7) & 0x3f);
        midi[1] = 0x80 | (ticks & 0x7f);
        midi[2] = data0;
        midi[3] = data1;

        BLE_CustomServiceSend( midi, 4);

    }
}

void sendMidiMessage3(uint8_t data0, uint8_t data1, uint8_t data2) {
    if (isConnected) {
     	uint8_t ticks=timestamp & 0x1fff;
        midi[0] = 0x80 | ((ticks >> 7) & 0x3f);
        midi[1] = 0x80 | (ticks & 0x7f);
        midi[2] = data0;
        midi[3] = data1;
        midi[4] = data2;

        BLE_CustomServiceSend( midi, 5);
    }
}


void sendMidiControllerMessages(uint8_t channel, uint8_t cc1, uint8_t val1,
		                                       uint8_t cc2 ,uint8_t val2,
											   uint8_t cc3, uint8_t val3 ) {
	  if (isConnected) {
	     	uint8_t ticks=timestamp & 0x1fff;
	        midi[0] = 0x80 | ((ticks >> 7) & 0x3f);
	        midi[1] = 0x80 | (ticks & 0x7f);
	        midi[2] = 0xb0 | (channel & 0xf);
	        midi[3] = cc1;
	        midi[4] = val1;
	        midi[5] = cc2;
	   	    midi[6] = val2;
	   	    midi[7] = cc3;
		    midi[8] = val3;

		    BLE_CustomServiceSend( midi, 9);
	  }
}



/*
void sendHiresControllerMessages(uint8_t channel, uint16_t cc1, uint16_t cc2,
		                                       uint16_t cc3 ) {
	  if (isConnected) {
	     	uint8_t ticks=timestamp & 0x1fff;
	        midi[0] = 0x80 | ((ticks >> 7) & 0x3f);
	        midi[1] = 0x80 | (ticks & 0x7f);
	        midi[2] = 0xb0 | (channel & 0xf);
	        midi[3] = 16;
	        midi[4] = val1;
	        midi[5] = cc2;
	   	    midi[6] = val2;
	   	    midi[7] = cc3;
		    midi[8] = val3;
	        midi[9] = cc1;
	        midi[10] = val1;
	        midi[11] = cc2;
	   	    midi[12] = val2;
	   	    midi[13] = cc3;
		    midi[14] = val3;
		    BLE_CustomServiceSend( midi, 9);
	  }
}
*/

void sendTuneRequest() {
    sendMidiMessage(0xf6);
}
void sendTimingClock() {
    sendMidiMessage(0xf8);
}
void sendStart() {
    sendMidiMessage(0xfa);
}
void sendContinue() {
    sendMidiMessage(0xfb);
}
void sendStop() {
    sendMidiMessage(0xfc);
}
void sendActiveSensing() {
    sendMidiMessage(0xfe);
}
void sendReset() {
    sendMidiMessage(0xff);
}
void sendProgramChange(uint8_t channel, uint8_t program) {
    sendMidiMessage2(0xc0 | (channel & 0xf), program);
}
void sendChannelAftertouch(uint8_t channel, uint8_t pressure) {
    sendMidiMessage2(0xd0 | (channel & 0xf), pressure);
}
void sendTimeCodeQuarterFrame(uint8_t timing) {
    sendMidiMessage2(0xf1, timing & 0x7f);
}
void sendSongSelect(uint8_t song) {
    sendMidiMessage2(0xf3, song & 0x7f);
}
void sendNoteOff(uint8_t channel, uint8_t note, uint8_t velocity) {
    sendMidiMessage3(0x80 | (channel & 0xf), note, velocity);
}
void sendNoteOn(uint8_t channel, uint8_t note, uint8_t velocity) {
    sendMidiMessage3(0x90 | (channel & 0xf), note, velocity);
}
void sendPolyphonicAftertouch(uint8_t channel, uint8_t note, uint8_t pressure) {
    sendMidiMessage3(0xa0 | (channel & 0xf), note, pressure);
}
void sendControlChange(uint8_t channel, uint8_t function, uint8_t value) {
    sendMidiMessage3(0xb0 | (channel & 0xf), function, value);
}
void sendPitchWheel(uint8_t channel, uint16_t amount) {
    sendMidiMessage3(0xe0 | (channel & 0xf), amount & 0x7f, (amount >> 7) & 0x7f);
}
void sendSongPositionPointer(uint16_t position) {
    sendMidiMessage3(0xf2, position & 0x7f, (position >> 7) & 0x7f);
}

BLE_ICS.h

C Header File
Modified Header file for bluetooth containing Service ID for Bletooth Over Low Energy
//-----------------------------------------------------------------------------
// Copyright (c) 2018 Semiconductor Components Industries LLC
// (d/b/a "ON Semiconductor").  All rights reserved.
// This software and/or documentation is licensed by ON Semiconductor under
// limited terms and conditions.  The terms and conditions pertaining to the
// software and/or documentation are available at
// http://www.onsemi.com/site/pdf/ONSEMI_T&C.pdf ("ON Semiconductor Standard
// Terms and Conditions of Sale, Section 8 Software") and if applicable the
// software license agreement.  Do not use this software and/or documentation
// unless you have carefully read and you agree to the limited terms and
// conditions.  By using this software and/or documentation, you agree to the
// limited terms and conditions.
//-----------------------------------------------------------------------------
//! \file BLE_ICS.h
//! \version v1.0.0
//!
//! \addtogroup BDK_GRP
//! \{
//! \addtogroup BLE_GRP
//! \{
//! \addtogroup ICS IoT IDK Custom Service Profile
//!
//! \brief BLE profile for bidirectional communication between BDK and client
//! device.
//!
//! Two characteristics are provided to act as RX line for receiving requests
//! from client device and TX line for providing responses.<br>
//! Size of message that can be send and received over these characteristics is
//! limited to 20 bytes.<br>
//! Transmitted data are application specific and can be in binary form or as
//! readable AT commands.
//!
//! \warning Custom Service Profile uses message handlers registered under
//! application task (TASK_APP) to communicate with GATTM and GATTC tasks which
//! manage attribute database and active connections.
//! Therefore there cannot be second user defined profile handled by
//! application task at the same time as they would collide.<br>
//! This concerns only profiles that are not directly supported by RSL10 BLE
//! stack which have assigned their own tasks by event kernel.<br>
//! Following message handlers will be added to application task:
//!     * GATTM_ADD_SVC_RSP
//!     * GATTC_READ_REQ_IND
//!     * GATTC_WRITE_REQ_IND
//!     * GATTC_ATT_INFO_REQ_IND
//!     * GATTC_CMP_EVT
//!
//! \b Example: \n
//! Minimal code example which uses Custom Service.
//! It echoes all data written to RX characteristic back to client by sending
//! notifications over TX characteristic with the same  data.
//! \include ics_example.c
//! \{
//-----------------------------------------------------------------------------

#ifndef BLE_ICS_H
#define BLE_ICS_H

#include "BLE_PeripheralServer.h"

#ifdef __cplusplus
extern "C"
{
#endif


/** \brief IoT IDK Service UUID */
/** MIDI spec UUID */
#define ICS_SERVICE_UUID                { 0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, \
                                          0x51, 0xa7, 0x33, 0x4b, 0xe8, 0xed, \
                                          0x5a, 0x0e, 0xb8, 0x03 }
#define ICS_TX_CHARACTERISTIC_UUID       { 0xf3, 0x6b, 0x10, 0x9d, 0x66, 0xf2, \
                                          0xa9, 0xa1, 0x12, 0x41, 0x68, 0x38, \
                                          0xdb, 0xe5, 0x72, 0x77 }


/** \brief IoT IDK Service RX Characteristic UUID */
#define ICS_RX_CHARACTERISTIC_UUID      { 0x24, 0xdc, 0x0e, 0x6e, 0x03, 0x40, \
                                          0xca, 0x9e, 0xe5, 0xa9, 0xa3, 0x00, \
                                          0xb7, 0xf3, 0x93, 0xe0 }

/** \brief Human readable TX characteristic description.
 *
 * Can be read from <i>Characteristic User Description</i> of TX
 * characteristic.
 */
#define ICS_TX_CHARACTERISTIC_NAME      "TX_VALUE - Response from BDK"

/** \brief Human readable RX characteristic description.
 *
 * Can be read from <i>Characteristic User Description</i> of RX
 * characteristic.
 */
#define ICS_RX_CHARACTERISTIC_NAME      "RX_VALUE - Command for BDK"

#define ICS_TX_CHARACTERISTIC_NAME_LEN  (sizeof(ICS_TX_CHARACTERISTIC_NAME) - 1)

#define ICS_RX_CHARACTERISTIC_NAME_LEN  (sizeof(ICS_RX_CHARACTERISTIC_NAME) - 1)

/** \brief Maximum amount of data that can be either received from RX
 * characteristic or send over TX characteristic.
 *
 * This is limited to 20 characters to accommodate for maximal allowed
 * notification length.
 */
#define ICS_CHARACTERISTIC_VALUE_LENGTH (20)

/** \brief Attribute database indexes of ICS characteristics. */
enum BLE_ICS_AttributeIndex
{
    /* TX Characteristic */
    ICS_IDX_TX_VALUE_CHAR,
    ICS_IDX_TX_VALUE_VAL,
    ICS_IDX_TX_VALUE_CCC,
    ICS_IDX_TX_VALUE_USR_DSCP,

    /* RX Characteristic
    ICS_IDX_RX_VALUE_CHAR,
    ICS_IDX_RX_VALUE_VAL,
    ICS_IDX_RX_VALUE_CCC,
    ICS_IDX_RX_VALUE_USR_DSCP,

    /* Max number of characteristics */
    ICS_IDX_NB,
};

/** \brief Initialization state of ICS library. */
enum BLE_ICS_State
{
    BLE_ICS_OFF = 0, /**< Initial state after device power up. */
    BLE_ICS_CREATE_DB, /**< Waiting for attribute database creation confirmation. */
    BLE_ICS_READY, /**< Waiting for client device to connect.  */
    BLE_ICS_CONNECTED /**< Client device connected. Notifications can be send. */
};

/** \brief Data structure passed to application specific Write Indication
 * callback handler.
 */
struct BLE_ICS_RxIndData
{
    /** \brief Stores data received in last write to RX characteristic. */
    uint8_t data[ICS_CHARACTERISTIC_VALUE_LENGTH];

    /** \brief Number of valid data bytes in \ref data array. */
    uint8_t data_len;
};

/** \brief Callback type for handling of RX Write indication events. */
typedef void (*BLE_ICS_RxIndHandler)(struct BLE_ICS_RxIndData *ind);

/** \brief Stores internal state ICS Profile. */
struct BLE_ICS_Resources
{
    /** \brief Stores current initialization and connection state. */
    uint8_t state;

    /** \brief Index of first ICS characteristic in attribute database. */
    uint16_t start_hdl;

    /** \brief Application specific handler for RX characteristic write
     * indication events.
     */
    BLE_ICS_RxIndHandler rx_write_handler;

    uint8_t tx_value[ICS_CHARACTERISTIC_VALUE_LENGTH];
    uint8_t tx_value_length;
    uint16_t tx_cccd_value;

    uint8_t rx_value[ICS_CHARACTERISTIC_VALUE_LENGTH];
    uint8_t rx_value_length;
    uint16_t rx_cccd_value;
};

/** \brief Adds IoT IDK Custom Service into BDK BLE stack.
 *
 * Calling this function will add Custom Service to BLE attribute database.
 *
 * This function does the following initialization steps:
 *     1. Initialize BDK BLE stack by calling BDK_BLE_Initalize.
 *     2. Add message handler callbacks to application task.<br>
 *        These handlers are needed to communicate with GATTM and GATTC tasks.
 *     3. Adds service initialization & enable callbacks that will be executed
 *        during attribute database generation.
 *
 * \pre This functions must be called before Application task is started and
 * before Event Kernel messaging is started, e.g. before entering main loop
 * and calling \ref BDK_Schedule .
 *
 * \param rx_ind_handler
 */
extern void BLE_ICS_Initialize(BLE_ICS_RxIndHandler rx_ind_handler);

/** \brief Send out a notification over TX characteristic.
 *
 * TX characteristic will be updated with new data and connected device will
 * receive notification of this change.
 *
 * \param data
 * Data that will be sent in notification packet and stored in TX
 * characteristic.
 *
 * \param data_len
 * Length of given data.
 *
 * \returns Operation status code.
 * | Code | Description                                              |
 * | ---- | -------------------------------------------------------- |
 * | 0    | On success.                                              |
 * | 1    | If there is no BLE client device connected.              |
 * | 2    | If length of data is bigger than allowed maximum length. |
 *
 */
extern uint32_t BLE_ICS_Notify(uint8_t *data, uint8_t data_len);

#ifdef __cplusplus
}
#endif

#endif /* BLE_ICS_H */

//! \}
//! \}
//! \}

Credits

Leon Chu

Leon Chu

11 projects • 10 followers
Indie Developer / Artist / Lifelong learner

Comments