Line data Source code
1 : #if !defined(ARDUINO) || defined(ESP32) || defined(DOXYGEN)
2 :
3 : #include "BluetoothMIDI_Interface.hpp"
4 : #include "BLEMIDI/ESP32/midi.h"
5 :
6 : BEGIN_CS_NAMESPACE
7 :
8 : // -------------------------------------------------------------------------- //
9 :
10 : // The following section defines functions that send the MIDI BLE packets in
11 : // the background.
12 :
13 27 : void BluetoothMIDI_Interface::startSendingThread() {
14 : // As long as you didn't get the stop signal, wait for data to send
15 78 : auto send_loop = [this] {
16 51 : while (handleSendEvents())
17 : ; // loop
18 54 : };
19 : // Need larger stack than default, pin to non-Arduino core
20 27 : ScopedThreadConfig(4096, 3, true, "SendBLEMIDI", 0);
21 : // Launch the thread
22 27 : send_thread = std::thread(send_loop);
23 27 : }
24 :
25 51 : bool BluetoothMIDI_Interface::handleSendEvents() {
26 51 : lock_t lock(mtx);
27 :
28 : // Wait for a packet to be started (or for a stop signal)
29 152 : cv.wait(lock, [this] { return !packetbuilder.empty() || stop_sending; });
30 51 : bool keep_going = !stop_sending;
31 : // Wait for flush signal or timeout
32 105 : bool flushing = cv.wait_for(lock, timeout, [this] { return flushnow; });
33 :
34 : // Send the packet over BLE, empty the buffer, and update the buffer size
35 : // based on the MTU of the connected clients.
36 51 : if (!packetbuilder.empty())
37 25 : notifyMIDIBLE(packetbuilder.getPacket());
38 51 : packetbuilder.reset();
39 51 : packetbuilder.setCapacity(min_mtu - 3);
40 :
41 : // Notify the main thread that the flush was done
42 51 : if (flushing) {
43 50 : flushnow = false;
44 50 : lock.unlock();
45 50 : cv.notify_one();
46 : }
47 102 : return keep_going;
48 : }
49 :
50 23 : void BluetoothMIDI_Interface::flushImpl(lock_t &lock) {
51 23 : assert(lock.owns_lock());
52 : // No need to send empty packets
53 23 : if (packetbuilder.empty())
54 0 : return;
55 :
56 : // Tell the background sender thread to send the packet now
57 23 : flushnow = true;
58 23 : lock.unlock();
59 23 : cv.notify_one();
60 :
61 : // Wait for flush to complete (when the sender clears the flushnow flag)
62 23 : lock.lock();
63 68 : cv.wait(lock, [this] { return !flushnow; });
64 23 : assert(lock.owns_lock());
65 : }
66 :
67 29 : void BluetoothMIDI_Interface::stopSendingThread() {
68 : // Tell the sender that this is the last packet
69 29 : stop_sending = true;
70 :
71 : // Tell it to send the packet right now (flush)
72 58 : lock_t lock(mtx);
73 29 : flushnow = true;
74 29 : lock.unlock();
75 29 : cv.notify_one();
76 :
77 : // Wait for it to be sent, and join the thread when done
78 29 : if (send_thread.joinable())
79 27 : send_thread.join();
80 29 : }
81 :
82 : // -------------------------------------------------------------------------- //
83 :
84 : #ifdef ARDUINO
85 : void BluetoothMIDI_Interface::notifyMIDIBLE(
86 : const std::vector<uint8_t> &packet) {
87 : midi_notify(packet.data(), packet.size());
88 : }
89 : #endif
90 :
91 : // -------------------------------------------------------------------------- //
92 :
93 : // The following section implements the MIDI sending functions.
94 :
95 11 : void BluetoothMIDI_Interface::sendChannelMessageImpl(ChannelMessage msg) {
96 11 : msg.hasTwoDataBytes() ? sendChannelMessageImpl3Bytes(msg)
97 3 : : sendChannelMessageImpl2Bytes(msg);
98 11 : }
99 :
100 8 : void BluetoothMIDI_Interface::sendChannelMessageImpl3Bytes(ChannelMessage msg) {
101 : // BLE packets are sent asynchronously, so we need a lock to access the
102 : // packet buffer
103 16 : lock_t lock(mtx);
104 8 : uint16_t timestamp = millis();
105 : // Try adding the message to the current packet
106 8 : if (!packetbuilder.add3B(msg.header, msg.data1, msg.data2, timestamp)) {
107 : // If that doesn't work, flush the packet (send it now and wait until
108 : // it is sent)
109 1 : flushImpl(lock);
110 : // And then add it to the (now emtpy) buffer
111 1 : packetbuilder.add3B(msg.header, msg.data1, msg.data2, timestamp);
112 : }
113 : // Notify the packet sender that data has been added to the buffer
114 8 : lock.unlock();
115 8 : cv.notify_one();
116 8 : }
117 :
118 3 : void BluetoothMIDI_Interface::sendChannelMessageImpl2Bytes(ChannelMessage msg) {
119 : // For comments, see
120 : // sendChannelMessageImpl3Bytes() above
121 6 : lock_t lock(mtx);
122 3 : uint16_t timestamp = millis();
123 3 : if (!packetbuilder.add2B(msg.header, msg.data1, timestamp)) {
124 1 : flushImpl(lock);
125 1 : packetbuilder.add2B(msg.header, msg.data1, timestamp);
126 : }
127 3 : lock.unlock();
128 3 : cv.notify_one();
129 3 : }
130 :
131 4 : void BluetoothMIDI_Interface::sendRealTimeImpl(RealTimeMessage msg) {
132 : // For comments, see
133 : // sendChannelMessageImpl3Bytes() above
134 8 : lock_t lock(mtx);
135 4 : uint16_t timestamp = millis();
136 4 : if (!packetbuilder.addRealTime(msg.message, timestamp)) {
137 1 : flushImpl(lock);
138 1 : packetbuilder.addRealTime(msg.message, timestamp);
139 : }
140 4 : lock.unlock();
141 4 : cv.notify_one();
142 4 : }
143 :
144 3 : void BluetoothMIDI_Interface::sendSysCommonImpl(SysCommonMessage msg) {
145 : // For comments, see
146 : // sendChannelMessageImpl3Bytes() above
147 6 : lock_t lock(mtx);
148 3 : uint16_t timestamp = millis();
149 3 : uint8_t num_data = msg.getNumberOfDataBytes();
150 3 : if (!packetbuilder.addSysCommon(num_data, msg.header, msg.data1, msg.data2,
151 : timestamp)) {
152 1 : flushImpl(lock);
153 1 : packetbuilder.addSysCommon(num_data, msg.header, msg.data1, msg.data2,
154 : timestamp);
155 : }
156 3 : lock.unlock();
157 3 : cv.notify_one();
158 3 : }
159 :
160 4 : void BluetoothMIDI_Interface::sendSysExImpl(SysExMessage msg) {
161 8 : lock_t lock(mtx);
162 :
163 4 : size_t length = msg.length;
164 4 : const uint8_t *data = msg.data;
165 4 : uint16_t timestamp = millis(); // BLE MIDI timestamp
166 : // TODO: I have no idea why, but the last byte gets cut off when the LSB
167 : // of the timestamp is 0x77 ... (Problem is probably in the BlueZ parser)
168 4 : if ((timestamp & 0x77) == 0x77)
169 0 : timestamp &= 0xFFFE;
170 :
171 : // Try adding at least the SysExStart header to the current packet
172 4 : if (!packetbuilder.addSysEx(data, length, timestamp)) {
173 : // If that didn't fit, flush the packet
174 1 : flushImpl(lock);
175 : // Add the first part of the SysEx message to this packet
176 1 : packetbuilder.addSysEx(data, length, timestamp);
177 : }
178 : // As long as there's data to be sent in the next packet
179 10 : while (data) {
180 : // Send the previous (full) packet
181 6 : flushImpl(lock);
182 : // And add the next part of the SysEx message to a continuation packet
183 6 : packetbuilder.continueSysEx(data, length, timestamp);
184 : }
185 : // Notify the packet sender that data has been added to the buffer
186 4 : lock.unlock();
187 4 : cv.notify_one();
188 4 : }
189 :
190 : // -------------------------------------------------------------------------- //
191 :
192 13 : void BluetoothMIDI_Interface::parse(const uint8_t *const data,
193 : const size_t len) {
194 13 : auto mididata = BLEMIDIParser(data, len);
195 13 : MIDIReadEvent event = parser.pull(mididata);
196 : // TODO: add a timeout instead of busy waiting?
197 36 : while (event != MIDIReadEvent::NO_MESSAGE) {
198 23 : switch (event) {
199 16 : case MIDIReadEvent::CHANNEL_MESSAGE:
200 32 : while (!queue.push(parser.getChannelMessage(),
201 16 : mididata.getTimestamp()))
202 0 : std::this_thread::yield();
203 16 : break;
204 4 : case MIDIReadEvent::SYSEX_CHUNK: // fallthrough
205 : case MIDIReadEvent::SYSEX_MESSAGE:
206 8 : while (!queue.push(parser.getSysExMessage(),
207 4 : mididata.getTimestamp()))
208 0 : std::this_thread::yield();
209 4 : break;
210 2 : case MIDIReadEvent::REALTIME_MESSAGE:
211 4 : while (!queue.push(parser.getRealTimeMessage(),
212 2 : mididata.getTimestamp()))
213 0 : std::this_thread::yield();
214 2 : break;
215 1 : case MIDIReadEvent::SYSCOMMON_MESSAGE:
216 2 : while (!queue.push(parser.getSysCommonMessage(),
217 1 : mididata.getTimestamp()))
218 0 : std::this_thread::yield();
219 1 : break;
220 : case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE
221 : default: break; // LCOV_EXCL_LINE
222 : }
223 23 : event = parser.pull(mididata);
224 : }
225 13 : parser.cancelRunningStatus();
226 13 : }
227 :
228 34 : MIDIReadEvent BluetoothMIDI_Interface::read() {
229 : // Pop a new message from the queue
230 34 : if (!queue.pop(incomingMessage))
231 11 : return MIDIReadEvent::NO_MESSAGE;
232 23 : return incomingMessage.eventType;
233 : }
234 :
235 16 : ChannelMessage BluetoothMIDI_Interface::getChannelMessage() const {
236 16 : return incomingMessage.eventType == MIDIReadEvent::CHANNEL_MESSAGE
237 16 : ? incomingMessage.message.channelmessage
238 32 : : ChannelMessage(0, 0, 0);
239 : }
240 :
241 1 : SysCommonMessage BluetoothMIDI_Interface::getSysCommonMessage() const {
242 1 : return incomingMessage.eventType == MIDIReadEvent::SYSCOMMON_MESSAGE
243 1 : ? incomingMessage.message.syscommonmessage
244 2 : : SysCommonMessage(0, 0, 0);
245 : }
246 :
247 2 : RealTimeMessage BluetoothMIDI_Interface::getRealTimeMessage() const {
248 2 : return incomingMessage.eventType == MIDIReadEvent::REALTIME_MESSAGE
249 2 : ? incomingMessage.message.realtimemessage
250 4 : : RealTimeMessage(0);
251 : }
252 :
253 4 : SysExMessage BluetoothMIDI_Interface::getSysExMessage() const {
254 4 : auto evt = incomingMessage.eventType;
255 4 : bool hasSysEx = evt == MIDIReadEvent::SYSEX_MESSAGE ||
256 : evt == MIDIReadEvent::SYSEX_CHUNK;
257 4 : return hasSysEx ? incomingMessage.message.sysexmessage
258 8 : : SysExMessage(nullptr, 0);
259 : }
260 :
261 4 : uint16_t BluetoothMIDI_Interface::getTimestamp() const {
262 4 : return incomingMessage.timestamp;
263 : }
264 :
265 : // -------------------------------------------------------------------------- //
266 :
267 8 : void BluetoothMIDI_Interface::updateMTU(uint16_t mtu) {
268 8 : uint16_t force_min_mtu_c = force_min_mtu;
269 8 : if (force_min_mtu_c == 0)
270 0 : min_mtu = mtu;
271 : else
272 8 : min_mtu = std::min(force_min_mtu_c, mtu);
273 : DEBUGFN(NAMEDVALUE(min_mtu));
274 16 : lock_t lock(mtx);
275 8 : if (packetbuilder.getSize() == 0)
276 8 : packetbuilder.setCapacity(min_mtu - 3);
277 8 : }
278 :
279 8 : void BluetoothMIDI_Interface::forceMinMTU(uint16_t mtu) {
280 8 : force_min_mtu = mtu;
281 8 : updateMTU(min_mtu);
282 8 : }
283 :
284 : // -------------------------------------------------------------------------- //
285 :
286 0 : extern "C" void BluetoothMIDI_Interface_midi_mtu_callback(uint16_t mtu) {
287 0 : BluetoothMIDI_Interface::midi_mtu_callback(mtu);
288 0 : }
289 :
290 0 : extern "C" void BluetoothMIDI_Interface_midi_write_callback(const uint8_t *data,
291 : size_t length) {
292 0 : BluetoothMIDI_Interface::midi_write_callback(data, length);
293 0 : }
294 :
295 : // -------------------------------------------------------------------------- //
296 :
297 0 : void BluetoothMIDI_Interface::setName(const char *name) {
298 : #ifdef ARDUINO
299 : set_midi_ble_name(name);
300 : #else
301 : (void)name;
302 : #endif
303 0 : }
304 :
305 27 : void BluetoothMIDI_Interface::begin() {
306 : #ifdef ARDUINO
307 : midi_set_mtu_callback(BluetoothMIDI_Interface_midi_mtu_callback);
308 : midi_set_write_callback(BluetoothMIDI_Interface_midi_write_callback);
309 : DEBUGFN(F("Initializing BLE MIDI Interface"));
310 : if (!midi_init()) {
311 : ERROR(F("Error initializing BLE MIDI interface"), 0x2022);
312 : return;
313 : }
314 : #endif
315 27 : startSendingThread();
316 27 : }
317 :
318 28 : void BluetoothMIDI_Interface::end() {
319 : #ifdef ARDUINO
320 : DEBUGFN(F("Deinitializing BLE MIDI Interface"));
321 : if (!midi_deinit()) {
322 : ERROR(F("Error deinitializing BLE MIDI interface"), 0x2023);
323 : return;
324 : }
325 : #endif
326 28 : }
327 :
328 : // -------------------------------------------------------------------------- //
329 :
330 : BluetoothMIDI_Interface *BluetoothMIDI_Interface::instance = nullptr;
331 :
332 : END_CS_NAMESPACE
333 :
334 : #endif
|