Line data Source code
1 : #pragma once
2 :
3 : #include <MIDI_Parsers/MIDI_MessageTypes.hpp>
4 :
5 : #include <AH/STL/vector>
6 :
7 : BEGIN_CS_NAMESPACE
8 :
9 : /// Class for building MIDI over Bluetooth Low Energy packets.
10 : class BLEMIDIPacketBuilder {
11 : private:
12 : uint8_t runningHeader = 0;
13 : uint8_t runningTimestamp = 0;
14 : std::vector<uint8_t> buffer = std::vector<uint8_t>(0);
15 :
16 : constexpr static const uint8_t SysExStart =
17 : static_cast<uint8_t>(MIDIMessageType::SysExStart);
18 : constexpr static const uint8_t SysExEnd =
19 : static_cast<uint8_t>(MIDIMessageType::SysExEnd);
20 :
21 : /// Check if the buffer has space left.
22 111 : bool hasSpaceFor(size_t bytes) const {
23 111 : return bytes <= size_t(buffer.capacity() - buffer.size());
24 : }
25 :
26 : /// Timestamp[0]: 0b10hh hhhh
27 71 : constexpr static uint8_t getTimestampMSB(uint16_t timestamp) {
28 71 : return ((timestamp >> 7) & 0x3F) | 0x80;
29 : }
30 : /// Timestamp[1]: 0b1lll llll
31 96 : constexpr static uint8_t getTimestampLSB(uint16_t timestamp) {
32 96 : return timestamp | 0x80;
33 : }
34 :
35 : /// If this is the first byte/message in the packet, add the header
36 : /// containing the 6 most significant bits of the timestamp
37 128 : void initBuffer(uint16_t timestamp) {
38 128 : if (buffer.empty())
39 71 : buffer.push_back(getTimestampMSB(timestamp));
40 128 : }
41 :
42 : /**
43 : * @brief Try adding a 2-byte or 3-byte MIDI channel voice message to the
44 : * packet.
45 : *
46 : * @tparam ThreeBytes
47 : * Set to `true` for a 3-byte message, `false` for a 2-byte message.
48 : *
49 : * @param header
50 : * MIDI status byte.
51 : * @param data1
52 : * MIDI data byte 1.
53 : * @param data2
54 : * MIDI data byte 2 (if `ThreeBytes == true`).
55 : * @param timestamp
56 : * 13-bit BLE-MIDI timestamp.
57 : *
58 : * @retval true
59 : * Successfully added message to the packet.
60 : * @retval false
61 : * Buffer is too full, send the current packet, reset the packet
62 : * builder, and try again.
63 : */
64 : template <bool ThreeBytes>
65 : bool addImpl(uint8_t header, uint8_t data1, uint8_t data2,
66 : uint16_t timestamp);
67 :
68 : public:
69 66 : BLEMIDIPacketBuilder(size_t capacity = 20) { buffer.reserve(capacity); }
70 :
71 : /// Reset the builder to start a new packet.
72 : void reset();
73 :
74 : /// Set the maximum capacity of the buffer. Set this to the MTU of the BLE
75 : /// link minus three bytes (for notify overhead).
76 : void setCapacity(uint16_t capacity);
77 :
78 : /// Get the size of the current packet.
79 29 : uint16_t getSize() const { return buffer.size(); }
80 : /// Get a pointer to the packet data buffer.
81 22 : const uint8_t *getBuffer() const { return buffer.data(); }
82 : /// Check if the packet buffer is empty.
83 121 : bool empty() const { return buffer.empty(); }
84 :
85 : /// Return the packet as a vector of bytes.
86 50 : const std::vector<uint8_t> &getPacket() const { return buffer; }
87 :
88 : /**
89 : * @brief Try adding a 3-byte MIDI channel voice message to the packet.
90 : *
91 : * @param header
92 : * MIDI status byte.
93 : * @param data1
94 : * MIDI data byte 1.
95 : * @param data2
96 : * MIDI data byte 2.
97 : * @param timestamp
98 : * 13-bit BLE-MIDI timestamp.
99 : *
100 : * @retval true
101 : * Successfully added message to the packet.
102 : * @retval false
103 : * Buffer is too full, send the current packet, reset the packet
104 : * builder, and try again.
105 : */
106 : bool add3B(uint8_t header, uint8_t data1, uint8_t data2,
107 : uint16_t timestamp);
108 :
109 : /**
110 : * @brief Try adding a 2-byte MIDI channel voice message to the packet.
111 : *
112 : * @param header
113 : * MIDI status byte.
114 : * @param data1
115 : * MIDI data byte 1.
116 : * @param timestamp
117 : * 13-bit BLE-MIDI timestamp.
118 : *
119 : * @retval true
120 : * Successfully added message to the packet.
121 : * @retval false
122 : * Buffer is too full, send the current packet, reset the packet
123 : * builder, and try again.
124 : */
125 : bool add2B(uint8_t header, uint8_t data1, uint16_t timestamp);
126 :
127 : /**
128 : * @brief Try adding a MIDI real-time message to the packet.
129 : *
130 : * @param rt
131 : * MIDI real-time byte.
132 : * @param timestamp
133 : * 13-bit BLE-MIDI timestamp.
134 : *
135 : * @retval true
136 : * Successfully added message to the packet.
137 : * @retval false
138 : * Buffer is too full, send the current packet, reset the packet
139 : * builder, and try again.
140 : */
141 : bool addRealTime(uint8_t rt, uint16_t timestamp);
142 :
143 : /**
144 : * @brief Try adding a MIDI system common message to the packet.
145 : *
146 : * @param num_data
147 : * The number of data bytes (0, 1 or 2).
148 : * @param header
149 : * System common status byte.
150 : * @param data1
151 : * MIDI data byte 1.
152 : * @param data2
153 : * MIDI data byte 2.
154 : * @param timestamp
155 : * 13-bit BLE-MIDI timestamp.
156 : *
157 : * @retval true
158 : * Successfully added message to the packet.
159 : * @retval false
160 : * Buffer is too full, send the current packet, reset the packet
161 : * builder, and try again.
162 : */
163 : bool addSysCommon(uint8_t num_data, uint8_t header, uint8_t data1,
164 : uint8_t data2, uint16_t timestamp);
165 :
166 : /**
167 : * @brief Try adding (part of) a SysEx message to the packet.
168 : *
169 : * @param[in,out] data
170 : * Pointer to the first byte of the SysEx message.
171 : * At the end, this will point to the first byte to
172 : * send in the next packet, or `nullptr` if the message was
173 : * finished.
174 : * @param[in,out] length
175 : * The number of bytes in the SysEx message.
176 : * At the end, this will be set to remaining number of
177 : * bytes to send in the next packet.
178 : * @param[in] timestamp
179 : * 13-bit BLE-MIDI timestamp.
180 : *
181 : * @retval true
182 : * Successfully added (part of) the message to the packet.
183 : * @retval false
184 : * Buffer is too full, send the current packet, reset the packet
185 : * builder, and try again.
186 : *
187 : * If the message fits in a single packet, `length` is set to `0` (no
188 : * remaining data bytes) and `data` is set to `nullptr`.
189 : *
190 : * For example:
191 : * ~~~cpp
192 : * BLEMIDIPacketBuilder packetbuilder;
193 : *
194 : * const uint8_t *data = (...);
195 : * size_t length = (...);
196 : * uint16_t timestamp = (...);
197 : *
198 : * if (!packetbuilder.addSysEx(data, length, timestamp)) {
199 : * sendnow(packetbuilder.getBuffer(), packetbuilder.getSize());
200 : * packetbuilder.reset();
201 : * packetbuilder.addSysEx(data, length, timestamp)
202 : * }
203 : * while (data) {
204 : * sendnow(packetbuilder.getBuffer(), packetbuilder.getSize());
205 : * packetbuilder.reset();
206 : * packetbuilder.continueSysEx(data, length, timestamp);
207 : * }
208 : * ~~~
209 : */
210 : bool addSysEx(const uint8_t *&data, size_t &length, uint16_t timestamp);
211 :
212 : /**
213 : * @brief Add a SysEx continuation to the packet.
214 : *
215 : * @param[in,out] data
216 : * Pointer to the first byte of the SysEx message to send
217 : * in this continuation packet.
218 : * At the end, this will point to the first byte to send in
219 : * the next packet, or `nullptr` if the message was
220 : * finished.
221 : * @param[in,out] length
222 : * The number of remaining bytes in the SysEx message.
223 : * At the end, this will be set to remaining number of
224 : * bytes to send in the next packet.
225 : * @param[in] timestamp
226 : * 13-bit BLE-MIDI timestamp.
227 : *
228 : * If the message can be completed in a single packet, `length` is set to
229 : * `0` (no remaining data bytes) and `data` is set to `nullptr`.
230 : *
231 : * @see @ref addSysEx()
232 : */
233 : void continueSysEx(const uint8_t *&data, size_t &length,
234 : uint16_t timestamp);
235 : };
236 :
237 : END_CS_NAMESPACE
|