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
|