initial upload
This commit is contained in:
parent
01edf0c6be
commit
253f3e4b51
|
|
@ -1,6 +1,11 @@
|
||||||
#include "BleMidi.h"
|
#include <MIDI.h>
|
||||||
|
|
||||||
BLEMIDI_CREATE_INSTANCE(bm);
|
#include <midi_bleTransport.h>
|
||||||
|
#include <Ble_esp32.h>
|
||||||
|
|
||||||
|
bleMidi::BluetoothEsp32 sBluetoothEsp32;
|
||||||
|
bleMidi::BleMidiTransport<bleMidi::BluetoothEsp32> bm((bleMidi::BluetoothEsp32&) sBluetoothEsp32);
|
||||||
|
midi::MidiInterface<bleMidi::BleMidiTransport<bleMidi::BluetoothEsp32>> MIDI((bleMidi::BleMidiTransport<bleMidi::BluetoothEsp32>&)bm);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
|
|
@ -9,17 +14,15 @@ void setup()
|
||||||
{
|
{
|
||||||
// Serial communications and wait for port to open:
|
// Serial communications and wait for port to open:
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
while (!Serial) {
|
while (!Serial);
|
||||||
; // wait for serial port to connect. Needed for Leonardo only
|
|
||||||
}
|
|
||||||
|
|
||||||
bm.begin("hehe");
|
MIDI.begin("hihi", 1);
|
||||||
|
|
||||||
bm.onConnected(OnBleMidiConnected);
|
bm.onConnected(OnBleMidiConnected);
|
||||||
bm.onDisconnected(OnBleMidiDisconnected);
|
bm.onDisconnected(OnBleMidiDisconnected);
|
||||||
|
|
||||||
bm.setHandleNoteOn(OnBleMidiNoteOn);
|
MIDI.setHandleNoteOn(OnBleMidiNoteOn);
|
||||||
bm.setHandleNoteOff(OnBleMidiNoteOff);
|
MIDI.setHandleNoteOff(OnBleMidiNoteOff);
|
||||||
|
|
||||||
Serial.println(F("looping"));
|
Serial.println(F("looping"));
|
||||||
}
|
}
|
||||||
|
|
@ -29,8 +32,10 @@ void setup()
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
bm.sendNoteOn(60, 127, 1); // note 60, velocity 127 on channel 1
|
MIDI.read();
|
||||||
bm.sendNoteOff(60, 127, 1);
|
|
||||||
|
MIDI.sendNoteOn(60, 127, 1); // note 60, velocity 127 on channel 1
|
||||||
|
MIDI.sendNoteOff(60, 127, 1);
|
||||||
|
|
||||||
delay(1000);
|
delay(1000);
|
||||||
}
|
}
|
||||||
|
|
@ -78,4 +83,4 @@ void OnBleMidiNoteOff(byte channel, byte note, byte velocity) {
|
||||||
Serial.print(F(" velocity:"));
|
Serial.print(F(" velocity:"));
|
||||||
Serial.print(velocity);
|
Serial.print(velocity);
|
||||||
Serial.println();
|
Serial.println();
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
#######################################
|
#######################################
|
||||||
# Syntax Coloring Map for AppleMIDI
|
# Syntax Coloring Map for BLEMIDI
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Datatypes (KEYWORD1)
|
# Datatypes (KEYWORD1)
|
||||||
#######################################
|
#######################################
|
||||||
BLEMIDI KEYWORD1
|
midi_bleTransport KEYWORD1
|
||||||
BLEMIDI.h KEYWORD1
|
midi_bleTransport.h KEYWORD1
|
||||||
|
Ble_esp32.h KEYWORD1
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Methods and Functions (KEYWORD2)
|
# Methods and Functions (KEYWORD2)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
name=BLE-MIDI
|
name=MIDI BLE Library
|
||||||
version=0.0.1
|
version=2.0.0
|
||||||
author=lathoub
|
author=lathoub
|
||||||
maintainer=lathoub <lathoub@gmail.com>
|
maintainer=lathoub
|
||||||
sentence=MIDI over Bluetooth Low Energy (BLE-MIDI) 1.0 for Arduino
|
sentence=BLE MIDI I/Os for Arduino
|
||||||
paragraph=MIDI over Bluetooth Low Energy
|
paragraph=Read & send MIDI messages over BLE
|
||||||
category=Communication
|
category=Communication
|
||||||
url=https://github.com/lathoub/Arduino-BLE-MIDI
|
url=
|
||||||
architectures=*
|
architectures=*
|
||||||
includes=BLEMIDI.h
|
includes=midi_bleTransport.h
|
||||||
|
dependency=https://github.com/FortySevenEffects/arduino_midi_library
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#include "BleMidi.h"
|
|
||||||
|
|
@ -1,15 +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"
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,395 +1,137 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Headers for ESP32 BLE
|
// Headers for ESP32 BLE
|
||||||
#include <BLEDevice.h>
|
#include <BLEDevice.h>
|
||||||
#include <BLEUtils.h>
|
#include <BLEUtils.h>
|
||||||
#include <BLEServer.h>
|
#include <BLEServer.h>
|
||||||
#include <BLE2902.h>
|
#include <BLE2902.h>
|
||||||
|
|
||||||
#include "common/midiCommon.h"
|
#define SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700"
|
||||||
using namespace Midi;
|
#define CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3"
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
class BleMidiInterface : public MidiCommonInterface
|
class BluetoothEsp32
|
||||||
{
|
{
|
||||||
protected:
|
private:
|
||||||
// ESP32
|
BLEServer* _server;
|
||||||
BLEServer * _server;
|
BLEAdvertising* _advertising;
|
||||||
BLEAdvertising * _advertising;
|
BLECharacteristic* _characteristic;
|
||||||
BLECharacteristic *_characteristic;
|
|
||||||
|
BleMidiTransport<class BluetoothEsp32>* _bleMidiTransport;
|
||||||
bool _connected;
|
|
||||||
|
public:
|
||||||
uint8_t _midiPacket[5]; // outgoing
|
BluetoothEsp32()
|
||||||
|
: _server(NULL),
|
||||||
public:
|
_advertising(NULL),
|
||||||
// callbacks
|
_characteristic(NULL),
|
||||||
void(*_connectedCallback)() = NULL;
|
_bleMidiTransport(NULL)
|
||||||
void(*_disconnectedCallback)() = NULL;
|
{
|
||||||
|
}
|
||||||
protected:
|
|
||||||
inline static void getMidiTimestamp (uint8_t *header, uint8_t *timestamp)
|
~BluetoothEsp32()
|
||||||
{
|
{
|
||||||
/*
|
}
|
||||||
The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages.
|
|
||||||
Header Byte
|
bool begin(const char*, BleMidiTransport<class BluetoothEsp32>*);
|
||||||
bit 7 Set to 1.
|
|
||||||
bit 6 Set to 0. (Reserved for future use)
|
inline void write(uint8_t* data, uint8_t length)
|
||||||
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
|
_characteristic->setValue(data, length);
|
||||||
packet. The remaining 7 bits of timing information for individual MIDI messages encoded in a
|
_characteristic->notify();
|
||||||
packet is expressed by timestamp bytes.
|
}
|
||||||
Timestamp Byte
|
|
||||||
bit 7 Set to 1.
|
inline void receive(uint8_t* buffer, uint8_t length)
|
||||||
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
|
_bleMidiTransport->receive(buffer, length);
|
||||||
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.
|
inline void connected()
|
||||||
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
|
if (_bleMidiTransport->_connectedCallback)
|
||||||
MIDI message in the same packet overflow/wrap (i.e., the timestampLow is smaller than a
|
_bleMidiTransport->_connectedCallback();
|
||||||
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).
|
inline void disconnected()
|
||||||
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
|
if (_bleMidiTransport->_disconnectedCallback)
|
||||||
per BLE packet.
|
_bleMidiTransport->_disconnectedCallback();
|
||||||
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.
|
|
||||||
*/
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
/*
|
public:
|
||||||
Calculating a Timestamp
|
MyServerCallbacks(BluetoothEsp32* bluetoothEsp32)
|
||||||
To calculate the timestamp, the built-in millis() is used.
|
: _bluetoothEsp32(bluetoothEsp32) {
|
||||||
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,
|
protected:
|
||||||
and the MSB of both bytes are set to indicate that this is a header byte.
|
BluetoothEsp32* _bluetoothEsp32;
|
||||||
Both bytes are placed into the first two position of an array in preparation for a MIDI message.
|
|
||||||
*/
|
void onConnect(BLEServer* server) {
|
||||||
auto currentTimeStamp = millis() & 0x01FFF;
|
_bluetoothEsp32->connected();
|
||||||
|
};
|
||||||
*header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB
|
|
||||||
*timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB
|
void onDisconnect(BLEServer* server) {
|
||||||
}
|
_bluetoothEsp32->disconnected();
|
||||||
|
}
|
||||||
|
};
|
||||||
// serialize towards hardware
|
|
||||||
|
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
|
||||||
void write(DataByte b1)
|
public:
|
||||||
{
|
MyCharacteristicCallbacks(BluetoothEsp32* bluetoothEsp32)
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
: _bluetoothEsp32(bluetoothEsp32 ) {
|
||||||
|
}
|
||||||
_midiPacket[2] = b1;
|
|
||||||
|
protected:
|
||||||
// TODO: quid running status
|
BluetoothEsp32* _bluetoothEsp32;
|
||||||
|
|
||||||
_characteristic->setValue(_midiPacket, 3);
|
void onWrite(BLECharacteristic * characteristic) {
|
||||||
_characteristic->notify();
|
std::string rxValue = characteristic->getValue();
|
||||||
};
|
if (rxValue.length() > 0) {
|
||||||
|
_bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
|
||||||
void write(DataByte b1, DataByte b2)
|
}
|
||||||
{
|
}
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
};
|
||||||
|
|
||||||
_midiPacket[2] = b1;
|
bool BluetoothEsp32::begin(const char* deviceName, BleMidiTransport<class BluetoothEsp32>* bleMidiTransport)
|
||||||
_midiPacket[3] = b2;
|
{
|
||||||
|
_bleMidiTransport = bleMidiTransport;
|
||||||
// TODO: quid running status
|
|
||||||
|
BLEDevice::init(deviceName);
|
||||||
_characteristic->setValue(_midiPacket, 4);
|
|
||||||
_characteristic->notify();
|
_server = BLEDevice::createServer();
|
||||||
};
|
_server->setCallbacks(new MyServerCallbacks(this));
|
||||||
|
|
||||||
void write(DataByte b1, DataByte b2, DataByte b3)
|
// Create the BLE Service
|
||||||
{
|
auto service = _server->createService(BLEUUID(SERVICE_UUID));
|
||||||
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
|
||||||
|
// Create a BLE Characteristic
|
||||||
_midiPacket[2] = b1;
|
_characteristic = service->createCharacteristic(
|
||||||
_midiPacket[3] = b2;
|
BLEUUID(CHARACTERISTIC_UUID),
|
||||||
_midiPacket[4] = b3;
|
BLECharacteristic::PROPERTY_READ |
|
||||||
|
BLECharacteristic::PROPERTY_WRITE |
|
||||||
// TODO: quid running status
|
BLECharacteristic::PROPERTY_NOTIFY |
|
||||||
|
BLECharacteristic::PROPERTY_WRITE_NR
|
||||||
_characteristic->setValue(_midiPacket, 5);
|
);
|
||||||
_characteristic->notify();
|
// Add CCCD 0x2902 to allow notify
|
||||||
};
|
_characteristic->addDescriptor(new BLE2902());
|
||||||
|
|
||||||
|
_characteristic->setCallbacks(new MyCharacteristicCallbacks(this));
|
||||||
public:
|
// Start the service
|
||||||
BleMidiInterface()
|
service->start();
|
||||||
{
|
|
||||||
}
|
auto advertisementData = BLEAdvertisementData();
|
||||||
|
advertisementData.setFlags(0x04);
|
||||||
~BleMidiInterface()
|
advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID));
|
||||||
{
|
advertisementData.setName(deviceName);
|
||||||
}
|
|
||||||
|
// Start advertising
|
||||||
// TODO why must these functions be inline??
|
_advertising = _server->getAdvertising();
|
||||||
|
_advertising->setAdvertisementData(advertisementData);
|
||||||
inline bool begin(const char* deviceName);
|
_advertising->start();
|
||||||
|
|
||||||
inline void read()
|
return true;
|
||||||
{
|
}
|
||||||
// n/a no need to call read() in loop, as incoming data comes in async via onWrite
|
|
||||||
}
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
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 @@
|
||||||
|
#include "midi_bleTransport.h"
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*!
|
||||||
|
* @file midi_bleTransport.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utility/midi_bleSettings.h"
|
||||||
|
#include "utility/midi_bleDefs.h"
|
||||||
|
|
||||||
|
#include <midi_RingBuffer.h>
|
||||||
|
|
||||||
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
|
template<class BleClass>
|
||||||
|
class BleMidiTransport
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint8_t _midiPacket[5]; // outgoing
|
||||||
|
|
||||||
|
typedef midi::RingBuffer<byte, 44> TxBuffer;
|
||||||
|
typedef midi::RingBuffer<byte, 44> RxBuffer;
|
||||||
|
TxBuffer mTxBuffer;
|
||||||
|
RxBuffer mRxBuffer;
|
||||||
|
|
||||||
|
bool _connected;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BleClass& mBleClass;
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline BleMidiTransport(BleClass& inBleClass)
|
||||||
|
: mBleClass(inBleClass)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ~BleMidiTransport() {}
|
||||||
|
|
||||||
|
inline bool begin(int baudrate) {} // n/a
|
||||||
|
|
||||||
|
inline bool begin(const char* deviceName)
|
||||||
|
{
|
||||||
|
return mBleClass.begin(deviceName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned available() { return mRxBuffer.getLength(); }
|
||||||
|
inline byte read() { return mRxBuffer.read(); }
|
||||||
|
inline void beginWrite() { mTxBuffer.clear(); }
|
||||||
|
inline void write(byte inData) { mTxBuffer.write(inData); }
|
||||||
|
inline void endWrite()
|
||||||
|
{
|
||||||
|
getMidiTimestamp(&_midiPacket[0], &_midiPacket[1]);
|
||||||
|
|
||||||
|
mTxBuffer.read(_midiPacket + 2, mTxBuffer.getLength());
|
||||||
|
|
||||||
|
mBleClass.write((uint8_t*)_midiPacket, mTxBuffer.getLength() + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void receive(uint8_t* buffer, uint8_t length)
|
||||||
|
{
|
||||||
|
mRxBuffer.read(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
inline 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
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// callbacks
|
||||||
|
void(*_connectedCallback)() = NULL;
|
||||||
|
void(*_disconnectedCallback)() = NULL;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void onConnected(void(*fptr)()) {
|
||||||
|
_connected = true;
|
||||||
|
_connectedCallback = fptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDisconnected(void(*fptr)()) {
|
||||||
|
_connected = false;
|
||||||
|
_disconnectedCallback = fptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
END_BLEMIDI_NAMESPACE
|
||||||
Binary file not shown.
|
|
@ -1,25 +1,25 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "BleMidi_Namespace.h"
|
#include "midi_bleNamespace.h"
|
||||||
|
|
||||||
#if ARDUINO
|
#if ARDUINO
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#else
|
#else
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
typedef uint8_t byte;
|
typedef uint8_t byte;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
/*! \brief Create an instance of the library
|
/*! \brief Create an instance of the library
|
||||||
*/
|
*/
|
||||||
#define BLEMIDI_CREATE_INSTANCE(Name) \
|
#define BLEMIDI_CREATE_INSTANCE(Type, Name) \
|
||||||
BLEMIDI_NAMESPACE::BleMidiInterface Name;
|
BLEMIDI_NAMESPACE::BleMidiTransport<Type> Name((Type&)SerialPort);
|
||||||
|
|
||||||
|
|
||||||
/*! \brief
|
/*! \brief
|
||||||
*/
|
*/
|
||||||
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
|
||||||
BLEMIDI_CREATE_INSTANCE(bm);
|
BLEMIDI_CREATE_INSTANCE(bm);
|
||||||
|
|
||||||
END_BLEMIDI_NAMESPACE
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define BLEMIDI_NAMESPACE bleMidi
|
#define BLEMIDI_NAMESPACE bleMidi
|
||||||
#define BEGIN_BLEMIDI_NAMESPACE namespace BLEMIDI_NAMESPACE {
|
#define BEGIN_BLEMIDI_NAMESPACE namespace BLEMIDI_NAMESPACE {
|
||||||
#define END_BLEMIDI_NAMESPACE }
|
#define END_BLEMIDI_NAMESPACE }
|
||||||
|
|
||||||
#define USING_NAMESPACE_BLEMIDI using namespace BLEMIDI_NAMESPACE;
|
#define USING_NAMESPACE_BLEMIDI using namespace BLEMIDI_NAMESPACE;
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
END_BLEMIDI_NAMESPACE
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "BleMidi_Namespace.h"
|
#include "midi_bleNamespace.h"
|
||||||
|
|
||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
#define RELEASE
|
#define RELEASE
|
||||||
|
|
||||||
#if defined(RELEASE)
|
#if defined(RELEASE)
|
||||||
#define RELEASE_BUILD
|
#define RELEASE_BUILD
|
||||||
#undef DEBUG_BUILD
|
#undef DEBUG_BUILD
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
#define DEBUG_BUILD
|
#define DEBUG_BUILD
|
||||||
#undef RELEASE_BUILD
|
#undef RELEASE_BUILD
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined(RELEASE_BUILD)
|
#if defined(RELEASE_BUILD)
|
||||||
#undef BLEMIDI_DEBUG
|
#undef BLEMIDI_DEBUG
|
||||||
#undef BLEMIDI_DEBUG_VERBOSE
|
#undef BLEMIDI_DEBUG_VERBOSE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(DEBUG_BUILD)
|
#if defined(DEBUG_BUILD)
|
||||||
#define BLEMIDI_DEBUG 1
|
#define BLEMIDI_DEBUG 1
|
||||||
#undef BLEMIDI_DEBUG_VERBOSE
|
#undef BLEMIDI_DEBUG_VERBOSE
|
||||||
#define BLEMIDI_DEBUG_PARSING
|
#define BLEMIDI_DEBUG_PARSING
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Loading…
Reference in New Issue