copied BLE MIDI copy from Pedalino (alf45tar)
Copied all BLE MIDI related code from Pedalino (alf45tar) and pasted here. Thank you alf45tar!
This commit is contained in:
parent
992e7e0420
commit
b92377bd5c
|
|
@ -21,7 +21,10 @@ void setup()
|
||||||
bm.onConnected(OnBleMidiConnected);
|
bm.onConnected(OnBleMidiConnected);
|
||||||
bm.onDisconnected(OnBleMidiDisconnected);
|
bm.onDisconnected(OnBleMidiDisconnected);
|
||||||
|
|
||||||
Serial.print(F("Getting IP address..."));
|
bm.OnReceiveNoteOn(OnBleMidiNoteOn);
|
||||||
|
|
||||||
|
|
||||||
|
Serial.println(F("looping"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
@ -41,7 +44,7 @@ void loop()
|
||||||
// rtpMIDI session. Device connected
|
// rtpMIDI session. Device connected
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
void OnBleMidiConnected() {
|
void OnBleMidiConnected() {
|
||||||
Serial.print(F("Connected"));
|
Serial.println(F("Connected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
@ -50,3 +53,16 @@ void OnBleMidiConnected() {
|
||||||
void OnBleMidiDisconnected() {
|
void OnBleMidiDisconnected() {
|
||||||
Serial.println(F("Disconnected"));
|
Serial.println(F("Disconnected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// rtpMIDI session. Device disconnected
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
483
src/Ble_esp32.h
483
src/Ble_esp32.h
|
|
@ -5,6 +5,8 @@
|
||||||
#include <BLEUtils.h>
|
#include <BLEUtils.h>
|
||||||
#include <BLEServer.h>
|
#include <BLEServer.h>
|
||||||
|
|
||||||
|
#include "utility/MIDI_Defs.h"
|
||||||
|
|
||||||
BEGIN_BLEMIDI_NAMESPACE
|
BEGIN_BLEMIDI_NAMESPACE
|
||||||
|
|
||||||
class BleMidiInterface
|
class BleMidiInterface
|
||||||
|
|
@ -18,111 +20,311 @@ protected:
|
||||||
bool _connected;
|
bool _connected;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// callbacks
|
||||||
void(*mConnectedCallback)();
|
void(*mConnectedCallback)();
|
||||||
void(*mDisconnectedCallback)();
|
void(*mDisconnectedCallback)();
|
||||||
|
|
||||||
|
void (*mNoteOffCallback)(byte channel, byte note, byte velocity);
|
||||||
|
void (*mNoteOnCallback)(byte channel, byte note, byte velocity);
|
||||||
|
void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity);
|
||||||
|
void (*mControlChangeCallback)(byte channel, byte, byte);
|
||||||
|
void (*mProgramChangeCallback)(byte channel, byte);
|
||||||
|
void (*mAfterTouchChannelCallback)(byte channel, byte);
|
||||||
|
void (*mPitchBendCallback)(byte channel, int);
|
||||||
|
void (*mSongPositionCallback)(unsigned short beats);
|
||||||
|
void (*mSongSelectCallback)(byte songnumber);
|
||||||
|
void (*mTuneRequestCallback)(void);
|
||||||
|
void (*mTimeCodeQuarterFrameCallback)(byte data);
|
||||||
|
void (*mSysExCallback)(const byte* array, uint16_t size);
|
||||||
|
void (*mClockCallback)(void);
|
||||||
|
void (*mStartCallback)(void);
|
||||||
|
void (*mContinueCallback)(void);
|
||||||
|
void (*mStopCallback)(void);
|
||||||
|
void (*mActiveSensingCallback)(void);
|
||||||
|
void (*mResetCallback)(void);
|
||||||
|
// end callback
|
||||||
|
|
||||||
|
protected:
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
unsigned long currentTimeStamp = millis() & 0x01FFF;
|
||||||
|
|
||||||
|
*header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB
|
||||||
|
*timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendChannelMessage1(byte type, byte channel, byte data1)
|
||||||
|
{
|
||||||
|
uint8_t midiPacket[4];
|
||||||
|
|
||||||
|
getMidiTimestamp(&midiPacket[0], &midiPacket[1]);
|
||||||
|
midiPacket[2] = (type & 0xf0) | ((channel - 1) & 0x0f);
|
||||||
|
midiPacket[3] = data1;
|
||||||
|
pCharacteristic->setValue(midiPacket, 4);
|
||||||
|
pCharacteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendChannelMessage2(byte type, byte channel, byte data1, byte data2)
|
||||||
|
{
|
||||||
|
uint8_t midiPacket[5];
|
||||||
|
|
||||||
|
getMidiTimestamp(&midiPacket[0], &midiPacket[1]);
|
||||||
|
midiPacket[2] = (type & 0xf0) | ((channel - 1) & 0x0f);
|
||||||
|
midiPacket[3] = data1;
|
||||||
|
midiPacket[4] = data2;
|
||||||
|
pCharacteristic->setValue(midiPacket, 5);
|
||||||
|
pCharacteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSystemCommonMessage1(byte type, byte data1)
|
||||||
|
{
|
||||||
|
uint8_t midiPacket[4];
|
||||||
|
|
||||||
|
getMidiTimestamp(&midiPacket[0], &midiPacket[1]);
|
||||||
|
midiPacket[2] = type;
|
||||||
|
midiPacket[3] = data1;
|
||||||
|
pCharacteristic->setValue(midiPacket, 4);
|
||||||
|
pCharacteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSystemCommonMessage2(byte type, byte data1, byte data2)
|
||||||
|
{
|
||||||
|
uint8_t midiPacket[5];
|
||||||
|
|
||||||
|
getMidiTimestamp(&midiPacket[0], &midiPacket[1]);
|
||||||
|
midiPacket[2] = type;
|
||||||
|
midiPacket[3] = data1;
|
||||||
|
midiPacket[4] = data2;
|
||||||
|
pCharacteristic->setValue(midiPacket, 5);
|
||||||
|
pCharacteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRealTimeMessage(byte type)
|
||||||
|
{
|
||||||
|
uint8_t midiPacket[3];
|
||||||
|
|
||||||
|
getMidiTimestamp(&midiPacket[0], &midiPacket[1]);
|
||||||
|
midiPacket[2] = type;
|
||||||
|
pCharacteristic->setValue(midiPacket, 3);
|
||||||
|
pCharacteristic->notify();
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline BleMidiInterface()
|
BleMidiInterface()
|
||||||
{
|
{
|
||||||
mConnectedCallback = NULL;
|
mConnectedCallback = NULL;
|
||||||
mDisconnectedCallback = NULL;
|
mDisconnectedCallback = NULL;
|
||||||
|
|
||||||
|
mNoteOffCallback = NULL;
|
||||||
|
mNoteOnCallback = NULL;
|
||||||
|
mAfterTouchPolyCallback = NULL;
|
||||||
|
mControlChangeCallback = NULL;
|
||||||
|
mProgramChangeCallback = NULL;
|
||||||
|
mAfterTouchChannelCallback = NULL;
|
||||||
|
mPitchBendCallback = NULL;
|
||||||
|
mSysExCallback = NULL;
|
||||||
|
mTimeCodeQuarterFrameCallback = NULL;
|
||||||
|
mSongPositionCallback = NULL;
|
||||||
|
mSongSelectCallback = NULL;
|
||||||
|
mTuneRequestCallback = NULL;
|
||||||
|
mClockCallback = NULL;
|
||||||
|
mStartCallback = NULL;
|
||||||
|
mContinueCallback = NULL;
|
||||||
|
mStopCallback = NULL;
|
||||||
|
mActiveSensingCallback = NULL;
|
||||||
|
mResetCallback = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ~BleMidiInterface()
|
~BleMidiInterface()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool begin(const char* deviceName);
|
inline bool begin(const char* deviceName);
|
||||||
|
|
||||||
inline void sendNoteOn(DataByte note, DataByte velocity, Channel channel) {
|
inline void receive(uint8_t *buffer, uint8_t bufferSize);
|
||||||
|
|
||||||
if (!_connected) return;
|
void sendNoteOn(DataByte note, DataByte velocity, Channel channel) {
|
||||||
if (pCharacteristic == NULL) return;
|
sendChannelMessage2(Type::NoteOn, channel, note, velocity);
|
||||||
|
|
||||||
uint8_t midiPacket[] = {
|
|
||||||
0x80, // header
|
|
||||||
0x80, // timestamp, not implemented
|
|
||||||
0x00, // status
|
|
||||||
0x3c, // 0x3c == 60 == middle c
|
|
||||||
0x00 // velocity
|
|
||||||
};
|
|
||||||
|
|
||||||
midiPacket[2] = note; // note, channel 0
|
|
||||||
midiPacket[4] = velocity; // velocity
|
|
||||||
|
|
||||||
pCharacteristic->setValue(midiPacket, 5); // packet, length in bytes
|
|
||||||
pCharacteristic->notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void sendNoteOff(DataByte note, DataByte velocity, Channel channel) {
|
void sendNoteOff(DataByte note, DataByte velocity, Channel channel) {
|
||||||
|
sendChannelMessage2(Type::NoteOff, channel, note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void sendProgramChange(DataByte inProgramNumber, Channel inChannel) {
|
void sendProgramChange(DataByte number, Channel channel) {
|
||||||
|
sendChannelMessage1(Type::ProgramChange, channel, number);
|
||||||
}
|
|
||||||
inline void sendControlChange(DataByte inControlNumber, DataByte inControlValue, Channel inChannel) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendPitchBend(int inPitchValue, Channel inChannel) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendPitchBend(double inPitchValue, Channel inChannel) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendPolyPressure(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendAfterTouch(DataByte inPressure, Channel inChannel) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendSysEx(const byte*, uint16_t inLength) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendTimeCodeQuarterFrame(DataByte inData) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendSongPosition(unsigned short inBeats) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendSongSelect(DataByte inSongNumber) {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendTuneRequest() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendActiveSensing() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendStart() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendContinue() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendStop() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendReset() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendClock() {
|
|
||||||
|
|
||||||
}
|
|
||||||
inline void sendTick() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void onConnected(void(*fptr)()) {
|
void sendControlChange(DataByte number, DataByte value, Channel channel) {
|
||||||
|
sendChannelMessage2(Type::ControlChange, channel, number, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPitchBend(int value, Channel channel) {
|
||||||
|
sendChannelMessage1(Type::PitchBend, channel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPitchBend(double pitchValue, Channel channel) {
|
||||||
|
// sendChannelMessage1(Type::PitchBend, channel, bend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPolyPressure(DataByte noteNumber, DataByte pressure, Channel channel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendAfterTouch(DataByte pressure, Channel channel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSysEx(const byte*, uint16_t inLength) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendTimeCodeQuarterFrame(DataByte typeNibble, DataByte valuesNibble) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendTimeCodeQuarterFrame(DataByte data) {
|
||||||
|
sendSystemCommonMessage1(Type::TimeCodeQuarterFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSongPosition(unsigned short beats) {
|
||||||
|
sendSystemCommonMessage2(Type::SongPosition, beats >> 4, beats & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSongSelect(DataByte number) {
|
||||||
|
sendSystemCommonMessage1(Type::SongSelect, number);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendTuneRequest() {
|
||||||
|
sendRealTimeMessage(Type::TuneRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendActiveSensing() {
|
||||||
|
sendRealTimeMessage(Type::ActiveSensing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendStart() {
|
||||||
|
sendRealTimeMessage(Type::Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendContinue() {
|
||||||
|
sendRealTimeMessage(Type::Continue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendStop() {
|
||||||
|
sendRealTimeMessage(Type::Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendReset() {
|
||||||
|
sendRealTimeMessage(Type::Reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendClock() {
|
||||||
|
sendRealTimeMessage(Type::Clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendTick() {
|
||||||
|
sendRealTimeMessage(Type::Tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onConnected(void(*fptr)()) {
|
||||||
_connected = true;
|
_connected = true;
|
||||||
mConnectedCallback = fptr;
|
mConnectedCallback = fptr;
|
||||||
}
|
}
|
||||||
inline void onDisconnected(void(*fptr)()) {
|
void onDisconnected(void(*fptr)()) {
|
||||||
_connected = false;
|
_connected = false;
|
||||||
mDisconnectedCallback = fptr;
|
mDisconnectedCallback = fptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnReceiveNoteOn(void (*fptr)(byte channel, byte note, byte velocity)){
|
||||||
|
mNoteOnCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveNoteOff(void (*fptr)(byte channel, byte note, byte velocity)){
|
||||||
|
mNoteOffCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)){
|
||||||
|
mAfterTouchPolyCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveControlChange(void (*fptr)(byte channel, byte number, byte value)){
|
||||||
|
mControlChangeCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveProgramChange(void (*fptr)(byte channel, byte number)){
|
||||||
|
mProgramChangeCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveAfterTouchChannel(void (*fptr)(byte channel, byte pressure)){
|
||||||
|
mAfterTouchChannelCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceivePitchBend(void (*fptr)(byte channel, int bend)){
|
||||||
|
mPitchBendCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveSysEx(void (*fptr)(const byte * data, uint16_t size)){
|
||||||
|
mSysExCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveTimeCodeQuarterFrame(void (*fptr)(byte data)){
|
||||||
|
mTimeCodeQuarterFrameCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveSongPosition(void (*fptr)(unsigned short beats)){
|
||||||
|
mSongPositionCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveSongSelect(void (*fptr)(byte songnumber)){
|
||||||
|
mSongSelectCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveTuneRequest(void (*fptr)(void)){
|
||||||
|
mTuneRequestCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveClock(void (*fptr)(void)){
|
||||||
|
mClockCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveStart(void (*fptr)(void)){
|
||||||
|
mStartCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveContinue(void (*fptr)(void)){
|
||||||
|
mContinueCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveStop(void (*fptr)(void)){
|
||||||
|
mStopCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveActiveSensing(void (*fptr)(void)){
|
||||||
|
mActiveSensingCallback = fptr;
|
||||||
|
}
|
||||||
|
void OnReceiveReset(void (*fptr)(void)){
|
||||||
|
mResetCallback = fptr;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class MyServerCallbacks: public BLEServerCallbacks {
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
|
|
@ -134,16 +336,29 @@ protected:
|
||||||
BleMidiInterface* _bleMidiInterface;
|
BleMidiInterface* _bleMidiInterface;
|
||||||
|
|
||||||
void onConnect(BLEServer* pServer) {
|
void onConnect(BLEServer* pServer) {
|
||||||
_bleMidiInterface->mConnectedCallback();
|
if (_bleMidiInterface->mConnectedCallback)
|
||||||
|
_bleMidiInterface->mConnectedCallback();
|
||||||
};
|
};
|
||||||
|
|
||||||
void onDisconnect(BLEServer* pServer) {
|
void onDisconnect(BLEServer* pServer) {
|
||||||
_bleMidiInterface->mDisconnectedCallback();
|
if (_bleMidiInterface->mDisconnectedCallback)
|
||||||
|
_bleMidiInterface->mDisconnectedCallback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MyCallbacks: public BLECharacteristicCallbacks {
|
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
|
||||||
|
public:
|
||||||
|
MyCharacteristicCallbacks(BleMidiInterface* bleMidiInterface) {
|
||||||
|
_bleMidiInterface = bleMidiInterface;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
BleMidiInterface* _bleMidiInterface;
|
||||||
|
|
||||||
void onWrite(BLECharacteristic *pCharacteristic) {
|
void onWrite(BLECharacteristic *pCharacteristic) {
|
||||||
|
std::string rxValue = pCharacteristic->getValue();
|
||||||
|
if (rxValue.length() > 0) {
|
||||||
|
_bleMidiInterface->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -165,7 +380,7 @@ bool BleMidiInterface::begin(const char* deviceName)
|
||||||
BLECharacteristic::PROPERTY_NOTIFY |
|
BLECharacteristic::PROPERTY_NOTIFY |
|
||||||
BLECharacteristic::PROPERTY_WRITE_NR
|
BLECharacteristic::PROPERTY_WRITE_NR
|
||||||
);
|
);
|
||||||
pCharacteristic->setCallbacks(new MyCallbacks());
|
pCharacteristic->setCallbacks(new MyCharacteristicCallbacks(this));
|
||||||
// Start the service
|
// Start the service
|
||||||
pService->start();
|
pService->start();
|
||||||
|
|
||||||
|
|
@ -182,4 +397,108 @@ bool BleMidiInterface::begin(const char* deviceName)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
Channel channel;
|
||||||
|
Type command;
|
||||||
|
|
||||||
|
//Pointers used to search through payload.
|
||||||
|
uint8_t lPtr = 0;
|
||||||
|
uint8_t rPtr = 0;
|
||||||
|
//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 used to capture runningStatus
|
||||||
|
uint8_t lastStatus = buffer[lPtr];
|
||||||
|
if ( (buffer[lPtr] < 0x80) ) {
|
||||||
|
//Status message not present, bail
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command = getTypeFromStatusByte(lastStatus);
|
||||||
|
channel = getChannelFromStatusByte(lastStatus);
|
||||||
|
|
||||||
|
//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
|
||||||
|
// MIDI.send(command, 0, 0, channel);
|
||||||
|
} else if ( rPtr - lPtr < 2 ) {
|
||||||
|
// MIDI.send(command, buffer[lPtr + 1], 0, channel);
|
||||||
|
} else if ( rPtr - lPtr < 3 ) {
|
||||||
|
|
||||||
|
// TODO: switch for type
|
||||||
|
|
||||||
|
if (mNoteOnCallback)
|
||||||
|
mNoteOnCallback(0, 1, 2);
|
||||||
|
|
||||||
|
// MIDI.send(command, buffer[lPtr + 1], buffer[lPtr + 2], channel);
|
||||||
|
} 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 (int i = lPtr; i < rPtr; i = i + 2) {
|
||||||
|
// MIDI.send(command, buffer[i + 1], buffer[i + 2], channel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xC0:
|
||||||
|
case 0xD0:
|
||||||
|
for (int i = lPtr; i < rPtr; i = i + 1) {
|
||||||
|
// MIDI.send(command, buffer[i + 1], 0, channel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Point to next status
|
||||||
|
lPtr = rPtr + 2;
|
||||||
|
if (lPtr >= bufferSize) {
|
||||||
|
//end of packet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
END_BLEMIDI_NAMESPACE
|
END_BLEMIDI_NAMESPACE
|
||||||
|
|
|
||||||
|
|
@ -162,4 +162,47 @@
|
||||||
PolyModeOn = 127
|
PolyModeOn = 127
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*! \brief Extract an enumerated MIDI type from a status byte
|
||||||
|
*/
|
||||||
|
static Type getTypeFromStatusByte(byte status)
|
||||||
|
{
|
||||||
|
if ((status < 0x80) ||
|
||||||
|
(status == 0xf4) ||
|
||||||
|
(status == 0xf5) ||
|
||||||
|
(status == 0xf9) ||
|
||||||
|
(status == 0xfD))
|
||||||
|
{
|
||||||
|
// Data bytes and undefined.
|
||||||
|
return InvalidType;
|
||||||
|
}
|
||||||
|
if (status < 0xf0)
|
||||||
|
{
|
||||||
|
// Channel message, remove channel nibble.
|
||||||
|
return Type(status & 0xf0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Type(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Returns channel in the range 1-16
|
||||||
|
*/
|
||||||
|
static Channel getChannelFromStatusByte(byte status)
|
||||||
|
{
|
||||||
|
return Channel((status & 0x0f) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief check if channel is in the range 1-16
|
||||||
|
*/
|
||||||
|
static bool isChannelMessage(Type type)
|
||||||
|
{
|
||||||
|
return (type == NoteOff ||
|
||||||
|
type == NoteOn ||
|
||||||
|
type == ControlChange ||
|
||||||
|
type == AfterTouchPoly ||
|
||||||
|
type == AfterTouchChannel ||
|
||||||
|
type == PitchBend ||
|
||||||
|
type == ProgramChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//}
|
//}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue