diff --git a/src/BLEMIDI_Transport.h b/src/BLEMIDI_Transport.h index 63a61ec..329a265 100644 --- a/src/BLEMIDI_Transport.h +++ b/src/BLEMIDI_Transport.h @@ -12,7 +12,7 @@ BEGIN_BLEMIDI_NAMESPACE -template +template class BLEMIDI_Transport { typedef _Settings Settings; @@ -23,26 +23,26 @@ private: byte mTxBuffer[Settings::MaxBufferSize]; // minimum 5 bytes unsigned mTxIndex = 0; - + char mDeviceName[24]; uint8_t mTimestampLow; private: - T mBleClass; + T mBleClass; -public: - BLEMIDI_Transport(const char* deviceName) - { +public: + BLEMIDI_Transport(const char *deviceName) + { strncpy(mDeviceName, deviceName, sizeof(mDeviceName)); - + mRxIndex = 0; mTxIndex = 0; - } + } -public: +public: static const bool thruActivated = false; - + void begin() { mBleClass.begin(mDeviceName, this); @@ -53,10 +53,10 @@ public: getMidiTimestamp(&mTxBuffer[0], &mTxBuffer[1]); mTxIndex = 2; mTimestampLow = mTxBuffer[1]; // or generate new ? - + return true; } - + void write(byte inData) { if (mTxIndex >= sizeof(mTxBuffer)) @@ -76,8 +76,8 @@ public: { mBleClass.write(mTxBuffer, mTxIndex - 1); - mTxIndex = 1; // keep header - mTxBuffer[mTxIndex++] = mTimestampLow; // or generate new ? + mTxIndex = 1; // keep header + mTxBuffer[mTxIndex++] = mTimestampLow; // or generate new ? } else { @@ -89,23 +89,28 @@ public: mBleClass.write(mTxBuffer, mTxIndex); mTxIndex = 0; } - + byte read() { return mRxBuffer[--mRxIndex]; } + bool end() + { + return mBleClass.end(); + } + unsigned available() { uint8_t byte; auto success = mBleClass.available(&byte); - if (!success) return mRxIndex; + 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. @@ -152,31 +157,33 @@ protected: 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) + 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 + + *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) + + static void setMidiTimestamp(uint8_t header, uint8_t *timestamp) { } - -public: - // callbacks - void(*_connectedCallback)() = nullptr; - void(*_disconnectedCallback)() = nullptr; public: - void setHandleConnected(void(*fptr)()) { - _connectedCallback = fptr; - } + // callbacks + void (*_connectedCallback)() = nullptr; + void (*_disconnectedCallback)() = nullptr; - void setHandleDisconnected(void(*fptr)()) { - _disconnectedCallback = fptr; - } +public: + void setHandleConnected(void (*fptr)()) + { + _connectedCallback = fptr; + } + + void setHandleDisconnected(void (*fptr)()) + { + _disconnectedCallback = fptr; + } /* The general form of a MIDI message follows: @@ -208,83 +215,179 @@ public: 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) - { + void receive(byte *buffer, size_t length) + { // Pointers used to search through payload. - byte lPtr = 0; - byte rPtr = 0; + int lPtr = 0; + int 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" - + // previousStatus used to continue a runningStatus interrupted by a timeStamp or a System Message. + byte previousStatus = 0x00; + + byte headerByte = buffer[lPtr++]; + + auto timestampHigh = 0x3f & headerByte; + + byte timestampByte = buffer[lPtr++]; + uint16_t timestamp = 0; + + bool sysExContinuation = false; + bool runningStatusContinuation = false; + + if (timestampByte >= 80) + { + auto timestampLow = 0x7f & timestampByte; + timestamp = timestampLow + (timestampHigh << 7); + } + else + { + sysExContinuation = true; + lPtr--; // the second byte is part of the SysEx + } + //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 + + if (previousStatus == 0x00) + { + if ((lastStatus < 0x80) && !sysExContinuation) + return; // Status message not present and it is not a runningStatus continuation, bail + } + else if (lastStatus < 0x80) + { + lastStatus = previousStatus; + runningStatusContinuation = true; + } // Point to next non-data byte rPtr = lPtr; - while( (buffer[rPtr + 1] < 0x80) && (rPtr < (length - 1)) ) + 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) + if (!runningStatusContinuation) + { + // look at l and r pointers and decode by size. + if (rPtr - lPtr < 1) + { + // Time code or system + mBleClass.add(buffer[lPtr]); + } + else if (rPtr - lPtr < 2) + { + mBleClass.add(buffer[lPtr]); + mBleClass.add(buffer[lPtr + 1]); + } + else if (rPtr - lPtr < 3) + { + mBleClass.add(buffer[lPtr]); + 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 + + auto midiType = lastStatus & 0xF0; + if (sysExContinuation) + midiType = 0xF0; + + switch (midiType) + { + 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(lastStatus); + for (auto i = lPtr; i < rPtr; i++) + mBleClass.add(buffer[i + 1]); + + break; + + default: + break; + } + } + } + else + { + auto midiType = lastStatus & 0xF0; + if (sysExContinuation) + midiType = 0xF0; + + switch (midiType) { case 0x80: case 0x90: case 0xA0: case 0xB0: case 0xE0: - for (auto i = lPtr; i < rPtr; i = i + 2) + //3 bytes full Midi -> 2 bytes runningStatus + for (auto i = lPtr; i <= rPtr; i = i + 2) { mBleClass.add(lastStatus); + mBleClass.add(buffer[i]); mBleClass.add(buffer[i + 1]); - mBleClass.add(buffer[i + 2]); } break; case 0xC0: case 0xD0: - for (auto i = lPtr; i < rPtr; i = i + 1) + //2 bytes full Midi -> 1 byte runningStatus + for (auto i = lPtr; i <= rPtr; i = i + 1) { mBleClass.add(lastStatus); - mBleClass.add(buffer[i + 1]); + mBleClass.add(buffer[i]); } break; - case 0xF0: - mBleClass.add(buffer[lPtr]); - for (auto i = lPtr; i < rPtr; i++) - mBleClass.add(buffer[i + 1]); - break; + default: break; } + runningStatusContinuation = false; } - + + if (++rPtr >= length) + return; // end of packet + + if (lastStatus < 0xf0) //exclude System Message. They must not be RunningStatus + { + previousStatus = lastStatus; + } + + timestampByte = buffer[rPtr++]; + if (timestampByte >= 80) // is bit 7 set? + { + auto timestampLow = 0x7f & timestampByte; + timestamp = timestampLow + (timestampHigh << 7); + } + // Point to next status - lPtr = rPtr + 2; - if(lPtr >= length) + lPtr = rPtr; + if (lPtr >= length) return; //end of packet } - } - + } }; struct MySettings : public MIDI_NAMESPACE::DefaultSettings @@ -293,4 +396,3 @@ struct MySettings : public MIDI_NAMESPACE::DefaultSettings }; END_BLEMIDI_NAMESPACE -