Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIDI over BLE #510

Open
chegewara opened this issue Apr 30, 2018 · 12 comments

Comments

Projects
None yet
4 participants
@chegewara
Copy link
Collaborator

commented Apr 30, 2018

/*
Based on:
    BLE_MIDI Example by neilbags 
    https://github.com/neilbags/arduino-esp32-BLE-MIDI
    
    Based on BLE_notify example by Evandro Copercini.

    Creates a BLE MIDI service and characteristic.
    Once a client subscibes, send a MIDI message every 2 seconds
*/

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

uint8_t note = 64;

BLECharacteristic *pCharacteristic;
bool deviceConnected = false;

#define MIDI_SERVICE_UUID        "03b80e5a-ede8-4b33-a751-6ce34ec4c700"
#define MIDI_CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3"

uint8_t midiPacket[] = {
   0x80,  // header
   0x80,  // timestamp, not implemented 
   0x00,  // status
   0x3c,  // 0x3c == 60 == middle c
   0x00   // velocity
};

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

void setup() {
  Serial.begin(115200);

  BLEDevice::init("MIDI");

  // Create the BLE Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  BLEDevice::setEncryptionLevel((esp_ble_sec_act_t)ESP_LE_AUTH_REQ_SC_BOND);

  // Create the BLE Service
  BLEService *pService = pServer->createService(BLEUUID(MIDI_SERVICE_UUID));
  
  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      BLEUUID(MIDI_CHARACTERISTIC_UUID),
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_WRITE_NR
                    );
  pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising

    BLESecurity *pSecurity = new BLESecurity();
    pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
    pSecurity->setCapability(ESP_IO_CAP_NONE);
    pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

  pServer->getAdvertising()->addServiceUUID(MIDI_SERVICE_UUID);
  pServer->getAdvertising()->start();

}

void loop() {
  if (deviceConnected) {

note = note + random(-5, 5);
   // note down
   midiPacket[2] = 0x90; // note down, channel 0
   midiPacket[3] = note;
   midiPacket[4] = 127;  // velocity
   pCharacteristic->setValue(midiPacket, 5); // packet, length in bytes
   pCharacteristic->notify();
   // play note for 500ms
   delay(500);

   // note up
   midiPacket[2] = 0x80; // note up, channel 0
   midiPacket[3] = note;
   midiPacket[4] = 0;    // velocity
   pCharacteristic->setValue(midiPacket, 5); // packet, length in bytes)
   pCharacteristic->notify();

   delay(500);
  }
}
@chegewara

This comment has been minimized.

Copy link
Collaborator Author

commented May 1, 2018

With random note on channel 10 and about 300 BPM there is nice percusion melody:

/**
 * Create a new BLE server.
 */
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"
#include "BLE2902.h"
#include <esp_log.h>
#include <string>
#include <Task.h>

#include "sdkconfig.h"

static char LOG_TAG[] = "MIDIDemo";
BLECharacteristic* pCharacteristic;
uint8_t note = 48;
uint8_t midiPacket[] = {
   0x80,  // header
   0x80,  // timestamp, not implemented 
   0x00,  // status
   0x3c,  // 0x3c == 60 == middle c
   0x00   // velocity
};

class MyTask : public Task {
    void run(void*) {
        while(1){
         note = esp_random() % 13 + 48;
            // note down
            midiPacket[2] = 0x99; // note down, channel 0
            midiPacket[3] = note;
            midiPacket[4] = 127;  // velocity
            pCharacteristic->setValue(midiPacket, 5); // packet, length in bytes
            pCharacteristic->notify();
            vTaskDelay(100/portTICK_PERIOD_MS);

            // note up
            midiPacket[2] = 0x89; // note up, channel 0
            midiPacket[3] = note;
            midiPacket[4] = 127;    // velocity
            pCharacteristic->setValue(midiPacket, 5); // packet, length in bytes)
            pCharacteristic->notify();
            vTaskDelay(100/portTICK_PERIOD_MS);
        }
    }
};

MyTask *task;
class MyCallbacks : public BLEServerCallbacks {
	void onConnect(BLEServer* pServer){
        task->start();
	}

	void onDisconnect(BLEServer* pServer){
        task->stop();
	}
};

class MyCharacteristicCallback : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* pChar){
		uint8_t value[5] = {0};
        memcpy(value, pChar->getValue().c_str(), 5);
		ESP_LOGW(LOG_TAG, "command: %d, note: %d, data: %d, %d, %d", value[2], value[3], value[0], value[1], value[4]);
    }
};

class MainBLEServer: public Task {
	void run(void *data) {
		ESP_LOGD(LOG_TAG, "Starting BLE work!");

		task = new MyTask();
		BLEDevice::init("ESP32");
		BLEServer *pServer = BLEDevice::createServer();
		pServer->setCallbacks(new MyCallbacks());

        BLEService* pService = pServer->createService("03b80e5a-ede8-4b33-a751-6ce34ec4c700");
        pCharacteristic = pService->createCharacteristic("7772e5db-3868-4112-a1a9-f2669d106bf3", 
                    BLECharacteristic::PROPERTY_READ   |
                    BLECharacteristic::PROPERTY_NOTIFY |
                    BLECharacteristic::PROPERTY_WRITE_NR
        );

        pCharacteristic->setCallbacks(new MyCharacteristicCallback());

        pCharacteristic->addDescriptor(new BLE2902());
        pService->start();


		BLEAdvertising *pAdvertising = pServer->getAdvertising();
		pAdvertising->addServiceUUID(pService->getUUID());
		pAdvertising->start();


		BLESecurity *pSecurity = new BLESecurity();
		pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);

		ESP_LOGD(LOG_TAG, "Advertising started!");
		delay(portMAX_DELAY);
	}
};


extern "C" void app_main(void)
{
	//esp_log_level_set("*", ESP_LOG_DEBUG);
	MainBLEServer* pMainBleServer = new MainBLEServer();
	pMainBleServer->setStackSize(8000);
	pMainBleServer->start();

} // app_main
@danover

This comment has been minimized.

Copy link

commented May 1, 2018

Looks fun! What task library are you using in the drum example above?

@chegewara

This comment has been minimized.

Copy link
Collaborator Author

commented May 1, 2018

What task library?

@johnty

This comment has been minimized.

Copy link

commented May 1, 2018

i would guess its the task library from RTOS

@chegewara

This comment has been minimized.

Copy link
Collaborator Author

commented May 1, 2018

@johnty

This comment has been minimized.

Copy link

commented May 1, 2018

ah, apologies. (should have realized its Task.h and not task.h ;) )

@chegewara

This comment has been minimized.

Copy link
Collaborator Author

commented May 2, 2018

@johnty

This comment has been minimized.

Copy link

commented May 8, 2018

Thanks! I can confirm this example works fine, and using the fluidsynth example I was able to get the same results with the random drum notes triggered on an Android device (Nexus 5). :)

What is curious is that it seems like security is not mandatory (but from what I can tell it is "recommended"). However, the name of the device MUST be less than or equal to 5 characters in length! e.g. "ESP32" works. "MIDI" works. but for example if you set the name to "MIDItest", you cannot discover it any more! @chegewara can you confirm this behaviour?

On iOS, Android, and MacOS it seems to exhibit this behaviour, while in Windows 10 it is happy to find the device with a longer name. On iOS and OSX you can still pair it with an external BLE utility however, and once paired, you can use the MIDI port; on Android, I'm able to pair it using the NRF utility but afterwards it doesn't look like any MIDI apps can see/use the port...).

I wonder if this 5 character limit is inherent in the spec, or something else?

@chegewara

This comment has been minimized.

Copy link
Collaborator Author

commented May 8, 2018

Yes, i didnt think about it earlier but yes, i can confirm that. There is few issues here about this. In short: we have 31 bytes for advertising and midi service is 128 bit (16 bytes) long. With all other parameters that are advertised we have 5 bytes for device name. To change it some cpp_utils library changes are required.

EDIT you can try to comment out pAdvertising->addService(service_uuid) but this can cause this behaviour:

On iOS, Android, and MacOS it seems to exhibit this behaviour, while in Windows 10 it is happy to find the device with a longer name. On iOS and OSX you can still pair it with an external BLE utility however, and once paired, you can use the MIDI port; on Android, I'm able to pair it using the NRF utility but afterwards it doesn't look like any MIDI apps can see/use the port...).

@johnty

This comment has been minimized.

Copy link

commented May 8, 2018

Good to know, thanks!

This sounds like a limit of MIDI-BLE then? I assume the 31 bytes for advertising is fixed by the BLE spec? and you need to send out the MIDI service advertising otherwise you get discovery issues mentioned above... From here, I guess the question is: Is there any unnecessary data sent with the advertising packet that we could potentially trim? Either way, its not a critical issue, just want to get a better understanding of whats going on under the hood!

Thanks again!

@chegewara

This comment has been minimized.

Copy link
Collaborator Author

commented May 8, 2018

You are right, its bluetooth le limitation.
I dont remember now from top of my head but i think there is TX power (2 bytes) and thats all or maybe 1 more data advertised.

@MagicCube

This comment has been minimized.

Copy link

commented Sep 2, 2018

THANK GOD! FINALLY I REALIZED IT'S A NAMING PROBLEM!! THIS BOTHERED ME A LOT IN 2 DAYS!!
THANK YOU SO MUCH, GUYS!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.