from feat/2.0.0
This commit is contained in:
parent
35c5aab271
commit
b22cc18fc0
|
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
examples/.DS_Store
|
||||||
|
src/.DS_Store
|
||||||
|
test/.vs
|
||||||
|
test/Debug
|
||||||
|
examples/ESP32_NoteOnOffEverySec/config.h
|
||||||
|
src/.vscode
|
||||||
|
test/x64
|
||||||
|
.development
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
examples/.DS_Store
|
examples/.DS_Store
|
||||||
src/.DS_Store
|
src/.DS_Store
|
||||||
|
.vscode/settings.json
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
|
|
||||||
41
README.md
41
README.md
|
|
@ -1,9 +1,38 @@
|
||||||
# Arduino-BLE-MIDI (DEPRECATED, use branch feat/2.0.0)
|
# Experimental
|
||||||
MIDI over Bluetooth Low Energy (BLE-MIDI) 1.0 for Arduino
|
|
||||||
|
|
||||||
Call interface similar to [FortySevenEffects/MIDI](https://github.com/FortySevenEffects/arduino_midi_library) and [lathoub/AppleMIDI](https://github.com/lathoub/Arduino-AppleMIDI-Library)
|
# Arduino BLE-MIDI Transport
|
||||||
|
This library implements the BLE-MIDI transport layer for the [FortySevenEffects Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library)
|
||||||
|
|
||||||
Inspired by Pedalino https://github.com/alf45tar/Pedalino by alf45tar
|
## Installation
|
||||||
|
This library depends on the [Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library).
|
||||||
|
|
||||||
# Supported devices
|
When installing this library from the Arduino IDE, the dependency be downloaded and installed in the same directory as this library. (Thanks to the `depends` clause in `library.properties`)
|
||||||
ESP32
|
|
||||||
|
When manually installing this library, you have to manually download [Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library) from github and install it in the same directory as this library - without this additional install, this library will not be able to compile.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
### Basic / Default
|
||||||
|
```cpp
|
||||||
|
#include <BLE-MIDI.h>
|
||||||
|
#include <hardware/BLE-MIDI_ESP32.h>
|
||||||
|
...
|
||||||
|
BLEMIDI_CREATE_DEFAULT_ESP32_INSTANCE();
|
||||||
|
...
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
MIDI.begin(1);
|
||||||
|
...
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
MIDI.read();
|
||||||
|
```
|
||||||
|
will create a instance named `bleMIDI` and listens to incoming MIDI on channel 1.
|
||||||
|
|
||||||
|
## Tested boards/modules
|
||||||
|
- ESP32
|
||||||
|
|
||||||
|
## Other Transport protocols:
|
||||||
|
The libraries below the same calling mechanism (API), making it easy to interchange the transport layer.
|
||||||
|
- [Arduino AppleMIDI Transport](https://github.com/lathoub/Arduino-AppleMIDI-Library)
|
||||||
|
- [Arduino USB-MIDI Transport](https://github.com/lathoub/USB-MIDI)
|
||||||
|
- [Arduino ipMIDI Transport](https://github.com/lathoub/Arduino-ipMIDI)
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#include "BleMidi.h"
|
|
||||||
|
|
||||||
BLEMIDI_CREATE_INSTANCE(bm);
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
// Serial communications and wait for port to open:
|
|
||||||
Serial.begin(115200);
|
|
||||||
while (!Serial) {
|
|
||||||
; // wait for serial port to connect. Needed for Leonardo only
|
|
||||||
}
|
|
||||||
|
|
||||||
bm.begin("hehe");
|
|
||||||
|
|
||||||
bm.onConnected(OnBleMidiConnected);
|
|
||||||
bm.onDisconnected(OnBleMidiDisconnected);
|
|
||||||
|
|
||||||
bm.setHandleNoteOn(OnBleMidiNoteOn);
|
|
||||||
bm.setHandleNoteOff(OnBleMidiNoteOff);
|
|
||||||
|
|
||||||
Serial.println(F("looping"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
bm.sendNoteOn(60, 127, 1); // note 60, velocity 127 on channel 1
|
|
||||||
bm.sendNoteOff(60, 127, 1);
|
|
||||||
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====================================================================================
|
|
||||||
// Event handlers for incoming MIDI messages
|
|
||||||
// ====================================================================================
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// rtpMIDI session. Device connected
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
void OnBleMidiConnected() {
|
|
||||||
Serial.println(F("Connected"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// rtpMIDI session. Device disconnected
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
void OnBleMidiDisconnected() {
|
|
||||||
Serial.println(F("Disconnected"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// received note on
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
void OnBleMidiNoteOn(byte channel, byte note, byte velocity) {
|
|
||||||
Serial.print(F("Incoming NoteOn from channel:"));
|
|
||||||
Serial.print(channel);
|
|
||||||
Serial.print(F(" note:"));
|
|
||||||
Serial.print(note);
|
|
||||||
Serial.print(F(" velocity:"));
|
|
||||||
Serial.print(velocity);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// received note off
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
void OnBleMidiNoteOff(byte channel, byte note, byte velocity) {
|
|
||||||
Serial.print(F("Incoming NoteOff from channel:"));
|
|
||||||
Serial.print(channel);
|
|
||||||
Serial.print(F(" note:"));
|
|
||||||
Serial.print(note);
|
|
||||||
Serial.print(F(" velocity:"));
|
|
||||||
Serial.print(velocity);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
#include <BLE-MIDI.h>
|
||||||
|
|
||||||
|
#include <hardware/BLEMIDI_ESP32_NimBLE.h>
|
||||||
|
//#include <hardware/BLEMIDI_ESP32.h>
|
||||||
|
//#include <hardware/BLEMIDI_nRF52.h>
|
||||||
|
//#include <hardware/BLEMIDI_ArduinoBLE.h>
|
||||||
|
|
||||||
|
BLEMIDI_CREATE_DEFAULT_INSTANCE()
|
||||||
|
|
||||||
|
unsigned long t0 = millis();
|
||||||
|
bool isConnected = false;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
MIDI.begin();
|
||||||
|
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
digitalWrite(LED_BUILTIN, LOW);
|
||||||
|
|
||||||
|
BLEMIDI.setHandleConnected(OnConnected);
|
||||||
|
BLEMIDI.setHandleDisconnected(OnDisconnected);
|
||||||
|
|
||||||
|
MIDI.setHandleNoteOn(OnNoteOn);
|
||||||
|
MIDI.setHandleNoteOff(OnNoteOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
MIDI.read();
|
||||||
|
|
||||||
|
if (isConnected && (millis() - t0) > 1000)
|
||||||
|
{
|
||||||
|
t0 = millis();
|
||||||
|
|
||||||
|
MIDI.sendNoteOn (60, 100, 1); // note 60, velocity 127 on channel 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================================================================
|
||||||
|
// Event handlers for incoming MIDI messages
|
||||||
|
// ====================================================================================
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Device connected
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void OnConnected() {
|
||||||
|
isConnected = true;
|
||||||
|
digitalWrite(LED_BUILTIN, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Device disconnected
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void OnDisconnected() {
|
||||||
|
isConnected = false;
|
||||||
|
digitalWrite(LED_BUILTIN, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Received note on
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void OnNoteOn(byte channel, byte note, byte velocity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Received note off
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void OnNoteOff(byte channel, byte note, byte velocity) {
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
#######################################
|
#######################################
|
||||||
# Syntax Coloring Map for AppleMIDI
|
# Syntax Coloring Map for BLEMIDI
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Datatypes (KEYWORD1)
|
# Datatypes (KEYWORD1)
|
||||||
#######################################
|
#######################################
|
||||||
BLEMIDI KEYWORD1
|
BLE-MIDI.h KEYWORD1
|
||||||
BLEMIDI.h KEYWORD1
|
BLEMIDI KEYWORD1
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Methods and Functions (KEYWORD2)
|
# Methods and Functions (KEYWORD2)
|
||||||
|
|
@ -40,10 +40,11 @@ tick KEYWORD2
|
||||||
#######################################
|
#######################################
|
||||||
# Instances (KEYWORD3)
|
# Instances (KEYWORD3)
|
||||||
#######################################
|
#######################################
|
||||||
|
BLEMIDI KEYWORD3
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Constants (LITERAL1)
|
# Constants (LITERAL1)
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
# Namespace, considering it as a literal
|
# Namespace, considering it as a literal
|
||||||
blemidi LITERAL1
|
BLEMIDI LITERAL1
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
name=BLE-MIDI
|
name=BLE-MIDI
|
||||||
version=0.0.1
|
version=2.1.0
|
||||||
author=lathoub
|
author=lathoub
|
||||||
maintainer=lathoub <lathoub@gmail.com>
|
maintainer=lathoub <lathoub@gmail.com>
|
||||||
sentence=BLE-MIDI I/Os for Arduino
|
sentence=BLE-MIDI I/Os for Arduino
|
||||||
paragraph=MIDI over Bluetooth Low Energy
|
paragraph=MIDI over Bluetooth Low Energy (BLE-MIDI) 1.0 for Arduino
|
||||||
category=Communication
|
category=Communication
|
||||||
url=https://github.com/lathoub/Arduino-BLE-MIDI
|
url=https://github.com/lathoub/Arduino-BLE-MIDI
|
||||||
architectures=*
|
architectures=esp32,samd,megaavr,mbed,nrf52
|
||||||
includes=BLEMIDI.h
|
includes=BLE-MIDI.h
|
||||||
|
depends=MIDI Library, NimBLE-Arduino, ArduinoBLE
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#include "BLE-MIDI.h"
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
/*!
|
||||||
|
* @file BLEMIDI.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MIDI.h>
|
||||||
|
using namespace MIDI_NAMESPACE;
|
||||||
|
|
||||||
|
#include "BLE-MIDI_Settings.h"
|
||||||
|
#include "BLE-MIDI_Defs.h"
|
||||||
|
#include "BLE-MIDI_Namespace.h"
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
template<class T, class _Settings = DefaultSettings>
|
||||||
|
class BLEMIDITransport
|
||||||
|
{
|
||||||
|
typedef _Settings Settings;
|
||||||
|
|
||||||
|
private:
|
||||||
|
byte mRxBuffer[Settings::MaxBufferSize];
|
||||||
|
unsigned mRxIndex = 0;
|
||||||
|
|
||||||
|
byte mTxBuffer[Settings::MaxBufferSize];
|
||||||
|
unsigned mTxIndex = 0;
|
||||||
|
|
||||||
|
char mDeviceName[24];
|
||||||
|
|
||||||
|
private:
|
||||||
|
T mBleClass;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEMIDITransport(const char* deviceName)
|
||||||
|
{
|
||||||
|
strncpy(mDeviceName, deviceName, 24);
|
||||||
|
|
||||||
|
mRxIndex = 0;
|
||||||
|
mTxIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const bool thruActivated = false;
|
||||||
|
|
||||||
|
void begin()
|
||||||
|
{
|
||||||
|
mBleClass.begin(mDeviceName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool beginTransmission(MidiType)
|
||||||
|
{
|
||||||
|
getMidiTimestamp(&mTxBuffer[0], &mTxBuffer[1]);
|
||||||
|
mTxIndex = 2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(byte inData)
|
||||||
|
{
|
||||||
|
// check for size! SysEx!!!
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
// should only happen from SysEx!
|
||||||
|
// if we approach the end of the buffer, chop-up in segments until
|
||||||
|
// we reach F7 (end of SysEx)
|
||||||
|
}
|
||||||
|
|
||||||
|
mTxBuffer[mTxIndex++] = inData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void endTransmission()
|
||||||
|
{
|
||||||
|
mBleClass.write(mTxBuffer, mTxIndex);
|
||||||
|
mTxIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte read()
|
||||||
|
{
|
||||||
|
return mRxBuffer[--mRxIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned available()
|
||||||
|
{
|
||||||
|
uint8_t byte;
|
||||||
|
auto success = mBleClass.available(&byte);
|
||||||
|
if (!success) return mRxIndex;
|
||||||
|
|
||||||
|
mRxBuffer[mRxIndex++] = byte;
|
||||||
|
|
||||||
|
return mRxIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*
|
||||||
|
The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages.
|
||||||
|
|
||||||
|
Header Byte
|
||||||
|
bit 7 Set to 1.
|
||||||
|
bit 6 Set to 0. (Reserved for future use)
|
||||||
|
bits 5-0 timestampHigh:Most significant 6 bits of timestamp information.
|
||||||
|
The header byte contains the topmost 6 bits of timing information for MIDI events in the BLE
|
||||||
|
packet. The remaining 7 bits of timing information for individual MIDI messages encoded in a
|
||||||
|
packet is expressed by timestamp bytes.
|
||||||
|
|
||||||
|
Timestamp Byte
|
||||||
|
bit 7 Set to 1.
|
||||||
|
bits 6-0 timestampLow: Least Significant 7 bits of timestamp information.
|
||||||
|
|
||||||
|
The 13-bit timestamp for the first MIDI message in a packet is calculated using 6 bits from the
|
||||||
|
header byte and 7 bits from the timestamp byte.
|
||||||
|
|
||||||
|
Timestamps are 13-bit values in milliseconds, and therefore the maximum value is 8,191 ms.
|
||||||
|
Timestamps must be issued by the sender in a monotonically increasing fashion.
|
||||||
|
timestampHigh is initially set using the lower 6 bits from the header byte while the timestampLow is
|
||||||
|
formed of the lower 7 bits from the timestamp byte. Should the timestamp value of a subsequent
|
||||||
|
MIDI message in the same packet overflow/wrap (i.e., the timestampLow is smaller than a
|
||||||
|
preceding timestampLow), the receiver is responsible for tracking this by incrementing the
|
||||||
|
timestampHigh by one (the incremented value is not transmitted, only understood as a result of the
|
||||||
|
overflow condition).
|
||||||
|
|
||||||
|
In practice, the time difference between MIDI messages in the same BLE packet should not span
|
||||||
|
more than twice the connection interval. As a result, a maximum of one overflow/wrap may occur
|
||||||
|
per BLE packet.
|
||||||
|
|
||||||
|
Timestamps are in the sender’s clock domain and are not allowed to be scheduled in the future.
|
||||||
|
Correlation between the receiver’s clock and the received timestamps must be performed to
|
||||||
|
ensure accurate rendering of MIDI messages, and is not addressed in this document.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculating a Timestamp
|
||||||
|
To calculate the timestamp, the built-in millis() is used.
|
||||||
|
The BLE standard only specifies 13 bits worth of millisecond data though,
|
||||||
|
so it’s bitwise anded with 0x1FFF for an ever repeating cycle of 13 bits.
|
||||||
|
This is done right after a MIDI message is detected. It’s split into a 6 upper bits, 7 lower bits,
|
||||||
|
and the MSB of both bytes are set to indicate that this is a header byte.
|
||||||
|
Both bytes are placed into the first two position of an array in preparation for a MIDI message.
|
||||||
|
*/
|
||||||
|
static void getMidiTimestamp (uint8_t *header, uint8_t *timestamp)
|
||||||
|
{
|
||||||
|
auto currentTimeStamp = millis() & 0x01FFF;
|
||||||
|
|
||||||
|
*header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB
|
||||||
|
*timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setMidiTimestamp (uint8_t header, uint8_t *timestamp)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// callbacks
|
||||||
|
void(*_connectedCallback)() = nullptr;
|
||||||
|
void(*_disconnectedCallback)() = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setHandleConnected(void(*fptr)()) {
|
||||||
|
_connectedCallback = fptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHandleDisconnected(void(*fptr)()) {
|
||||||
|
_disconnectedCallback = fptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The general form of a MIDI message follows:
|
||||||
|
n-byte MIDI Message
|
||||||
|
Byte 0 MIDI message Status byte, Bit 7 is Set to 1.
|
||||||
|
Bytes 1 to n-1 MIDI message Data bytes, if n > 1. Bit 7 is Set to 0
|
||||||
|
There are two types of MIDI messages that can appear in a single packet: full MIDI messages and
|
||||||
|
Running Status MIDI messages. Each is encoded differently.
|
||||||
|
A full MIDI message is simply the MIDI message with the Status byte included.
|
||||||
|
A Running Status MIDI message is a MIDI message with the Status byte omitted. Running Status
|
||||||
|
MIDI messages may only be placed in the data stream if the following criteria are met:
|
||||||
|
1. The original MIDI message is 2 bytes or greater and is not a System Common or System
|
||||||
|
Real-Time message.
|
||||||
|
2. The omitted Status byte matches the most recently preceding full MIDI message’s Status
|
||||||
|
byte within the same BLE packet.
|
||||||
|
In addition, the following rules apply with respect to Running Status:
|
||||||
|
1. A Running Status MIDI message is allowed within the packet after at least one full MIDI
|
||||||
|
message.
|
||||||
|
2. Every MIDI Status byte must be preceded by a timestamp byte. Running Status MIDI
|
||||||
|
messages may be preceded by a timestamp byte. If a Running Status MIDI message is not
|
||||||
|
preceded by a timestamp byte, the timestamp byte of the most recently preceding message
|
||||||
|
in the same packet is used.
|
||||||
|
3. System Common and System Real-Time messages do not cancel Running Status if
|
||||||
|
interspersed between Running Status MIDI messages. However, a timestamp byte must
|
||||||
|
precede the Running Status MIDI message that follows.
|
||||||
|
4. The end of a BLE packet does cancel Running Status.
|
||||||
|
In the MIDI 1.0 protocol, System Real-Time messages can be sent at any time and may be
|
||||||
|
inserted anywhere in a MIDI data stream, including between Status and Data bytes of any other
|
||||||
|
MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved
|
||||||
|
from other messages – except for System Exclusive messages.
|
||||||
|
*/
|
||||||
|
void receive(byte* buffer, size_t length)
|
||||||
|
{
|
||||||
|
// Pointers used to search through payload.
|
||||||
|
byte lPtr = 0;
|
||||||
|
byte rPtr = 0;
|
||||||
|
// lastStatus used to capture runningStatus
|
||||||
|
byte lastStatus;
|
||||||
|
// Decode first packet -- SHALL be "Full MIDI message"
|
||||||
|
lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status"
|
||||||
|
|
||||||
|
//While statement contains incrementing pointers and breaks when buffer size exceeded.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
lastStatus = buffer[lPtr];
|
||||||
|
|
||||||
|
if( (buffer[lPtr] < 0x80))
|
||||||
|
return; // Status message not present, bail
|
||||||
|
|
||||||
|
// Point to next non-data byte
|
||||||
|
rPtr = lPtr;
|
||||||
|
while( (buffer[rPtr + 1] < 0x80) && (rPtr < (length - 1)) )
|
||||||
|
rPtr++;
|
||||||
|
if (buffer[rPtr + 1] == 0xF7) rPtr++;
|
||||||
|
|
||||||
|
// look at l and r pointers and decode by size.
|
||||||
|
if( rPtr - lPtr < 1 ) {
|
||||||
|
// Time code or system
|
||||||
|
mBleClass.add(lastStatus);
|
||||||
|
} else if( rPtr - lPtr < 2 ) {
|
||||||
|
mBleClass.add(lastStatus);
|
||||||
|
mBleClass.add(buffer[lPtr + 1]);
|
||||||
|
} else if( rPtr - lPtr < 3 ) {
|
||||||
|
mBleClass.add(lastStatus);
|
||||||
|
mBleClass.add(buffer[lPtr + 1]);
|
||||||
|
mBleClass.add(buffer[lPtr + 2]);
|
||||||
|
} else {
|
||||||
|
// Too much data
|
||||||
|
// If not System Common or System Real-Time, send it as running status
|
||||||
|
switch(buffer[lPtr] & 0xF0)
|
||||||
|
{
|
||||||
|
case 0x80:
|
||||||
|
case 0x90:
|
||||||
|
case 0xA0:
|
||||||
|
case 0xB0:
|
||||||
|
case 0xE0:
|
||||||
|
for (auto i = lPtr; i < rPtr; i = i + 2)
|
||||||
|
{
|
||||||
|
mBleClass.add(lastStatus);
|
||||||
|
mBleClass.add(buffer[i + 1]);
|
||||||
|
mBleClass.add(buffer[i + 2]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xC0:
|
||||||
|
case 0xD0:
|
||||||
|
for (auto i = lPtr; i < rPtr; i = i + 1)
|
||||||
|
{
|
||||||
|
mBleClass.add(lastStatus);
|
||||||
|
mBleClass.add(buffer[i + 1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xF0:
|
||||||
|
mBleClass.add(buffer[lPtr]);
|
||||||
|
for (auto i = lPtr; i < rPtr; i++)
|
||||||
|
mBleClass.add(buffer[i + 1]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point to next status
|
||||||
|
lPtr = rPtr + 2;
|
||||||
|
if(lPtr >= length)
|
||||||
|
return; //end of packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
struct MySettings : public MIDI_NAMESPACE::DefaultSettings
|
||||||
|
{
|
||||||
|
static const bool Use1ByteParsing = false;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#include <hardware/BLEMIDI_ESP32_NimBLE.h>
|
||||||
|
//#include <hardware/BLEMIDI_ESP32.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <hardware/BLEMIDI_nRF52.h>
|
||||||
|
#ifdef BLEMIDI_nRF52
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef ArduinoBLE
|
||||||
|
#include <hardware/BLEMIDI_ArduinoBLE.h>
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BLE-MIDI_Namespace.h"
|
||||||
|
|
||||||
|
// As specified in
|
||||||
|
// Specification for MIDI over Bluetooth Low Energy (BLE-MIDI)
|
||||||
|
// Version 1.0a, NOvember 1, 2015
|
||||||
|
// 3. BLE Service and Characteristics Definitions
|
||||||
|
#define SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700"
|
||||||
|
#define CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3"
|
||||||
|
|
||||||
|
#if ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#else
|
||||||
|
#include <inttypes.h>
|
||||||
|
typedef uint8_t byte;
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BLE-MIDI_Namespace.h"
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
struct DefaultSettings
|
||||||
|
{
|
||||||
|
static const size_t MaxBufferSize = 64;
|
||||||
|
};
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -1,524 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// Headers for ESP32 BLE
|
|
||||||
#include <BLEDevice.h>
|
|
||||||
#include <BLEUtils.h>
|
|
||||||
#include <BLEServer.h>
|
|
||||||
#include <BLE2902.h>
|
|
||||||
//#include "BLEScan.h"
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
#include "common/midiCommon.h"
|
|
||||||
using namespace Midi;
|
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
|
||||||
|
|
||||||
static bool isDataReceived = false;
|
|
||||||
static bool doScan = false;
|
|
||||||
static BLEScan* pBLEScan;
|
|
||||||
|
|
||||||
static void notifyCallback(
|
|
||||||
BLERemoteCharacteristic* pBLERemoteCharacteristic,
|
|
||||||
uint8_t* pData,
|
|
||||||
size_t length,
|
|
||||||
bool isNotify)
|
|
||||||
{
|
|
||||||
isDataReceived = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void scanCompleteCB(BLEScanResults scanResults)
|
|
||||||
{
|
|
||||||
scanResults.dump();
|
|
||||||
if (doScan)
|
|
||||||
{
|
|
||||||
pBLEScan->start(10, scanCompleteCB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyClientCallbacks;
|
|
||||||
|
|
||||||
class BleMidiInterfaceClient : public MidiCommonInterface
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
// ESP32
|
|
||||||
BLEClient * pClient;
|
|
||||||
//BLEAdvertising * _advertising;
|
|
||||||
//BLECharacteristic *_characteristic;
|
|
||||||
|
|
||||||
uint8_t _midiPacket[5]; // outgoing
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
void * operator new(size_t size)
|
|
||||||
{
|
|
||||||
void * p = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
// callbacks
|
|
||||||
void(*_connectedCallback)() = NULL;
|
|
||||||
void(*_disconnectedCallback)() = NULL;
|
|
||||||
char _deviceName[32];
|
|
||||||
bool _doConnect;
|
|
||||||
BLEAdvertisedDevice * _advertising;
|
|
||||||
BLERemoteService* pRemoteService;
|
|
||||||
BLERemoteCharacteristic* pRemoteCharacteristic;
|
|
||||||
bool _connected;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
inline static void getMidiTimestamp (uint8_t *header, uint8_t *timestamp)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages.
|
|
||||||
Header Byte
|
|
||||||
bit 7 Set to 1.
|
|
||||||
bit 6 Set to 0. (Reserved for future use)
|
|
||||||
bits 5-0 timestampHigh:Most significant 6 bits of timestamp information.
|
|
||||||
The header byte contains the topmost 6 bits of timing information for MIDI events in the BLE
|
|
||||||
packet. The remaining 7 bits of timing information for individual MIDI messages encoded in a
|
|
||||||
packet is expressed by timestamp bytes.
|
|
||||||
Timestamp Byte
|
|
||||||
bit 7 Set to 1.
|
|
||||||
bits 6-0 timestampLow: Least Significant 7 bits of timestamp information.
|
|
||||||
The 13-bit timestamp for the first MIDI message in a packet is calculated using 6 bits from the
|
|
||||||
header byte and 7 bits from the timestamp byte.
|
|
||||||
Timestamps are 13-bit values in milliseconds, and therefore the maximum value is 8,191 ms.
|
|
||||||
Timestamps must be issued by the sender in a monotonically increasing fashion.
|
|
||||||
timestampHigh is initially set using the lower 6 bits from the header byte while the timestampLow is
|
|
||||||
formed of the lower 7 bits from the timestamp byte. Should the timestamp value of a subsequent
|
|
||||||
MIDI message in the same packet overflow/wrap (i.e., the timestampLow is smaller than a
|
|
||||||
preceding timestampLow), the receiver is responsible for tracking this by incrementing the
|
|
||||||
timestampHigh by one (the incremented value is not transmitted, only understood as a result of the
|
|
||||||
overflow condition).
|
|
||||||
In practice, the time difference between MIDI messages in the same BLE packet should not span
|
|
||||||
more than twice the connection interval. As a result, a maximum of one overflow/wrap may occur
|
|
||||||
per BLE packet.
|
|
||||||
Timestamps are in the sender’s clock domain and are not allowed to be scheduled in the future.
|
|
||||||
Correlation between the receiver’s clock and the received timestamps must be performed to
|
|
||||||
ensure accurate rendering of MIDI messages, and is not addressed in this document.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
Calculating a Timestamp
|
|
||||||
To calculate the timestamp, the built-in millis() is used.
|
|
||||||
The BLE standard only specifies 13 bits worth of millisecond data though,
|
|
||||||
so it’s bitwise anded with 0x1FFF for an ever repeating cycle of 13 bits.
|
|
||||||
This is done right after a MIDI message is detected. It’s split into a 6 upper bits, 7 lower bits,
|
|
||||||
and the MSB of both bytes are set to indicate that this is a header byte.
|
|
||||||
Both bytes are placed into the first two position of an array in preparation for a MIDI message.
|
|
||||||
*/
|
|
||||||
auto currentTimeStamp = millis() & 0x01FFF;
|
|
||||||
|
|
||||||
*header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB
|
|
||||||
*timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialize towards hardware
|
|
||||||
|
|
||||||
void write(DataByte b1)
|
|
||||||
{
|
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
|
||||||
|
|
||||||
// TODO: quid running status
|
|
||||||
|
|
||||||
if(pRemoteCharacteristic && pRemoteCharacteristic->canWrite())
|
|
||||||
{
|
|
||||||
pRemoteCharacteristic->writeValue(_midiPacket, 3);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void write(DataByte b1, DataByte b2)
|
|
||||||
{
|
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
|
||||||
_midiPacket[3] = b2;
|
|
||||||
|
|
||||||
// TODO: quid running status
|
|
||||||
if(pRemoteCharacteristic && pRemoteCharacteristic->canWrite())
|
|
||||||
{
|
|
||||||
pRemoteCharacteristic->writeValue(_midiPacket, 4);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void write(DataByte b1, DataByte b2, DataByte b3)
|
|
||||||
{
|
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
|
||||||
_midiPacket[3] = b2;
|
|
||||||
_midiPacket[4] = b3;
|
|
||||||
|
|
||||||
// TODO: quid running status
|
|
||||||
if(pRemoteCharacteristic && pRemoteCharacteristic->canWrite())
|
|
||||||
{
|
|
||||||
pRemoteCharacteristic->writeValue(_midiPacket, 5);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool connectToServer()
|
|
||||||
{
|
|
||||||
// Connect to the remote BLE Server.
|
|
||||||
pClient->connect(_advertising); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
|
|
||||||
|
|
||||||
// Obtain a reference to the service we are after in the remote BLE server.
|
|
||||||
pRemoteService = pClient->getService(BLEUUID(SERVICE_UUID));
|
|
||||||
if (pRemoteService == nullptr)
|
|
||||||
{
|
|
||||||
pClient->disconnect();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain a reference to the characteristic in the service of the remote BLE server.
|
|
||||||
pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(CHARACTERISTIC_UUID));
|
|
||||||
if (pRemoteCharacteristic == nullptr)
|
|
||||||
{
|
|
||||||
pClient->disconnect();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
BleMidiInterfaceClient()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~BleMidiInterfaceClient()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO why must these functions be inline??
|
|
||||||
|
|
||||||
inline bool begin(const char* deviceName);
|
|
||||||
|
|
||||||
inline void read()
|
|
||||||
{
|
|
||||||
// If the flag "doConnect" is true then we have scanned for and found the desired
|
|
||||||
// BLE Server with which we wish to connect. Now we connect to it. Once we are
|
|
||||||
// connected we set the connected flag to be true.
|
|
||||||
if (_doConnect == true)
|
|
||||||
{
|
|
||||||
if (connectToServer())
|
|
||||||
{
|
|
||||||
// ("We are now connected to the BLE Server.");
|
|
||||||
if (nullptr != pRemoteCharacteristic)
|
|
||||||
{
|
|
||||||
pRemoteCharacteristic->registerForNotify(notifyCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ("We have failed to connect to the server; there is nothin more we will do.");
|
|
||||||
doScan = true;
|
|
||||||
pBLEScan->start(10, scanCompleteCB);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_doConnect = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (false == _connected)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (nullptr == pRemoteCharacteristic)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Read the value of the characteristic.
|
|
||||||
//if(pRemoteCharacteristic->canRead())
|
|
||||||
if (isDataReceived)
|
|
||||||
{
|
|
||||||
std::string rxValue = pRemoteCharacteristic->readValue();
|
|
||||||
if (rxValue.length() > 0)
|
|
||||||
{
|
|
||||||
this->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
isDataReceived = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void sendMIDI(StatusByte, DataByte data1 = 0, DataByte data2 = 0);
|
|
||||||
inline void receive(uint8_t *buffer, uint8_t bufferSize);
|
|
||||||
|
|
||||||
void onConnected(void(*fptr)())
|
|
||||||
{
|
|
||||||
_connectedCallback = fptr;
|
|
||||||
}
|
|
||||||
void onDisconnected(void(*fptr)())
|
|
||||||
{
|
|
||||||
_disconnectedCallback = fptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyClientCallbacks: public BLEClientCallbacks {
|
|
||||||
public:
|
|
||||||
MyClientCallbacks(BleMidiInterfaceClient* BleMidiInterfaceClient) {
|
|
||||||
_BleMidiInterfaceClient = BleMidiInterfaceClient;
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
BleMidiInterfaceClient* _BleMidiInterfaceClient;
|
|
||||||
|
|
||||||
void onConnect(BLEClient* client)
|
|
||||||
{
|
|
||||||
if (_BleMidiInterfaceClient->_connectedCallback)
|
|
||||||
{
|
|
||||||
_BleMidiInterfaceClient->_connectedCallback();
|
|
||||||
}
|
|
||||||
_BleMidiInterfaceClient->_connected = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
void onDisconnect(BLEClient* client)
|
|
||||||
{
|
|
||||||
if (_BleMidiInterfaceClient->_disconnectedCallback)
|
|
||||||
{
|
|
||||||
_BleMidiInterfaceClient->_disconnectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
_BleMidiInterfaceClient->_doConnect = false;
|
|
||||||
_BleMidiInterfaceClient->pRemoteService = nullptr;
|
|
||||||
_BleMidiInterfaceClient->pRemoteCharacteristic = nullptr;
|
|
||||||
doScan = true;
|
|
||||||
pBLEScan->start(10, scanCompleteCB);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
|
|
||||||
/**
|
|
||||||
* Called for each advertising BLE server.
|
|
||||||
*/
|
|
||||||
public:
|
|
||||||
|
|
||||||
MyAdvertisedDeviceCallbacks(BleMidiInterfaceClient* BleMidiInterfaceClient) : BLEAdvertisedDeviceCallbacks()
|
|
||||||
{
|
|
||||||
_BleMidiInterfaceClient = BleMidiInterfaceClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onResult(BLEAdvertisedDevice advertisedDevice) {
|
|
||||||
|
|
||||||
if (strstr(advertisedDevice.toString().c_str(), _BleMidiInterfaceClient->_deviceName) == NULL)
|
|
||||||
{
|
|
||||||
// Check that the device matches our host name
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have found a device, let us now see if it contains the service we are looking for.
|
|
||||||
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID)))
|
|
||||||
{
|
|
||||||
BLEDevice::getScan()->stop();
|
|
||||||
_BleMidiInterfaceClient->_advertising = new BLEAdvertisedDevice(advertisedDevice);
|
|
||||||
_BleMidiInterfaceClient->_doConnect = true;
|
|
||||||
doScan = false;
|
|
||||||
|
|
||||||
} // Found our server
|
|
||||||
} // onResult
|
|
||||||
|
|
||||||
protected:
|
|
||||||
BleMidiInterfaceClient* _BleMidiInterfaceClient;
|
|
||||||
|
|
||||||
}; // MyAdvertisedDeviceCallbacks
|
|
||||||
|
|
||||||
bool BleMidiInterfaceClient::begin(const char* deviceName)
|
|
||||||
{
|
|
||||||
BLEDevice::init(deviceName);
|
|
||||||
|
|
||||||
strncpy(_deviceName, deviceName, 32);
|
|
||||||
|
|
||||||
pClient = BLEDevice::createClient();
|
|
||||||
pClient->setClientCallbacks(new MyClientCallbacks(this));
|
|
||||||
|
|
||||||
// Retrieve a Scanner and set the callback we want to use to be informed when we
|
|
||||||
// have detected a new device. Specify that we want active scanning and start the
|
|
||||||
// scan to run for 5 seconds.
|
|
||||||
pBLEScan = BLEDevice::getScan();
|
|
||||||
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(this));
|
|
||||||
pBLEScan->setInterval(1349);
|
|
||||||
pBLEScan->setWindow(449);
|
|
||||||
pBLEScan->setActiveScan(true);
|
|
||||||
doScan = true;
|
|
||||||
pBLEScan->start(10, scanCompleteCB);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void BleMidiInterfaceClient::sendMIDI(StatusByte status, DataByte data1, DataByte data2)
|
|
||||||
{
|
|
||||||
MidiType type = getTypeFromStatusByte(status);
|
|
||||||
Channel channel = getChannelFromStatusByte(status);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case NoteOff:
|
|
||||||
if (_noteOffCallback) _noteOffCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case NoteOn:
|
|
||||||
if (_noteOnCallback) _noteOnCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case AfterTouchPoly:
|
|
||||||
if (_afterTouchPolyCallback) _afterTouchPolyCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case ControlChange:
|
|
||||||
if (_controlChangeCallback) _controlChangeCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case ProgramChange:
|
|
||||||
if (_programChangeCallback) _programChangeCallback(channel, data1);
|
|
||||||
break;
|
|
||||||
case AfterTouchChannel:
|
|
||||||
if (_afterTouchChannelCallback) _afterTouchChannelCallback(channel, data1);
|
|
||||||
break;
|
|
||||||
case PitchBend:
|
|
||||||
if (_pitchBendCallback) {
|
|
||||||
int value = (int) ((data1 & 0x7f) | ((data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN;
|
|
||||||
_pitchBendCallback(channel, value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SystemExclusive:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TimeCodeQuarterFrame:
|
|
||||||
if (_timeCodeQuarterFrameCallback) _timeCodeQuarterFrameCallback(data1);
|
|
||||||
break;
|
|
||||||
case SongPosition:
|
|
||||||
if (_songPositionCallback) {
|
|
||||||
unsigned short value = unsigned((data1 & 0x7f) | ((data2 & 0x7f) << 7));
|
|
||||||
_songPositionCallback(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SongSelect:
|
|
||||||
if (_songSelectCallback) _songSelectCallback(data1);
|
|
||||||
break;
|
|
||||||
case TuneRequest:
|
|
||||||
if (_tuneRequestCallback) _tuneRequestCallback();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Clock:
|
|
||||||
if (_clockCallback) _clockCallback();
|
|
||||||
break;
|
|
||||||
case Tick:
|
|
||||||
break;
|
|
||||||
case Start:
|
|
||||||
if (_startCallback) _startCallback();
|
|
||||||
break;
|
|
||||||
case Continue:
|
|
||||||
if (_continueCallback) _continueCallback();
|
|
||||||
break;
|
|
||||||
case Stop:
|
|
||||||
if (_stopCallback) _stopCallback();
|
|
||||||
break;
|
|
||||||
case ActiveSensing:
|
|
||||||
if (_activeSensingCallback) _activeSensingCallback();
|
|
||||||
break;
|
|
||||||
case SystemReset:
|
|
||||||
if (_resetCallback) _resetCallback();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BleMidiInterfaceClient::receive(uint8_t *buffer, uint8_t bufferSize)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
The general form of a MIDI message follows:
|
|
||||||
n-byte MIDI Message
|
|
||||||
Byte 0 MIDI message Status byte, Bit 7 is Set to 1.
|
|
||||||
Bytes 1 to n-1 MIDI message Data bytes, if n > 1. Bit 7 is Set to 0
|
|
||||||
There are two types of MIDI messages that can appear in a single packet: full MIDI messages and
|
|
||||||
Running Status MIDI messages. Each is encoded differently.
|
|
||||||
A full MIDI message is simply the MIDI message with the Status byte included.
|
|
||||||
A Running Status MIDI message is a MIDI message with the Status byte omitted. Running Status
|
|
||||||
MIDI messages may only be placed in the data stream if the following criteria are met:
|
|
||||||
1. The original MIDI message is 2 bytes or greater and is not a System Common or System
|
|
||||||
Real-Time message.
|
|
||||||
2. The omitted Status byte matches the most recently preceding full MIDI message’s Status
|
|
||||||
byte within the same BLE packet.
|
|
||||||
In addition, the following rules apply with respect to Running Status:
|
|
||||||
1. A Running Status MIDI message is allowed within the packet after at least one full MIDI
|
|
||||||
message.
|
|
||||||
2. Every MIDI Status byte must be preceded by a timestamp byte. Running Status MIDI
|
|
||||||
messages may be preceded by a timestamp byte. If a Running Status MIDI message is not
|
|
||||||
preceded by a timestamp byte, the timestamp byte of the most recently preceding message
|
|
||||||
in the same packet is used.
|
|
||||||
3. System Common and System Real-Time messages do not cancel Running Status if
|
|
||||||
interspersed between Running Status MIDI messages. However, a timestamp byte must
|
|
||||||
precede the Running Status MIDI message that follows.
|
|
||||||
4. The end of a BLE packet does cancel Running Status.
|
|
||||||
In the MIDI 1.0 protocol, System Real-Time messages can be sent at any time and may be
|
|
||||||
inserted anywhere in a MIDI data stream, including between Status and Data bytes of any other
|
|
||||||
MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved
|
|
||||||
from other messages – except for System Exclusive messages.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Pointers used to search through payload.
|
|
||||||
uint8_t lPtr = 0;
|
|
||||||
uint8_t rPtr = 0;
|
|
||||||
|
|
||||||
//lastStatus used to capture runningStatus
|
|
||||||
uint8_t lastStatus;
|
|
||||||
|
|
||||||
//Decode first packet -- SHALL be "Full MIDI message"
|
|
||||||
lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status"
|
|
||||||
|
|
||||||
//While statement contains incrementing pointers and breaks when buffer size exceeded.
|
|
||||||
while(1){
|
|
||||||
lastStatus = buffer[lPtr];
|
|
||||||
if( (buffer[lPtr] < 0x80) ){
|
|
||||||
//Status message not present, bail
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//Point to next non-data byte
|
|
||||||
rPtr = lPtr;
|
|
||||||
while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){
|
|
||||||
rPtr++;
|
|
||||||
}
|
|
||||||
//look at l and r pointers and decode by size.
|
|
||||||
if( rPtr - lPtr < 1 ){
|
|
||||||
//Time code or system
|
|
||||||
sendMIDI(lastStatus);
|
|
||||||
} else if( rPtr - lPtr < 2 ) {
|
|
||||||
sendMIDI(lastStatus, buffer[lPtr + 1]);
|
|
||||||
} else if( rPtr - lPtr < 3 ) {
|
|
||||||
sendMIDI(lastStatus, buffer[lPtr + 1], buffer[lPtr + 2]);
|
|
||||||
} else {
|
|
||||||
//Too much data
|
|
||||||
//If not System Common or System Real-Time, send it as running status
|
|
||||||
switch( buffer[lPtr] & 0xF0 )
|
|
||||||
{
|
|
||||||
case NoteOff:
|
|
||||||
case NoteOn:
|
|
||||||
case AfterTouchPoly:
|
|
||||||
case ControlChange:
|
|
||||||
case PitchBend:
|
|
||||||
for(int i = lPtr; i < rPtr; i = i + 2)
|
|
||||||
sendMIDI(lastStatus, buffer[i + 1], buffer[i + 2]);
|
|
||||||
break;
|
|
||||||
case ProgramChange:
|
|
||||||
case AfterTouchChannel:
|
|
||||||
for(int i = lPtr; i < rPtr; i = i + 1)
|
|
||||||
sendMIDI(lastStatus, buffer[i + 1]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Point to next status
|
|
||||||
lPtr = rPtr + 2;
|
|
||||||
if(lPtr >= bufferSize){
|
|
||||||
//end of packet
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
END_BLEMIDI_NAMESPACE
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#include "BleMidi.h"
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file BleMidi.h
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utility/BleMidi_Settings.h"
|
|
||||||
#include "utility/BleMidi_Defs.h"
|
|
||||||
|
|
||||||
#define SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700"
|
|
||||||
#define CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3"
|
|
||||||
|
|
||||||
#if defined(ESP32)
|
|
||||||
#include "Ble_esp32.h"
|
|
||||||
#include "BleClient_esp32.h"
|
|
||||||
#endif
|
|
||||||
395
src/Ble_esp32.h
395
src/Ble_esp32.h
|
|
@ -1,395 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// Headers for ESP32 BLE
|
|
||||||
#include <BLEDevice.h>
|
|
||||||
#include <BLEUtils.h>
|
|
||||||
#include <BLEServer.h>
|
|
||||||
#include <BLE2902.h>
|
|
||||||
|
|
||||||
#include "common/midiCommon.h"
|
|
||||||
using namespace Midi;
|
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
|
||||||
|
|
||||||
class BleMidiInterface : public MidiCommonInterface
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
// ESP32
|
|
||||||
BLEServer * _server;
|
|
||||||
BLEAdvertising * _advertising;
|
|
||||||
BLECharacteristic *_characteristic;
|
|
||||||
|
|
||||||
bool _connected;
|
|
||||||
|
|
||||||
uint8_t _midiPacket[5]; // outgoing
|
|
||||||
|
|
||||||
public:
|
|
||||||
// callbacks
|
|
||||||
void(*_connectedCallback)() = NULL;
|
|
||||||
void(*_disconnectedCallback)() = NULL;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
inline static void getMidiTimestamp (uint8_t *header, uint8_t *timestamp)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages.
|
|
||||||
Header Byte
|
|
||||||
bit 7 Set to 1.
|
|
||||||
bit 6 Set to 0. (Reserved for future use)
|
|
||||||
bits 5-0 timestampHigh:Most significant 6 bits of timestamp information.
|
|
||||||
The header byte contains the topmost 6 bits of timing information for MIDI events in the BLE
|
|
||||||
packet. The remaining 7 bits of timing information for individual MIDI messages encoded in a
|
|
||||||
packet is expressed by timestamp bytes.
|
|
||||||
Timestamp Byte
|
|
||||||
bit 7 Set to 1.
|
|
||||||
bits 6-0 timestampLow: Least Significant 7 bits of timestamp information.
|
|
||||||
The 13-bit timestamp for the first MIDI message in a packet is calculated using 6 bits from the
|
|
||||||
header byte and 7 bits from the timestamp byte.
|
|
||||||
Timestamps are 13-bit values in milliseconds, and therefore the maximum value is 8,191 ms.
|
|
||||||
Timestamps must be issued by the sender in a monotonically increasing fashion.
|
|
||||||
timestampHigh is initially set using the lower 6 bits from the header byte while the timestampLow is
|
|
||||||
formed of the lower 7 bits from the timestamp byte. Should the timestamp value of a subsequent
|
|
||||||
MIDI message in the same packet overflow/wrap (i.e., the timestampLow is smaller than a
|
|
||||||
preceding timestampLow), the receiver is responsible for tracking this by incrementing the
|
|
||||||
timestampHigh by one (the incremented value is not transmitted, only understood as a result of the
|
|
||||||
overflow condition).
|
|
||||||
In practice, the time difference between MIDI messages in the same BLE packet should not span
|
|
||||||
more than twice the connection interval. As a result, a maximum of one overflow/wrap may occur
|
|
||||||
per BLE packet.
|
|
||||||
Timestamps are in the sender’s clock domain and are not allowed to be scheduled in the future.
|
|
||||||
Correlation between the receiver’s clock and the received timestamps must be performed to
|
|
||||||
ensure accurate rendering of MIDI messages, and is not addressed in this document.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
Calculating a Timestamp
|
|
||||||
To calculate the timestamp, the built-in millis() is used.
|
|
||||||
The BLE standard only specifies 13 bits worth of millisecond data though,
|
|
||||||
so it’s bitwise anded with 0x1FFF for an ever repeating cycle of 13 bits.
|
|
||||||
This is done right after a MIDI message is detected. It’s split into a 6 upper bits, 7 lower bits,
|
|
||||||
and the MSB of both bytes are set to indicate that this is a header byte.
|
|
||||||
Both bytes are placed into the first two position of an array in preparation for a MIDI message.
|
|
||||||
*/
|
|
||||||
auto currentTimeStamp = millis() & 0x01FFF;
|
|
||||||
|
|
||||||
*header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB
|
|
||||||
*timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// serialize towards hardware
|
|
||||||
|
|
||||||
void write(DataByte b1)
|
|
||||||
{
|
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
|
||||||
|
|
||||||
// TODO: quid running status
|
|
||||||
|
|
||||||
_characteristic->setValue(_midiPacket, 3);
|
|
||||||
_characteristic->notify();
|
|
||||||
};
|
|
||||||
|
|
||||||
void write(DataByte b1, DataByte b2)
|
|
||||||
{
|
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
|
||||||
_midiPacket[3] = b2;
|
|
||||||
|
|
||||||
// TODO: quid running status
|
|
||||||
|
|
||||||
_characteristic->setValue(_midiPacket, 4);
|
|
||||||
_characteristic->notify();
|
|
||||||
};
|
|
||||||
|
|
||||||
void write(DataByte b1, DataByte b2, DataByte b3)
|
|
||||||
{
|
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
|
||||||
_midiPacket[3] = b2;
|
|
||||||
_midiPacket[4] = b3;
|
|
||||||
|
|
||||||
// TODO: quid running status
|
|
||||||
|
|
||||||
_characteristic->setValue(_midiPacket, 5);
|
|
||||||
_characteristic->notify();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
BleMidiInterface()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~BleMidiInterface()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO why must these functions be inline??
|
|
||||||
|
|
||||||
inline bool begin(const char* deviceName);
|
|
||||||
|
|
||||||
inline void read()
|
|
||||||
{
|
|
||||||
// n/a no need to call read() in loop, as incoming data comes in async via onWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void sendMIDI(StatusByte, DataByte data1 = 0, DataByte data2 = 0);
|
|
||||||
inline void receive(uint8_t *buffer, uint8_t bufferSize);
|
|
||||||
|
|
||||||
void onConnected(void(*fptr)()) {
|
|
||||||
_connected = true;
|
|
||||||
_connectedCallback = fptr;
|
|
||||||
}
|
|
||||||
void onDisconnected(void(*fptr)()) {
|
|
||||||
_connected = false;
|
|
||||||
_disconnectedCallback = fptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyServerCallbacks: public BLEServerCallbacks {
|
|
||||||
public:
|
|
||||||
MyServerCallbacks(BleMidiInterface* bleMidiInterface) {
|
|
||||||
_bleMidiInterface = bleMidiInterface;
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
BleMidiInterface* _bleMidiInterface;
|
|
||||||
|
|
||||||
void onConnect(BLEServer* server) {
|
|
||||||
if (_bleMidiInterface->_connectedCallback)
|
|
||||||
_bleMidiInterface->_connectedCallback();
|
|
||||||
};
|
|
||||||
|
|
||||||
void onDisconnect(BLEServer* server) {
|
|
||||||
if (_bleMidiInterface->_disconnectedCallback)
|
|
||||||
_bleMidiInterface->_disconnectedCallback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
|
|
||||||
public:
|
|
||||||
MyCharacteristicCallbacks(BleMidiInterface* bleMidiInterface) {
|
|
||||||
_bleMidiInterface = bleMidiInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
BleMidiInterface* _bleMidiInterface;
|
|
||||||
|
|
||||||
void onWrite(BLECharacteristic * characteristic) {
|
|
||||||
std::string rxValue = characteristic->getValue();
|
|
||||||
if (rxValue.length() > 0) {
|
|
||||||
_bleMidiInterface->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool BleMidiInterface::begin(const char* deviceName)
|
|
||||||
{
|
|
||||||
BLEDevice::init(deviceName);
|
|
||||||
|
|
||||||
_server = BLEDevice::createServer();
|
|
||||||
_server->setCallbacks(new MyServerCallbacks(this));
|
|
||||||
|
|
||||||
// Create the BLE Service
|
|
||||||
auto service = _server->createService(BLEUUID(SERVICE_UUID));
|
|
||||||
|
|
||||||
// Create a BLE Characteristic
|
|
||||||
_characteristic = service->createCharacteristic(
|
|
||||||
BLEUUID(CHARACTERISTIC_UUID),
|
|
||||||
BLECharacteristic::PROPERTY_READ |
|
|
||||||
BLECharacteristic::PROPERTY_WRITE |
|
|
||||||
BLECharacteristic::PROPERTY_NOTIFY |
|
|
||||||
BLECharacteristic::PROPERTY_WRITE_NR
|
|
||||||
);
|
|
||||||
// Add CCCD 0x2902 to allow notify
|
|
||||||
_characteristic->addDescriptor(new BLE2902());
|
|
||||||
|
|
||||||
_characteristic->setCallbacks(new MyCharacteristicCallbacks(this));
|
|
||||||
// Start the service
|
|
||||||
service->start();
|
|
||||||
|
|
||||||
auto advertisementData = BLEAdvertisementData();
|
|
||||||
advertisementData.setFlags(0x04);
|
|
||||||
advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID));
|
|
||||||
advertisementData.setName(deviceName);
|
|
||||||
|
|
||||||
// Start advertising
|
|
||||||
_advertising = _server->getAdvertising();
|
|
||||||
_advertising->setAdvertisementData(advertisementData);
|
|
||||||
_advertising->start();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BleMidiInterface::sendMIDI(StatusByte status, DataByte data1, DataByte data2)
|
|
||||||
{
|
|
||||||
MidiType type = getTypeFromStatusByte(status);
|
|
||||||
Channel channel = getChannelFromStatusByte(status);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case NoteOff:
|
|
||||||
if (_noteOffCallback) _noteOffCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case NoteOn:
|
|
||||||
if (_noteOnCallback) _noteOnCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case AfterTouchPoly:
|
|
||||||
if (_afterTouchPolyCallback) _afterTouchPolyCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case ControlChange:
|
|
||||||
if (_controlChangeCallback) _controlChangeCallback(channel, data1, data2);
|
|
||||||
break;
|
|
||||||
case ProgramChange:
|
|
||||||
if (_programChangeCallback) _programChangeCallback(channel, data1);
|
|
||||||
break;
|
|
||||||
case AfterTouchChannel:
|
|
||||||
if (_afterTouchChannelCallback) _afterTouchChannelCallback(channel, data1);
|
|
||||||
break;
|
|
||||||
case PitchBend:
|
|
||||||
if (_pitchBendCallback) {
|
|
||||||
int value = (int) ((data1 & 0x7f) | ((data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN;
|
|
||||||
_pitchBendCallback(channel, value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SystemExclusive:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TimeCodeQuarterFrame:
|
|
||||||
if (_timeCodeQuarterFrameCallback) _timeCodeQuarterFrameCallback(data1);
|
|
||||||
break;
|
|
||||||
case SongPosition:
|
|
||||||
if (_songPositionCallback) {
|
|
||||||
unsigned short value = unsigned((data1 & 0x7f) | ((data2 & 0x7f) << 7));
|
|
||||||
_songPositionCallback(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SongSelect:
|
|
||||||
if (_songSelectCallback) _songSelectCallback(data1);
|
|
||||||
break;
|
|
||||||
case TuneRequest:
|
|
||||||
if (_tuneRequestCallback) _tuneRequestCallback();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Clock:
|
|
||||||
if (_clockCallback) _clockCallback();
|
|
||||||
break;
|
|
||||||
case Tick:
|
|
||||||
break;
|
|
||||||
case Start:
|
|
||||||
if (_startCallback) _startCallback();
|
|
||||||
break;
|
|
||||||
case Continue:
|
|
||||||
if (_continueCallback) _continueCallback();
|
|
||||||
break;
|
|
||||||
case Stop:
|
|
||||||
if (_stopCallback) _stopCallback();
|
|
||||||
break;
|
|
||||||
case ActiveSensing:
|
|
||||||
if (_activeSensingCallback) _activeSensingCallback();
|
|
||||||
break;
|
|
||||||
case SystemReset:
|
|
||||||
if (_resetCallback) _resetCallback();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BleMidiInterface::receive(uint8_t *buffer, uint8_t bufferSize)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
The general form of a MIDI message follows:
|
|
||||||
n-byte MIDI Message
|
|
||||||
Byte 0 MIDI message Status byte, Bit 7 is Set to 1.
|
|
||||||
Bytes 1 to n-1 MIDI message Data bytes, if n > 1. Bit 7 is Set to 0
|
|
||||||
There are two types of MIDI messages that can appear in a single packet: full MIDI messages and
|
|
||||||
Running Status MIDI messages. Each is encoded differently.
|
|
||||||
A full MIDI message is simply the MIDI message with the Status byte included.
|
|
||||||
A Running Status MIDI message is a MIDI message with the Status byte omitted. Running Status
|
|
||||||
MIDI messages may only be placed in the data stream if the following criteria are met:
|
|
||||||
1. The original MIDI message is 2 bytes or greater and is not a System Common or System
|
|
||||||
Real-Time message.
|
|
||||||
2. The omitted Status byte matches the most recently preceding full MIDI message’s Status
|
|
||||||
byte within the same BLE packet.
|
|
||||||
In addition, the following rules apply with respect to Running Status:
|
|
||||||
1. A Running Status MIDI message is allowed within the packet after at least one full MIDI
|
|
||||||
message.
|
|
||||||
2. Every MIDI Status byte must be preceded by a timestamp byte. Running Status MIDI
|
|
||||||
messages may be preceded by a timestamp byte. If a Running Status MIDI message is not
|
|
||||||
preceded by a timestamp byte, the timestamp byte of the most recently preceding message
|
|
||||||
in the same packet is used.
|
|
||||||
3. System Common and System Real-Time messages do not cancel Running Status if
|
|
||||||
interspersed between Running Status MIDI messages. However, a timestamp byte must
|
|
||||||
precede the Running Status MIDI message that follows.
|
|
||||||
4. The end of a BLE packet does cancel Running Status.
|
|
||||||
In the MIDI 1.0 protocol, System Real-Time messages can be sent at any time and may be
|
|
||||||
inserted anywhere in a MIDI data stream, including between Status and Data bytes of any other
|
|
||||||
MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved
|
|
||||||
from other messages – except for System Exclusive messages.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Pointers used to search through payload.
|
|
||||||
uint8_t lPtr = 0;
|
|
||||||
uint8_t rPtr = 0;
|
|
||||||
|
|
||||||
//lastStatus used to capture runningStatus
|
|
||||||
uint8_t lastStatus;
|
|
||||||
|
|
||||||
//Decode first packet -- SHALL be "Full MIDI message"
|
|
||||||
lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status"
|
|
||||||
|
|
||||||
//While statement contains incrementing pointers and breaks when buffer size exceeded.
|
|
||||||
while(1){
|
|
||||||
lastStatus = buffer[lPtr];
|
|
||||||
if( (buffer[lPtr] < 0x80) ){
|
|
||||||
//Status message not present, bail
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//Point to next non-data byte
|
|
||||||
rPtr = lPtr;
|
|
||||||
while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){
|
|
||||||
rPtr++;
|
|
||||||
}
|
|
||||||
//look at l and r pointers and decode by size.
|
|
||||||
if( rPtr - lPtr < 1 ){
|
|
||||||
//Time code or system
|
|
||||||
sendMIDI(lastStatus);
|
|
||||||
} else if( rPtr - lPtr < 2 ) {
|
|
||||||
sendMIDI(lastStatus, buffer[lPtr + 1]);
|
|
||||||
} else if( rPtr - lPtr < 3 ) {
|
|
||||||
sendMIDI(lastStatus, buffer[lPtr + 1], buffer[lPtr + 2]);
|
|
||||||
} else {
|
|
||||||
//Too much data
|
|
||||||
//If not System Common or System Real-Time, send it as running status
|
|
||||||
switch( buffer[lPtr] & 0xF0 )
|
|
||||||
{
|
|
||||||
case NoteOff:
|
|
||||||
case NoteOn:
|
|
||||||
case AfterTouchPoly:
|
|
||||||
case ControlChange:
|
|
||||||
case PitchBend:
|
|
||||||
for(int i = lPtr; i < rPtr; i = i + 2)
|
|
||||||
sendMIDI(lastStatus, buffer[i + 1], buffer[i + 2]);
|
|
||||||
break;
|
|
||||||
case ProgramChange:
|
|
||||||
case AfterTouchChannel:
|
|
||||||
for(int i = lPtr; i < rPtr; i = i + 1)
|
|
||||||
sendMIDI(lastStatus, buffer[i + 1]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Point to next status
|
|
||||||
lPtr = rPtr + 2;
|
|
||||||
if(lPtr >= bufferSize){
|
|
||||||
//end of packet
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
END_BLEMIDI_NAMESPACE
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 8f745084c39e6016f0ea7afad95973342c6f7e0e
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Headers for ESP32 BLE
|
||||||
|
#include <BLEDevice.h>
|
||||||
|
#include <BLEUtils.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
class BLEMIDI_Client_ESP32
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
BLEClient* _client = nullptr;
|
||||||
|
|
||||||
|
BLEMIDI<class BLEMIDI_Client_ESP32>* _bleMidiTransport = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEMIDI_Client_ESP32()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool begin(const char*, BLEMIDI<class BLEMIDI_Client_ESP32>*);
|
||||||
|
|
||||||
|
void write(uint8_t* data, uint8_t length)
|
||||||
|
{
|
||||||
|
_characteristic->setValue(data, length);
|
||||||
|
_characteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
// Post the items to the back of the queue
|
||||||
|
// (drop the first 2 items)
|
||||||
|
for (size_t i = 2; i < length; i++)
|
||||||
|
xQueueSend(_bleMidiTransport->mRxQueue, &buffer[i], portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_connectedCallback)
|
||||||
|
_bleMidiTransport->_connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_disconnectedCallback)
|
||||||
|
_bleMidiTransport->_disconnectedCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyClientCallbacks: public BLEClientCallbacks {
|
||||||
|
public:
|
||||||
|
MyClientCallbacks(BLEMIDI_Client_ESP32* bluetoothEsp32)
|
||||||
|
: _bluetoothEsp32(bluetoothEsp32) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BLEMIDI_Client_ESP32* _bluetoothEsp32 = nullptr;
|
||||||
|
|
||||||
|
void onConnect(BLEClient*) {
|
||||||
|
if (_bluetoothEsp32)
|
||||||
|
_bluetoothEsp32->connected();
|
||||||
|
};
|
||||||
|
|
||||||
|
void onDisconnect(BLEClient*) {
|
||||||
|
if (_bluetoothEsp32)
|
||||||
|
_bluetoothEsp32->disconnected();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BLEMIDI_Client_ESP32::begin(const char* deviceName, BLEMIDI<class BLEMIDI_ESP32>* bleMidiTransport)
|
||||||
|
{
|
||||||
|
_bleMidiTransport = bleMidiTransport;
|
||||||
|
|
||||||
|
BLEDevice::init(deviceName);
|
||||||
|
|
||||||
|
_client = BLEDevice::createClient();
|
||||||
|
_client->setCallbacks(new MyClientCallbacks(this));
|
||||||
|
|
||||||
|
// Retrieve a Scanner and set the callback we want to use to be informed when we
|
||||||
|
// have detected a new device. Specify that we want active scanning and start the
|
||||||
|
// scan to run for 5 seconds.
|
||||||
|
pBLEScan = BLEDevice::getScan();
|
||||||
|
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(this));
|
||||||
|
pBLEScan->setInterval(1349);
|
||||||
|
pBLEScan->setWindow(449);
|
||||||
|
pBLEScan->setActiveScan(true);
|
||||||
|
doScan = true;
|
||||||
|
pBLEScan->start(10, scanCompleteCB);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoBLE.h>
|
||||||
|
|
||||||
|
BLEService midiService(SERVICE_UUID);
|
||||||
|
BLEStringCharacteristic midiChar(CHARACTERISTIC_UUID, // standard 16-bit characteristic UUID
|
||||||
|
BLERead | BLEWrite | BLENotify | BLEWriteWithoutResponse, 16); // remote clients will be able to get notifications if this characteristic changes
|
||||||
|
|
||||||
|
#define BLE_POLLING
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
template<typename T, int rawSize>
|
||||||
|
class Fifo {
|
||||||
|
public:
|
||||||
|
const size_t size; //speculative feature, in case it's needed
|
||||||
|
|
||||||
|
Fifo(): size(rawSize)
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
T dequeue()
|
||||||
|
{
|
||||||
|
numberOfElements--;
|
||||||
|
nextOut %= size;
|
||||||
|
return raw[ nextOut++];
|
||||||
|
};
|
||||||
|
|
||||||
|
bool enqueue( T element )
|
||||||
|
{
|
||||||
|
if ( count() >= rawSize )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
numberOfElements++;
|
||||||
|
nextIn %= size;
|
||||||
|
raw[nextIn] = element;
|
||||||
|
nextIn++; //advance to next index
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
T peek() const
|
||||||
|
{
|
||||||
|
return raw[ nextOut % size];
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
nextIn = nextOut = numberOfElements = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// how many elements are currently in the FIFO?
|
||||||
|
size_t count() { return numberOfElements; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t numberOfElements;
|
||||||
|
size_t nextIn;
|
||||||
|
size_t nextOut;
|
||||||
|
T raw[rawSize];
|
||||||
|
};
|
||||||
|
|
||||||
|
class BLEMIDI_ArduinoBLE
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static BLEMIDITransport<class BLEMIDI_ArduinoBLE>* _bleMidiTransport;
|
||||||
|
static BLEDevice* _central;
|
||||||
|
|
||||||
|
Fifo<byte, 64> mRxBuffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEMIDI_ArduinoBLE()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool begin(const char*, BLEMIDITransport<class BLEMIDI_ArduinoBLE>*);
|
||||||
|
|
||||||
|
void write(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
// TODO: test length
|
||||||
|
((BLECharacteristic)midiChar).writeValue(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool available(byte* pvBuffer)
|
||||||
|
{
|
||||||
|
#ifdef BLE_POLLING
|
||||||
|
|
||||||
|
if (mRxBuffer.count() > 0) {
|
||||||
|
*pvBuffer = mRxBuffer.dequeue();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
poll();
|
||||||
|
|
||||||
|
if (midiChar.written()) {
|
||||||
|
// auto buffer = midiChar.value();
|
||||||
|
auto length = midiChar.valueLength();
|
||||||
|
|
||||||
|
if (length > 0) {
|
||||||
|
auto buffer = midiChar.value().c_str();
|
||||||
|
_bleMidiTransport->receive((byte*)buffer, length);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#ifdef BLE_EVENTS
|
||||||
|
/ BLE.poll();
|
||||||
|
return ; // ??
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(byte value)
|
||||||
|
{
|
||||||
|
// called from BLE-MIDI, to add it to a buffer here
|
||||||
|
mRxBuffer.enqueue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void receive(const unsigned char* buffer, size_t length)
|
||||||
|
{
|
||||||
|
// forward the buffer so it can be parsed
|
||||||
|
_bleMidiTransport->receive((uint8_t*)buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool poll()
|
||||||
|
{
|
||||||
|
BLEDevice central = BLE.central();
|
||||||
|
if (!central) {
|
||||||
|
if (_central) {
|
||||||
|
BLEMIDI_ArduinoBLE::blePeripheralDisconnectHandler(*_central);
|
||||||
|
_central = nullptr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!central.connected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nullptr == _central) {
|
||||||
|
BLEMIDI_ArduinoBLE::blePeripheralConnectHandler(central);
|
||||||
|
_central = ¢ral;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (*_central != central) {
|
||||||
|
BLEMIDI_ArduinoBLE::blePeripheralDisconnectHandler(*_central);
|
||||||
|
BLEMIDI_ArduinoBLE::blePeripheralConnectHandler(central);
|
||||||
|
_central = ¢ral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blePeripheralConnectHandler(BLEDevice central)
|
||||||
|
{
|
||||||
|
_central = ¢ral;
|
||||||
|
if (_bleMidiTransport->_connectedCallback)
|
||||||
|
_bleMidiTransport->_connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blePeripheralDisconnectHandler(BLEDevice central)
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_disconnectedCallback)
|
||||||
|
_bleMidiTransport->_disconnectedCallback();
|
||||||
|
_central = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void characteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
|
||||||
|
auto buffer = characteristic.value();
|
||||||
|
auto length = characteristic.valueLength();
|
||||||
|
|
||||||
|
if (length > 0)
|
||||||
|
receive(buffer, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BLEMIDITransport<class BLEMIDI_ArduinoBLE>* BLEMIDI_ArduinoBLE::_bleMidiTransport = nullptr;
|
||||||
|
BLEDevice* BLEMIDI_ArduinoBLE::_central = nullptr;
|
||||||
|
|
||||||
|
bool BLEMIDI_ArduinoBLE::begin(const char* deviceName, BLEMIDITransport<class BLEMIDI_ArduinoBLE>* bleMidiTransport)
|
||||||
|
{
|
||||||
|
_bleMidiTransport = bleMidiTransport;
|
||||||
|
|
||||||
|
if (!BLE.begin())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BLE.setLocalName(deviceName);
|
||||||
|
|
||||||
|
BLE.setAdvertisedService(midiService);
|
||||||
|
midiService.addCharacteristic(midiChar);
|
||||||
|
BLE.addService(midiService);
|
||||||
|
|
||||||
|
#ifdef BLE_EVENTS
|
||||||
|
// assign event handlers for connected, disconnected to peripheral
|
||||||
|
BLE.setEventHandler(BLEConnected, BLEMIDI_ArduinoBLE::blePeripheralConnectHandler);
|
||||||
|
BLE.setEventHandler(BLEDisconnected, BLEMIDI_ArduinoBLE::blePeripheralDisconnectHandler);
|
||||||
|
|
||||||
|
midiChar.setEventHandler(BLEWritten, characteristicWritten);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Start advertising BLE. It will start continuously transmitting BLE
|
||||||
|
advertising packets and will be visible to remote BLE central devices
|
||||||
|
until it receives a new connection */
|
||||||
|
|
||||||
|
// start advertising
|
||||||
|
BLE.advertise();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an instance for nRF52 named <DeviceName>
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
|
||||||
|
BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ArduinoBLE> BLE##Name(DeviceName); \
|
||||||
|
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ArduinoBLE>, MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ArduinoBLE> &)BLE##Name);
|
||||||
|
|
||||||
|
/*! \brief Create a default instance for nRF52 named BLE-MIDI
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
||||||
|
BLEMIDI_CREATE_INSTANCE("BLE-MIDI", MIDI)
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Headers for ESP32 BLE
|
||||||
|
#include <BLEDevice.h>
|
||||||
|
#include <BLEUtils.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
class BLEMIDI_ESP32
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
BLEServer* _server = nullptr;
|
||||||
|
BLEAdvertising* _advertising = nullptr;
|
||||||
|
BLECharacteristic* _characteristic = nullptr;
|
||||||
|
|
||||||
|
BLEMIDITransport<class BLEMIDI_ESP32>* _bleMidiTransport = nullptr;
|
||||||
|
|
||||||
|
friend class MyServerCallbacks;
|
||||||
|
friend class MyCharacteristicCallbacks;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QueueHandle_t mRxQueue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEMIDI_ESP32()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool begin(const char*, BLEMIDITransport<class BLEMIDI_ESP32>*);
|
||||||
|
|
||||||
|
void write(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
_characteristic->setValue(buffer, length);
|
||||||
|
_characteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool available(byte* pvBuffer)
|
||||||
|
{
|
||||||
|
return xQueueReceive(mRxQueue, pvBuffer, 0); // return immediately when the queue is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(byte value)
|
||||||
|
{
|
||||||
|
// called from BLE-MIDI, to add it to a buffer here
|
||||||
|
xQueueSend(mRxQueue, &value, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void receive(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
// parse the incoming buffer
|
||||||
|
_bleMidiTransport->receive(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_connectedCallback)
|
||||||
|
_bleMidiTransport->_connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_disconnectedCallback)
|
||||||
|
_bleMidiTransport->_disconnectedCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
|
public:
|
||||||
|
MyServerCallbacks(BLEMIDI_ESP32* bluetoothEsp32)
|
||||||
|
: _bluetoothEsp32(bluetoothEsp32) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BLEMIDI_ESP32* _bluetoothEsp32 = nullptr;
|
||||||
|
|
||||||
|
void onConnect(BLEServer*) {
|
||||||
|
if (_bluetoothEsp32)
|
||||||
|
_bluetoothEsp32->connected();
|
||||||
|
};
|
||||||
|
|
||||||
|
void onDisconnect(BLEServer*) {
|
||||||
|
if (_bluetoothEsp32)
|
||||||
|
_bluetoothEsp32->disconnected();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
|
||||||
|
public:
|
||||||
|
MyCharacteristicCallbacks(BLEMIDI_ESP32* bluetoothEsp32)
|
||||||
|
: _bluetoothEsp32(bluetoothEsp32 ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BLEMIDI_ESP32* _bluetoothEsp32 = nullptr;
|
||||||
|
|
||||||
|
void onWrite(BLECharacteristic * characteristic) {
|
||||||
|
std::string rxValue = characteristic->getValue();
|
||||||
|
if (rxValue.length() > 0) {
|
||||||
|
_bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BLEMIDI_ESP32::begin(const char* deviceName, BLEMIDITransport<class BLEMIDI_ESP32>* bleMidiTransport)
|
||||||
|
{
|
||||||
|
_bleMidiTransport = bleMidiTransport;
|
||||||
|
|
||||||
|
BLEDevice::init(deviceName);
|
||||||
|
|
||||||
|
// To communicate between the 2 cores.
|
||||||
|
// Core_0 runs here, core_1 runs the BLE stack
|
||||||
|
mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize
|
||||||
|
|
||||||
|
_server = BLEDevice::createServer();
|
||||||
|
_server->setCallbacks(new MyServerCallbacks(this));
|
||||||
|
|
||||||
|
// Create the BLE Service
|
||||||
|
auto service = _server->createService(BLEUUID(SERVICE_UUID));
|
||||||
|
|
||||||
|
// Create a BLE Characteristic
|
||||||
|
_characteristic = service->createCharacteristic(
|
||||||
|
BLEUUID(CHARACTERISTIC_UUID),
|
||||||
|
BLECharacteristic::PROPERTY_READ |
|
||||||
|
BLECharacteristic::PROPERTY_WRITE |
|
||||||
|
BLECharacteristic::PROPERTY_NOTIFY |
|
||||||
|
BLECharacteristic::PROPERTY_WRITE_NR
|
||||||
|
);
|
||||||
|
// Add CCCD 0x2902 to allow notify
|
||||||
|
_characteristic->addDescriptor(new BLE2902());
|
||||||
|
|
||||||
|
_characteristic->setCallbacks(new MyCharacteristicCallbacks(this));
|
||||||
|
// Start the service
|
||||||
|
service->start();
|
||||||
|
|
||||||
|
auto advertisementData = BLEAdvertisementData();
|
||||||
|
advertisementData.setFlags(0x04);
|
||||||
|
advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID));
|
||||||
|
advertisementData.setName(deviceName);
|
||||||
|
|
||||||
|
// Start advertising
|
||||||
|
_advertising = _server->getAdvertising();
|
||||||
|
_advertising->setAdvertisementData(advertisementData);
|
||||||
|
_advertising->start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an instance for ESP32 named <DeviceName>
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
|
||||||
|
BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32> BLE##Name(DeviceName); \
|
||||||
|
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32>, MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32> &)BLE##Name);
|
||||||
|
|
||||||
|
/*! \brief Create a default instance for ESP32 named BLE-MIDI
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
||||||
|
BLEMIDI_CREATE_INSTANCE("BLE-MIDI", MIDI)
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Headers for ESP32 NimBLE
|
||||||
|
#include <NimBLEDevice.h>
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
class BLEMIDI_ESP32_NimBLE
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
BLEServer* _server = nullptr;
|
||||||
|
BLEAdvertising* _advertising = nullptr;
|
||||||
|
BLECharacteristic* _characteristic = nullptr;
|
||||||
|
|
||||||
|
BLEMIDITransport<class BLEMIDI_ESP32_NimBLE>* _bleMidiTransport = nullptr;
|
||||||
|
|
||||||
|
friend class MyServerCallbacks;
|
||||||
|
friend class MyCharacteristicCallbacks;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QueueHandle_t mRxQueue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEMIDI_ESP32_NimBLE()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool begin(const char*, BLEMIDITransport<class BLEMIDI_ESP32_NimBLE>*);
|
||||||
|
|
||||||
|
void write(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
_characteristic->setValue(buffer, length);
|
||||||
|
_characteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool available(byte* pvBuffer)
|
||||||
|
{
|
||||||
|
// return 1 byte from the Queue
|
||||||
|
return xQueueReceive(mRxQueue, (void*)pvBuffer, 0); // return immediately when the queue is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(byte value)
|
||||||
|
{
|
||||||
|
// called from BLE-MIDI, to add it to a buffer here
|
||||||
|
xQueueSend(mRxQueue, &value, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void receive(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
// forward the buffer so it can be parsed
|
||||||
|
_bleMidiTransport->receive(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_connectedCallback)
|
||||||
|
_bleMidiTransport->_connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_disconnectedCallback)
|
||||||
|
_bleMidiTransport->_disconnectedCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
|
public:
|
||||||
|
MyServerCallbacks(BLEMIDI_ESP32_NimBLE* bluetoothEsp32)
|
||||||
|
: _bluetoothEsp32(bluetoothEsp32) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BLEMIDI_ESP32_NimBLE* _bluetoothEsp32 = nullptr;
|
||||||
|
|
||||||
|
void onConnect(BLEServer*) {
|
||||||
|
if (_bluetoothEsp32)
|
||||||
|
_bluetoothEsp32->connected();
|
||||||
|
};
|
||||||
|
|
||||||
|
void onDisconnect(BLEServer*) {
|
||||||
|
if (_bluetoothEsp32)
|
||||||
|
_bluetoothEsp32->disconnected();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
|
||||||
|
public:
|
||||||
|
MyCharacteristicCallbacks(BLEMIDI_ESP32_NimBLE* bluetoothEsp32)
|
||||||
|
: _bluetoothEsp32(bluetoothEsp32 ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BLEMIDI_ESP32_NimBLE* _bluetoothEsp32 = nullptr;
|
||||||
|
|
||||||
|
void onWrite(BLECharacteristic * characteristic) {
|
||||||
|
std::string rxValue = characteristic->getValue();
|
||||||
|
if (rxValue.length() > 0) {
|
||||||
|
_bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BLEMIDI_ESP32_NimBLE::begin(const char* deviceName, BLEMIDITransport<class BLEMIDI_ESP32_NimBLE>* bleMidiTransport)
|
||||||
|
{
|
||||||
|
_bleMidiTransport = bleMidiTransport;
|
||||||
|
|
||||||
|
BLEDevice::init(deviceName);
|
||||||
|
|
||||||
|
// To communicate between the 2 cores.
|
||||||
|
// Core_0 runs here, core_1 runs the BLE stack
|
||||||
|
mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize
|
||||||
|
|
||||||
|
_server = BLEDevice::createServer();
|
||||||
|
_server->setCallbacks(new MyServerCallbacks(this));
|
||||||
|
|
||||||
|
// Create the BLE Service
|
||||||
|
auto service = _server->createService(BLEUUID(SERVICE_UUID));
|
||||||
|
|
||||||
|
// Create a BLE Characteristic
|
||||||
|
_characteristic = service->createCharacteristic(
|
||||||
|
BLEUUID(CHARACTERISTIC_UUID),
|
||||||
|
NIMBLE_PROPERTY::READ |
|
||||||
|
NIMBLE_PROPERTY::WRITE |
|
||||||
|
NIMBLE_PROPERTY::NOTIFY |
|
||||||
|
NIMBLE_PROPERTY::WRITE_NR
|
||||||
|
);
|
||||||
|
|
||||||
|
_characteristic->setCallbacks(new MyCharacteristicCallbacks(this));
|
||||||
|
// Start the service
|
||||||
|
service->start();
|
||||||
|
|
||||||
|
auto advertisementData = BLEAdvertisementData();
|
||||||
|
advertisementData.setFlags(0x04);
|
||||||
|
advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID));
|
||||||
|
advertisementData.setName(deviceName);
|
||||||
|
|
||||||
|
// Start advertising
|
||||||
|
_advertising = _server->getAdvertising();
|
||||||
|
_advertising->setAdvertisementData(advertisementData);
|
||||||
|
_advertising->start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an instance for ESP32 named <DeviceName>
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
|
||||||
|
BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE> BLE##Name(DeviceName); \
|
||||||
|
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE>, MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE> &)BLE##Name);
|
||||||
|
|
||||||
|
/*! \brief Create a default instance for ESP32 named BLE-MIDI
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
||||||
|
BLEMIDI_CREATE_INSTANCE("BLE-MIDI", MIDI)
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bluefruit.h>
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
class BLEMIDI_nRF52
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
BLEDis bledis;
|
||||||
|
BLEMidi blemidi;
|
||||||
|
|
||||||
|
BLEMIDITransport<class BLEMIDI_nRF52>* _bleMidiTransport;
|
||||||
|
|
||||||
|
friend class MyServerCallbacks;
|
||||||
|
friend class MyCharacteristicCallbacks;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BLEMIDI_nRF52()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool begin(const char*, BLEMIDITransport<class BLEMIDI_nRF52>*);
|
||||||
|
|
||||||
|
void write(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool available(byte* pvBuffer)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(byte value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void receive(uint8_t* buffer, size_t length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void connected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_connectedCallback)
|
||||||
|
_bleMidiTransport->_connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnected()
|
||||||
|
{
|
||||||
|
if (_bleMidiTransport->_disconnectedCallback)
|
||||||
|
_bleMidiTransport->_disconnectedCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BLEMIDI_nRF52::begin(const char* deviceName, BLEMIDITransport<class BLEMIDI_nRF52>* bleMidiTransport)
|
||||||
|
{
|
||||||
|
_bleMidiTransport = bleMidiTransport;
|
||||||
|
|
||||||
|
// Config the peripheral connection with maximum bandwidth
|
||||||
|
// more SRAM required by SoftDevice
|
||||||
|
// Note: All config***() function must be called before begin()
|
||||||
|
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||||
|
|
||||||
|
Bluefruit.begin();
|
||||||
|
Bluefruit.setName(deviceName);
|
||||||
|
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
|
||||||
|
|
||||||
|
// Setup the on board blue LED to be enabled on CONNECT
|
||||||
|
Bluefruit.autoConnLed(true);
|
||||||
|
|
||||||
|
// Configure and Start Device Information Service
|
||||||
|
bledis.setManufacturer("Adafruit Industries");
|
||||||
|
bledis.setModel("Bluefruit Feather52");
|
||||||
|
bledis.begin();
|
||||||
|
|
||||||
|
// Start advertising ----------------------------
|
||||||
|
|
||||||
|
// Set General Discoverable Mode flag
|
||||||
|
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||||
|
|
||||||
|
// Advertise TX Power
|
||||||
|
Bluefruit.Advertising.addTxPower();
|
||||||
|
|
||||||
|
// Advertise BLE MIDI Service
|
||||||
|
Bluefruit.Advertising.addService(blemidi);
|
||||||
|
|
||||||
|
// Secondary Scan Response packet (optional)
|
||||||
|
// Since there is no room for 'Name' in Advertising packet
|
||||||
|
Bluefruit.ScanResponse.addName();
|
||||||
|
|
||||||
|
/* Start Advertising
|
||||||
|
* - Enable auto advertising if disconnected
|
||||||
|
* - Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||||
|
* - Timeout for fast mode is 30 seconds
|
||||||
|
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||||
|
*
|
||||||
|
* For recommended advertising interval
|
||||||
|
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||||
|
*/
|
||||||
|
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||||
|
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||||
|
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||||
|
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an instance for nRF52 named <DeviceName>
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
|
||||||
|
BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_nRF52> BLE##Name(DeviceName); \
|
||||||
|
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_nRF52>, MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDITransport<BLEMIDI_NAMESPACE::BLEMIDI_nRF52> &)BLE##Name);
|
||||||
|
|
||||||
|
/*! \brief Create a default instance for nRF52 named BLE-MIDI
|
||||||
|
*/
|
||||||
|
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
||||||
|
BLEMIDI_CREATE_INSTANCE("BLE-MIDI", MIDI)
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
Binary file not shown.
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "BleMidi_Namespace.h"
|
|
||||||
|
|
||||||
#if ARDUINO
|
|
||||||
#include <Arduino.h>
|
|
||||||
#else
|
|
||||||
#include <inttypes.h>
|
|
||||||
typedef uint8_t byte;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
|
||||||
|
|
||||||
/*! \brief Create an instance of the library
|
|
||||||
*/
|
|
||||||
#define BLEMIDI_CREATE_INSTANCE(Name) \
|
|
||||||
BLEMIDI_NAMESPACE::BleMidiInterface Name;
|
|
||||||
|
|
||||||
/*! \brief Create an instance of the library
|
|
||||||
*/
|
|
||||||
#define BLEMIDI_CREATE_CLIENT_INSTANCE(Name) \
|
|
||||||
BLEMIDI_NAMESPACE::BleMidiInterfaceClient Name;
|
|
||||||
|
|
||||||
|
|
||||||
/*! \brief
|
|
||||||
*/
|
|
||||||
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
|
||||||
BLEMIDI_CREATE_INSTANCE(bm);
|
|
||||||
|
|
||||||
END_BLEMIDI_NAMESPACE
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "BleMidi_Namespace.h"
|
|
||||||
|
|
||||||
//#define DEBUG
|
|
||||||
#define RELEASE
|
|
||||||
|
|
||||||
#if defined(RELEASE)
|
|
||||||
#define RELEASE_BUILD
|
|
||||||
#undef DEBUG_BUILD
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(DEBUG)
|
|
||||||
#define DEBUG_BUILD
|
|
||||||
#undef RELEASE_BUILD
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(RELEASE_BUILD)
|
|
||||||
#undef BLEMIDI_DEBUG
|
|
||||||
#undef BLEMIDI_DEBUG_VERBOSE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(DEBUG_BUILD)
|
|
||||||
#define BLEMIDI_DEBUG 1
|
|
||||||
#undef BLEMIDI_DEBUG_VERBOSE
|
|
||||||
#define BLEMIDI_DEBUG_PARSING
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue