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 13 : BLEMIDIParser(const uint8_t *data, size_t length) 19 13 : : 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 13 : if (length < 2) { 26 1 : this->data = end; 27 : } 28 : // First byte should be a header. If it's a data byte, discard packet. 29 12 : 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 11 : 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 10 : this->timestamp = data[0] & 0x7F; 42 10 : this->timestamp <<= 7; 43 10 : this->timestamp |= data[1] & 0x7F; 44 10 : this->data += 2; 45 : } 46 13 : } 47 : 48 : /// Get the next MIDI byte from the BLE packet (if available). 49 : /// @return True if a byte was available, false otherwise. 50 100 : bool pull(uint8_t &output) { 51 100 : while (data != end) { 52 : // Simply pass on all normal data bytes to the MIDI parser. 53 87 : if (isData(*data)) { 54 48 : output = *data++; 55 48 : prevWasTimestamp = false; 56 48 : return true; 57 : } 58 : // If it's not a data byte, it's either a timestamp byte or a 59 : // MIDI status byte. 60 : else { 61 : // Timestamp bytes and MIDI status bytes should alternate. 62 39 : prevWasTimestamp = !prevWasTimestamp; 63 : // If the previous non-data byte was a timestamp, this one is 64 : // a MIDI status byte, so send it to the MIDI parser. 65 39 : if (!prevWasTimestamp) { 66 23 : output = *data++; 67 23 : return true; 68 : } 69 : // Otherwise it's a time stamp 70 : else { 71 16 : timestamp = (timestamp & 0x3F80) | (*data++ & 0x7F); 72 : } 73 : } 74 : } 75 13 : return false; 76 : } 77 : 78 23 : uint16_t getTimestamp() const { return timestamp; } 79 : 80 : private: 81 : const uint8_t *data; 82 : const uint8_t *const end; 83 : bool prevWasTimestamp = true; 84 : uint16_t timestamp = 0; 85 : 86 : private: 87 : /// Check if the given byte is a data byte (and not a header, timestamp or 88 : /// status byte). 89 110 : static bool isData(uint8_t data) { return (data & (1 << 7)) == 0; } 90 : }; 91 : 92 : END_CS_NAMESPACE