Line data Source code
1 : #pragma once 2 : 3 : #include <Settings/NamespaceSettings.hpp> 4 : #include <stddef.h> 5 : #include <stdint.h> 6 : 7 : BEGIN_CS_NAMESPACE 8 : 9 : /** 10 : * @brief Class for parsing BLE-MIDI packets. It doesn't parse the actual MIDI 11 : * messages, it just extracts the relevant MIDI data from the BLE 12 : * packets. 13 : * 14 : * @ingroup MIDIParsers 15 : */ 16 : class BLEMIDIParser { 17 : public: 18 44 : BLEMIDIParser(const uint8_t *data, size_t length) 19 44 : : data(data), end(data + length) { 20 : // Need at least two bytes to be useful. 21 : // Usually, we have header, timestamp and at least one MIDI byte, 22 : // but a SysEx continuation could perhaps have only a header and a 23 : // single data byte (this is not explicitly allowed by the spec, but 24 : // handling this case requires no extra effort) 25 44 : if (length < 2) { 26 30 : this->data = end; 27 : } 28 : // First byte should be a header. If it's a data byte, discard packet. 29 14 : else if (isData(data[0])) { 30 1 : this->data = end; 31 : } 32 : // If the second byte is a data byte, this is a SysEx continuation 33 : // packet 34 13 : else if (isData(data[1])) { 35 1 : this->timestamp = data[0] & 0x7F; 36 1 : this->timestamp <<= 7; 37 1 : this->data += 1; 38 : } 39 : // Otherwise, the second byte is a timestamp, so skip it 40 : else { 41 12 : this->timestamp = data[0] & 0x7F; 42 12 : this->timestamp <<= 7; 43 12 : this->timestamp |= data[1] & 0x7F; 44 12 : this->data += 2; 45 : } 46 44 : } 47 : 48 : /// Extend the BLE packet with the given buffer. 49 : /// @pre The previous buffer was fully consumed (@ref pull returned false). 50 : /// @note This function should only be used for a single packet that spans 51 : /// multiple buffers. If you want to parse a new packet, you should 52 : /// create a new BLEMIDIParser instance. 53 0 : void extend(const uint8_t *data, size_t length) { 54 0 : this->data = data; 55 0 : this->end = data + length; 56 0 : } 57 : 58 : /// Get the next MIDI byte from the BLE packet (if available). 59 : /// @return True if a byte was available, false otherwise. 60 133 : bool pull(uint8_t &output) { 61 156 : while (data != end) { 62 : // Simply pass on all normal data bytes to the MIDI parser. 63 129 : if (isData(*data)) { 64 77 : output = *data++; 65 77 : prevWasTimestamp = false; 66 77 : return true; 67 : } 68 : // If it's not a data byte, it's either a timestamp byte or a 69 : // MIDI status byte. 70 : else { 71 : // Timestamp bytes and MIDI status bytes should alternate. 72 52 : prevWasTimestamp = !prevWasTimestamp; 73 : // If the previous non-data byte was a timestamp, this one is 74 : // a MIDI status byte, so send it to the MIDI parser. 75 52 : if (!prevWasTimestamp) { 76 29 : output = *data++; 77 29 : return true; 78 : } 79 : // Otherwise it's a time stamp 80 : else { 81 23 : uint16_t timestampLow = *data++ & 0x7F; 82 : // The BLE MIDI spec has the following to say about overflow: 83 : // > Should the timestamp value of a subsequent MIDI message 84 : // > in the same packet overflow/wrap (i.e., the 85 : // > timestampLow is smaller than a preceding timestampLow), 86 : // > the receiver is responsible for tracking this by 87 : // > incrementing the timestampHigh by one (the incremented 88 : // > value is not transmitted, only understood as a result 89 : // > of the overflow condition). 90 23 : if (timestampLow < (timestamp & 0x7F)) // overflow 91 1 : timestamp += 0x80; 92 23 : timestamp = (timestamp & 0x3F80) | timestampLow; 93 : } 94 : } 95 : } 96 27 : return false; 97 : } 98 : 99 40 : uint16_t getTimestamp() const { return timestamp; } 100 : 101 : private: 102 : const uint8_t *data; 103 : const uint8_t *end; 104 : bool prevWasTimestamp = true; 105 : uint16_t timestamp = 0; 106 : 107 : private: 108 : /// Check if the given byte is a data byte (and not a header, timestamp or 109 : /// status byte). 110 156 : static bool isData(uint8_t data) { return (data & (1 << 7)) == 0; } 111 : }; 112 : 113 : END_CS_NAMESPACE