nkolban / esp32-snippets

Sample ESP32 snippets and code fragments
https://leanpub.com/kolban-ESP32
Apache License 2.0
2.34k stars 712 forks source link

MIDI over BLE #510

Open chegewara opened 6 years ago

chegewara commented 6 years ago
/*
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 commented 6 years ago

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 commented 6 years ago

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

chegewara commented 6 years ago

What task library?

johnty commented 6 years ago

i would guess its the task library from RTOS

chegewara commented 6 years ago

https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/Task.cpp

johnty commented 6 years ago

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

chegewara commented 6 years ago

@johnty https://www.youtube.com/watch?v=ZslbdgeX3iw

johnty commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

guumaster commented 4 years ago

Just a quick tip for copy&pasters like me that reach this snippets. The code works, but if you use Platform.IO, remember to add #include <Arduino.h> at the top.