Control Surface master
MIDI Control Surface library for Arduino
BluetoothMIDI_Interface.cpp
Go to the documentation of this file.
1#if !defined(ARDUINO) || defined(ESP32) || defined(DOXYGEN)
2
5
7
8// -------------------------------------------------------------------------- //
9
10// The following section defines functions that send the MIDI BLE packets in
11// the background.
12
14 // As long as you didn't get the stop signal, wait for data to send
15 auto send_loop = [this] {
16 while (handleSendEvents())
17 ; // loop
18 };
19 // Need larger stack than default, pin to non-Arduino core
20 ScopedThreadConfig(4096, 3, true, "SendBLEMIDI", 0);
21 // Launch the thread
22 send_thread = std::thread(send_loop);
23}
24
26 lock_t lock(mtx);
27
28 // Wait for a packet to be started (or for a stop signal)
29 cv.wait(lock, [this] { return !packetbuilder.empty() || stop_sending; });
30 bool keep_going = !stop_sending;
31 // Wait for flush signal or timeout
32 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 if (!packetbuilder.empty())
40
41 // Notify the main thread that the flush was done
42 if (flushing) {
43 flushnow = false;
44 lock.unlock();
45 cv.notify_one();
46 }
47 return keep_going;
48}
49
51 assert(lock.owns_lock());
52 // No need to send empty packets
53 if (packetbuilder.empty())
54 return;
55
56 // Tell the background sender thread to send the packet now
57 flushnow = true;
58 lock.unlock();
59 cv.notify_one();
60
61 // Wait for flush to complete (when the sender clears the flushnow flag)
62 lock.lock();
63 cv.wait(lock, [this] { return !flushnow; });
64 assert(lock.owns_lock());
65}
66
68 // Tell the sender that this is the last packet
69 stop_sending = true;
70
71 // Tell it to send the packet right now (flush)
72 lock_t lock(mtx);
73 flushnow = true;
74 lock.unlock();
75 cv.notify_one();
76
77 // Wait for it to be sent, and join the thread when done
78 if (send_thread.joinable())
79 send_thread.join();
80}
81
82// -------------------------------------------------------------------------- //
83
84#ifdef ARDUINO
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
98}
99
101 // BLE packets are sent asynchronously, so we need a lock to access the
102 // packet buffer
103 lock_t lock(mtx);
104 uint16_t timestamp = millis();
105 // Try adding the message to the current packet
106 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 flushImpl(lock);
110 // And then add it to the (now emtpy) buffer
111 packetbuilder.add3B(msg.header, msg.data1, msg.data2, timestamp);
112 }
113 // Notify the packet sender that data has been added to the buffer
114 lock.unlock();
115 cv.notify_one();
116}
117
119 // For comments, see
120 // sendChannelMessageImpl3Bytes() above
121 lock_t lock(mtx);
122 uint16_t timestamp = millis();
123 if (!packetbuilder.add2B(msg.header, msg.data1, timestamp)) {
124 flushImpl(lock);
125 packetbuilder.add2B(msg.header, msg.data1, timestamp);
126 }
127 lock.unlock();
128 cv.notify_one();
129}
130
132 // For comments, see
133 // sendChannelMessageImpl3Bytes() above
134 lock_t lock(mtx);
135 uint16_t timestamp = millis();
136 if (!packetbuilder.addRealTime(msg.message, timestamp)) {
137 flushImpl(lock);
138 packetbuilder.addRealTime(msg.message, timestamp);
139 }
140 lock.unlock();
141 cv.notify_one();
142}
143
145 // For comments, see
146 // sendChannelMessageImpl3Bytes() above
147 lock_t lock(mtx);
148 uint16_t timestamp = millis();
149 uint8_t num_data = msg.getNumberOfDataBytes();
150 if (!packetbuilder.addSysCommon(num_data, msg.header, msg.data1, msg.data2,
151 timestamp)) {
152 flushImpl(lock);
153 packetbuilder.addSysCommon(num_data, msg.header, msg.data1, msg.data2,
154 timestamp);
155 }
156 lock.unlock();
157 cv.notify_one();
158}
159
161 lock_t lock(mtx);
162
163 size_t length = msg.length;
164 const uint8_t *data = msg.data;
165 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 if ((timestamp & 0x77) == 0x77)
169 timestamp &= 0xFFFE;
170
171 // Try adding at least the SysExStart header to the current packet
172 if (!packetbuilder.addSysEx(data, length, timestamp)) {
173 // If that didn't fit, flush the packet
174 flushImpl(lock);
175 // Add the first part of the SysEx message to this packet
176 packetbuilder.addSysEx(data, length, timestamp);
177 }
178 // As long as there's data to be sent in the next packet
179 while (data) {
180 // Send the previous (full) packet
181 flushImpl(lock);
182 // And add the next part of the SysEx message to a continuation packet
183 packetbuilder.continueSysEx(data, length, timestamp);
184 }
185 // Notify the packet sender that data has been added to the buffer
186 lock.unlock();
187 cv.notify_one();
188}
189
190// -------------------------------------------------------------------------- //
191
192void BluetoothMIDI_Interface::parse(const uint8_t *const data,
193 const size_t len) {
194 auto mididata = BLEMIDIParser(data, len);
195 MIDIReadEvent event = parser.pull(mididata);
196 // TODO: add a timeout instead of busy waiting?
197 while (event != MIDIReadEvent::NO_MESSAGE) {
198 switch (event) {
201 mididata.getTimestamp()))
202 std::this_thread::yield();
203 break;
204 case MIDIReadEvent::SYSEX_CHUNK: // fallthrough
207 mididata.getTimestamp()))
208 std::this_thread::yield();
209 break;
212 mididata.getTimestamp()))
213 std::this_thread::yield();
214 break;
217 mididata.getTimestamp()))
218 std::this_thread::yield();
219 break;
220 case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE
221 default: break; // LCOV_EXCL_LINE
222 }
223 event = parser.pull(mididata);
224 }
226}
227
229 // Pop a new message from the queue
233}
234
238 : ChannelMessage(0, 0, 0);
239}
240
244 : SysCommonMessage(0, 0, 0);
245}
246
250 : RealTimeMessage(0);
251}
252
254 auto evt = incomingMessage.eventType;
255 bool hasSysEx = evt == MIDIReadEvent::SYSEX_MESSAGE ||
257 return hasSysEx ? incomingMessage.message.sysexmessage
258 : SysExMessage(nullptr, 0);
259}
260
263}
264
265// -------------------------------------------------------------------------- //
266
268 uint16_t force_min_mtu_c = force_min_mtu;
269 if (force_min_mtu_c == 0)
270 min_mtu = mtu;
271 else
272 min_mtu = std::min(force_min_mtu_c, mtu);
274 lock_t lock(mtx);
275 if (packetbuilder.getSize() == 0)
277}
278
280 force_min_mtu = mtu;
282}
283
284// -------------------------------------------------------------------------- //
285
286extern "C" void BluetoothMIDI_Interface_midi_mtu_callback(uint16_t mtu) {
288}
289
290extern "C" void BluetoothMIDI_Interface_midi_write_callback(const uint8_t *data,
291 size_t length) {
293}
294
295// -------------------------------------------------------------------------- //
296
298#ifdef ARDUINO
301 DEBUGFN(F("Initializing BLE MIDI Interface"));
302 if (!midi_init()) {
303 ERROR(F("Error initializing BLE MIDI interface"), 0x2022);
304 return;
305 }
306#endif
308}
309
311#ifdef ARDUINO
312 DEBUGFN(F("Deinitializing BLE MIDI Interface"));
313 if (!midi_deinit()) {
314 ERROR(F("Error deinitializing BLE MIDI interface"), 0x2023);
315 return;
316 }
317#endif
318}
319
320// -------------------------------------------------------------------------- //
321
323
325
326#endif
void BluetoothMIDI_Interface_midi_mtu_callback(uint16_t mtu)
void BluetoothMIDI_Interface_midi_write_callback(const uint8_t *data, size_t length)
#define ERROR(msg, errc)
Definition: Error.hpp:22
MIDIReadEvent
Values returned by the MIDI reading functions.
@ CHANNEL_MESSAGE
A MIDI Channel message was received.
@ SYSEX_CHUNK
An incomplete System Exclusive message.
@ SYSCOMMON_MESSAGE
A MIDI System Common message was received.
@ NO_MESSAGE
No new messages were received.
@ SYSEX_MESSAGE
A MIDI System Exclusive message was received.
@ REALTIME_MESSAGE
A MIDI Real-Time message was received.
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
uint16_t getSize() const
Get the size of the current packet.
void setCapacity(uint16_t capacity)
Set the maximum capacity of the buffer.
void continueSysEx(const uint8_t *&data, size_t &length, uint16_t timestamp)
Add a SysEx continuation to the packet.
bool addSysEx(const uint8_t *&data, size_t &length, uint16_t timestamp)
Try adding (part of) a SysEx message to the packet.
bool empty() const
Check if the packet buffer is empty.
bool add2B(uint8_t header, uint8_t data1, uint16_t timestamp)
Try adding a 2-byte MIDI channel voice message to the packet.
bool addSysCommon(uint8_t num_data, uint8_t header, uint8_t data1, uint8_t data2, uint16_t timestamp)
Try adding a MIDI system common message to the packet.
bool addRealTime(uint8_t rt, uint16_t timestamp)
Try adding a MIDI real-time message to the packet.
const std::vector< uint8_t > & getPacket() const
Return the packet as a vector of bytes.
void reset()
Reset the builder to start a new packet.
bool add3B(uint8_t header, uint8_t data1, uint8_t data2, uint16_t timestamp)
Try adding a 3-byte MIDI channel voice message to the packet.
Class for parsing BLE-MIDI packets.
Bluetooth Low Energy MIDI Interface for the ESP32.
ChannelMessage getChannelMessage() const
Return the received channel voice message.
RealTimeMessage getRealTimeMessage() const
Return the received real-time message.
std::condition_variable cv
Condition variable used by the background sender thread to wait for data to send, and for the main th...
std::unique_lock< std::mutex > lock_t
Lock type used to lock the mutex.
std::chrono::milliseconds timeout
Timeout before the sender thread sends a packet.
MIDIMessageQueue::MIDIMessageQueueElement incomingMessage
Incoming message that can be from retrieved using the getChannelMessage(), getSysCommonMessage(),...
bool handleSendEvents()
Function that waits for BLE packets and sends them in the background.
void sendChannelMessageImpl(ChannelMessage) override
Low-level function for sending a MIDI channel voice message.
void parse(const uint8_t *const data, const size_t len)
void forceMinMTU(uint16_t mtu)
Force the MTU to an artificially small value (used for testing).
static BluetoothMIDI_Interface * instance
Only one active instance.
SysCommonMessage getSysCommonMessage() const
Return the received system common message.
void sendRealTimeImpl(RealTimeMessage) override
Low-level function for sending a MIDI real-time message.
std::atomic_uint_fast16_t force_min_mtu
Override the minimum MTU (0 means don't override, nonzero overrides if it's smaller than the minimum ...
void flushImpl(lock_t &lock)
Tell the background BLE sender thread to send the current packet.
void begin() override
Initialize this updatable.
BLEMIDIPacketBuilder packetbuilder
Builds outgoing MIDI BLE packets.
MIDIMessageQueue queue
Queue for incoming MIDI messages.
void sendChannelMessageImpl2Bytes(ChannelMessage)
void stopSendingThread()
Tell the background BLE sender thread to stop gracefully, and join it.
void sendSysExImpl(SysExMessage) override
Low-level function for sending a system exclusive MIDI message.
static void midi_mtu_callback(uint16_t mtu)
void startSendingThread()
Launch a thread that sends the BLE packets in the background.
void sendChannelMessageImpl3Bytes(ChannelMessage)
void sendSysCommonImpl(SysCommonMessage) override
Low-level function for sending a MIDI system common message.
bool flushnow
Flag to tell the sender thread to send the packet immediately.
std::thread send_thread
Background thread that sends the actual MIDI BLE packets.
void notifyMIDIBLE(const std::vector< uint8_t > &packet)
std::mutex mtx
Mutex to lock the MIDI BLE packet builder and the flush flag.
void updateMTU(uint16_t mtu)
Set the maximum transmission unit of the Bluetooth link.
std::atomic_bool stop_sending
Flag to stop the background thread.
SysExMessage getSysExMessage() const
Return the received system exclusive message.
std::atomic_uint_fast16_t min_mtu
The minimum MTU of all connected clients.
SerialMIDI_Parser parser
MIDI Parser for incoming data.
uint16_t getTimestamp() const
Get the BLE-MIDI timestamp of the latest MIDI message.
static void midi_write_callback(const uint8_t *data, size_t length)
bool push(ChannelMessage message, uint16_t timestamp)
bool pop(MIDIMessageQueueElement &message)
ChannelMessage getChannelMessage() const
Get the latest MIDI channel voice message.
Definition: MIDI_Parser.hpp:19
RealTimeMessage getRealTimeMessage() const
Get the latest MIDI real-time message.
Definition: MIDI_Parser.hpp:25
SysCommonMessage getSysCommonMessage() const
Get the latest MIDI system common message.
Definition: MIDI_Parser.hpp:21
MIDIReadEvent pull(BytePuller &&puller)
Parse one incoming MIDI message.
SysExMessage getSysExMessage() const
Get the latest SysEx message.
void cancelRunningStatus()
Clear the running status header for MIDI Channel messages.
#define NAMEDVALUE(x)
Macro for printing an expression as a string, followed by its value.
Definition: Debug.hpp:92
#define DEBUGFN(x)
Print an expression and its function (function name and line number) to the debug output if debugging...
Definition: Debug.hpp:118
constexpr auto min(const T &a, const U &b) -> decltype(b< a ? b :a)
Return the smaller of two numbers/objects.
Definition: MinMaxFix.hpp:15
Public MIDI over Bluetooth Low Energy API.
bool midi_deinit()
Cleanup the MIDI BLE application and de-initialize the Bluetooth stack.
bool midi_init()
Initialize the Bluetooth stack and register the MIDI BLE application with the Bluedroid driver.
void midi_set_write_callback(midi_write_callback_t cb)
Set the callback that is to be called when the client writes (sends) a MIDI packet.
void midi_set_mtu_callback(midi_mtu_callback_t cb)
Set the callback that is to be called when the MTU negotiation with the BLE client is finished.
bool midi_notify(const uint8_t *data, size_t len)
Send a MIDI BLE notification to the client.
bool hasTwoDataBytes() const
Check whether this message has one or two data bytes.
union MIDIMessageQueue::MIDIMessageQueueElement::Message message
uint8_t data2
First MIDI data byte.
uint8_t header
MIDI status byte (message type and channel).
uint8_t data1
First MIDI data byte.
uint8_t getNumberOfDataBytes() const
Get the number of data bytes of this type of System Common message.
const uint8_t * data