Control Surface new-input
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 
4 #include "BLEMIDI/ESP32/midi.h"
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
20  ScopedThreadConfig(4096, 3, true, "SendBLEMIDI");
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 
192 void 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
206  while (!queue.push(parser.getSysExMessage(),
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
230  if (!queue.pop(incomingMessage))
232  return incomingMessage.eventType;
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 
262  return incomingMessage.timestamp;
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 
286 extern "C" void BluetoothMIDI_Interface_midi_mtu_callback(uint16_t mtu) {
288 }
289 
290 extern "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)
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.
void reset()
Reset the builder to start a new packet.
const std::vector< uint8_t > & getPacket() const
Return the packet as a vector of bytes.
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
#define ERROR(msg, errc)
Print the error message and error code, and stop the execution if FATAL_ERRORS are enabled.
Definition: Error.hpp:42
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