Control Surface stm32
MIDI Control Surface library for Arduino
TeensyHostMIDI.ipp
Go to the documentation of this file.
1#define CS_MIDI_USB_ASSERT(a) \
2 do { \
3 if (!(a)) { \
4 Serial.println("USB Host MIDI: Assertion failed: " #a); \
5 Serial.flush(); \
6 while (1) \
7 yield(); \
8 } \
9 } while (0)
10
12
13constexpr static auto mo_rel = std::memory_order_release;
14constexpr static auto mo_acq = std::memory_order_acquire;
15constexpr static auto mo_rlx = std::memory_order_relaxed;
16constexpr static auto mo_acq_rel = std::memory_order_acq_rel;
17
18// ------------------------------- INITIALIZATION ------------------------------
19
20template <uint16_t MaxPacketSize>
22 contribute_Pipes(mypipes, len(mypipes));
23 contribute_Transfers(mytransfers, len(mytransfers));
24 contribute_String_Buffers(mystring_bufs, len(mystring_bufs));
25 rxpipe = nullptr;
26 txpipe = nullptr;
27 driver_ready_for_device(this);
28}
29
30template <uint16_t MaxPacketSize>
31bool TeensyHostMIDI<MaxPacketSize>::claim(Device_t *dev, int type,
32 const uint8_t *descriptors,
33 uint32_t len) {
34 // only claim at interface level
35 if (type != 1)
36 return false;
37
38 if (!claim_if_midi(dev, type, descriptors, len)) {
39 USBHost::println_("This interface is not MIDI");
40 return false;
41 }
42 USBHost::println_("claimed");
43
44 // if an IN endpoint was found, create its pipe
45 if (rx_ep && rx_size <= PacketSize) {
46 rxpipe = new_Pipe(dev, rx_ep_type, rx_ep, 1, rx_size);
47 if (rxpipe) {
48 rxpipe->callback_function = rx_callback;
49
50 reading.available.store(0, mo_rlx);
51 reading.read_idx.store(0, mo_rlx);
52 reading.write_idx.store(0, mo_rlx);
53 reading.reading.store(true, mo_rlx);
55 }
56 } else {
57 rxpipe = nullptr;
58 }
59
60 // if an OUT endpoint was found, create its pipe
61 if (tx_ep && tx_size <= PacketSize) {
62 txpipe = new_Pipe(dev, tx_ep_type, tx_ep, 0, tx_size);
63 if (txpipe) {
64 txpipe->callback_function = tx_callback;
65
66 writing.send_timeout.store(nullptr, mo_rlx);
67 writing.buffers[0].size.store(0, mo_rlx);
68 writing.buffers[0].ready_to_send.store(false, mo_rlx);
69 writing.buffers[1].size.store(0, mo_rlx);
70 writing.buffers[1].ready_to_send.store(false, mo_rlx);
72 writing.sending.store(nullptr, mo_rlx);
73 }
74 } else {
75 txpipe = nullptr;
76 }
77
78 // claim if either pipe created
79 return (rxpipe || txpipe);
80}
81
82template <uint16_t MaxPacketSize>
84 rxpipe = nullptr;
85 txpipe = nullptr;
86}
87
88// ------------------------------ USB CALLBACKS ----------------------------- //
89
90template <uint16_t MaxPacketSize>
91void TeensyHostMIDI<MaxPacketSize>::rx_callback(const Transfer_t *transfer) {
92 if (transfer->driver)
93 reinterpret_cast<TeensyHostMIDI *>(transfer->driver)
94 ->out_callback(transfer);
95}
96template <uint16_t MaxPacketSize>
97void TeensyHostMIDI<MaxPacketSize>::tx_callback(const Transfer_t *transfer) {
98 if (transfer->driver)
99 reinterpret_cast<TeensyHostMIDI *>(transfer->driver)
100 ->in_callback(transfer);
101}
102
103template <uint16_t MaxPacketSize>
104void TeensyHostMIDI<MaxPacketSize>::timer_event(USBDriverTimer *whichtimer) {
105 if (whichtimer == &write_timeout)
107}
108
109// ----------------------------- USB READ/WRITE ----------------------------- //
110
111template <uint16_t MaxPacketSize>
113 uint32_t size) {
114 // digitalWrite(3, HIGH);
115 __disable_irq();
116 if (txpipe)
117 queue_Data_Transfer(txpipe, buffer, size, this);
118 __enable_irq();
119}
120template <uint16_t MaxPacketSize>
122 uint32_t size) {
123 // digitalWrite(3, HIGH);
124 if (txpipe)
125 queue_Data_Transfer(txpipe, buffer, size, this);
126}
127template <uint16_t MaxPacketSize>
128uint32_t
129TeensyHostMIDI<MaxPacketSize>::write_finish(const Transfer_t *transfer) {
130 // digitalWrite(3, LOW);
131 // delayMicroseconds(200);
132 return transfer->length - ((transfer->qtd.token >> 16) & 0x7FFF);
133}
134
135template <uint16_t MaxPacketSize>
136void TeensyHostMIDI<MaxPacketSize>::read_start(uint8_t *buffer, uint32_t size) {
137 __disable_irq();
138 if (rxpipe)
139 queue_Data_Transfer(rxpipe, buffer, size, this);
140 __enable_irq();
141}
142template <uint16_t MaxPacketSize>
144 uint32_t size) {
145 if (rxpipe)
146 queue_Data_Transfer(rxpipe, buffer, size, this);
147}
148template <uint16_t MaxPacketSize>
149uint32_t
150TeensyHostMIDI<MaxPacketSize>::read_finish(const Transfer_t *transfer) {
151 return transfer->length - ((transfer->qtd.token >> 16) & 0x7FFF);
152}
153
154// ---------------------------------- WRITING ----------------------------------
155
156template <uint16_t MaxPacketSize>
157void TeensyHostMIDI<MaxPacketSize>::write(uint32_t msg) {
158 write_impl(&msg, 1, false); // blocking
159}
160
161template <uint16_t MaxPacketSize>
162void TeensyHostMIDI<MaxPacketSize>::write(const uint32_t *msgs,
163 uint32_t num_msgs) {
164 const uint32_t *end = msgs + num_msgs;
165 while (msgs != end)
166 msgs += write_impl(msgs, end - msgs, false); // blocking
167}
168
169template <uint16_t MaxPacketSize>
170uint32_t TeensyHostMIDI<MaxPacketSize>::write_nonblock(const uint32_t *msgs,
171 uint32_t num_msgs) {
172 uint32_t total_sent = 0, sent = 1;
173 while (total_sent < num_msgs && sent != 0) {
174 sent = write_impl(msgs + total_sent, num_msgs - total_sent, true);
175 total_sent += sent;
176 }
177 return total_sent;
178}
179
180template <uint16_t MaxPacketSize>
182 uint32_t active_idx, size;
183 wbuffer_t *writebuf;
184 std::tie(active_idx, writebuf, size) = read_writebuf_size();
185 if (size > 0)
186 send_now_impl_nonblock(active_idx);
187}
188
189/*
190 * write(...)
191 * - called by user
192 * - adds data to the transmit buffer
193 * - starts a timeout to send the data (even if the buffer isn't full yet)
194 * - sends the transmit buffer to the USB stack when full
195 * - blocks if there's no space left in the transmit buffers
196 *
197 * timeout_callback()
198 * - called from a timer interrupt, a fixed time after the first write to a
199 * buffer
200 * - sends the transmit buffer even if it isn't full yet
201 * - if a previous transmission is still in progress, schedules it to be sent
202 * immediately after the current transmission finishes.
203 *
204 * in_callback()
205 * - called by the USB stack when a transmission is complete
206 * - finishes the transmission
207 * - releases the “sending” lock
208 * - starts a new transmission if it was scheduled while the previous
209 * transmission was in progress
210 */
211
212/*
213 * - Both send buffers start out empty (size = 0), the first buffer is active
214 * (`writebuffer = 0`, which means that the first buffer is the one being
215 * filled with the outgoing data).
216 * - The `write()` function is called.
217 * - The message is added to the active buffer and the size is incremented.
218 * (Writing the data happens inside of an atomic compare-and-swap loop, if in
219 * the meantime an interrupts occurs that sends the buffer we just wrote to,
220 * we write the data again to the new writing buffer.)
221 * - If the active buffer is now full, the packet is sent to the USB stack, and
222 * the buffers are swapped. If the previous buffer wasn't completely sent yet,
223 * it is scheduled to be sent when the previous transmission is complete.
224 * - If this was the first message in the buffer, a timeout is started.
225 * - If the active buffer wasn't full, nothing is actually sent, and the
226 * `write()` function exits.
227 * - When the timeout fires, `timeout_callback()` is called (from a timer
228 * interrupt).
229 * - If there is data in the active buffer, it's sent over USB. If the previous
230 * buffer wasn't completely sent yet, it's not possible to send now, and it's
231 * scheduled to be sent later.
232 * - When a buffer has been sent, `in_callback()` is called (from a USB
233 * interrupt).
234 * - The write is finished, the “sending” lock is released and the buffer size
235 * is reset to zero to let the main `write()` function know that it can write
236 * to this buffer. If the other buffer was scheduled to be sent while we were
237 * sending this packet, immediately send the next one.
238 */
239
240template <uint16_t MaxPacketSize>
243 uint32_t active_idx;
244 wbuffer_t *writebuffer;
245 uint32_t size;
246 // active_idx must be newer than active size.
247 active_idx = writing.active_writebuffer.load(mo_rlx);
248 writebuffer = &writing.buffers[active_idx];
249 size = writebuffer->size.load(mo_acq);
250 // If the size was set to reserved, the active buffer must have been
251 // swapped as well. If both buffers are empty, the active buffer might have
252 // been swapped as well (but not necessarily).
253 if (size == SizeReserved || size == 0) {
254 uint32_t old_idx = active_idx;
255 active_idx = writing.active_writebuffer.load(mo_rlx);
256 if (old_idx != active_idx) {
257 writebuffer = &writing.buffers[active_idx];
258 size = writebuffer->size.load(mo_acq);
259 }
260 }
262 return std::make_tuple(active_idx, writebuffer, size);
263}
264
265template <uint16_t MaxPacketSize>
266uint32_t TeensyHostMIDI<MaxPacketSize>::write_impl(const uint32_t *msgs,
267 uint32_t num_msgs,
268 bool nonblocking) {
269 if (!txpipe)
270 return 0;
271
272 uint32_t active_idx; // Index of the buffer currently being written to.
273 wbuffer_t *writebuf; // Pointer to buffer currently being written to.
274 uint32_t size; // Current size of that buffer (i.e. the first index we
275 // can write to).
276
277 // Read the active write buffer and its current size
278 std::tie(active_idx, writebuf, size) = read_writebuf_size();
279 // Make sure that there's space in the buffer for us to write data
280 if (size >= PacketSize) {
281 // If there's no space
282 if (nonblocking) {
283 // either return without blocking
284 return 0;
285 } else {
286 // Or wait until the active writing buffer changes to an empty one
287 auto old_idx = active_idx;
288 do {
289 yield(); // spin
290 std::tie(active_idx, writebuf, size) = read_writebuf_size();
291 } while (size >= PacketSize);
292 CS_MIDI_USB_ASSERT(old_idx != active_idx);
293 CS_MIDI_USB_ASSERT(size == 0);
294 }
295 }
297
298 // Copy the data into the active buffer
299 uint32_t free_size = std::min(PacketSize - size, num_msgs * 4u);
300 CS_MIDI_USB_ASSERT(free_size % 4 == 0);
301 memcpy(&writebuf->buffer[size], msgs, free_size);
302 uint32_t newsize = size + free_size;
303
304 // Update the size of the buffer
305 while (!writebuf->size.compare_exchange_weak(size, newsize, mo_rel)) {
306 // If the size changed while we were writing the data, this means
307 // that a sender in a different thread or ISR was/is sending the active
308 // buffer (so it is no longer the active buffer).
309
310 // Read the new write buffer and its size.
311 std::tie(active_idx, writebuf, size) = read_writebuf_size();
312 // CS_MIDI_USB_ASSERT(size == 0); // Only true with cmp_xchg_strong
313
314 // Copy the data into the new buffer
315 free_size = std::min(PacketSize - size, num_msgs * 4u);
316 memcpy(&writebuf->buffer[size], msgs, free_size);
317 newsize = size + free_size;
318 }
319
320 // If the buffer is now full, send it immediately (but don't block)
321 if (newsize == PacketSize) {
322 send_now_impl_nonblock(active_idx);
323 }
324 // If this is the first data in the buffer, start a timer that will send
325 // the buffer after a given timeout
326 else if (size == 0) {
327 wbuffer_t *old = writing.send_timeout.exchange(writebuf, mo_rel);
328 CS_MIDI_USB_ASSERT(old == nullptr);
329 std::atomic_signal_fence(mo_rel);
330 /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Timeout] ▼ */
332 }
333
334 // How many messages were added to the transmit buffer
335 return free_size / 4u;
336}
337
338template <uint16_t MaxPacketSize>
340 uint32_t active_idx) {
341 // Disable the timeout
342 auto old_timeout = writing.send_timeout.exchange(nullptr, mo_acq);
343 if (old_timeout != nullptr)
344 write_timeout.stop();
345
346 wbuffer_t *writebuffer = &writing.buffers[active_idx];
347
348 // Schedule the active buffer to be sent immediately after the previous one
349 bool old_ready = writebuffer->ready_to_send.exchange(true, mo_rlx);
350 if (old_ready)
351 // If the send flag was already set, there's nothing left for us to do
352 return true;
353
354 // If we were the ones who set the send flag, try to send the packet now
355
356 // Try to acquire the “sending” lock:
357 wbuffer_t *send = nullptr;
358 if (!writing.sending.compare_exchange_strong(send, writebuffer, mo_acq))
359 // If we failed to get the lock, he previous transmission is still in
360 // progress, so we can't send now, but sending of our buffer is
361 // scheduled using the send flag, so we can just return.
362 return false;
363
364 // Swap the active write buffer
365 CS_MIDI_USB_ASSERT(writing.buffers[!active_idx].size.load(mo_rlx) == 0);
366 writing.active_writebuffer.store(!active_idx, mo_rlx);
367
368 // Get the size of the buffer to send and atomically set it to reserved
369 uint32_t size = writebuffer->size.exchange(SizeReserved, mo_acq_rel);
371 CS_MIDI_USB_ASSERT(size != 0);
372
373 // Actually send the buffer
374 std::atomic_signal_fence(mo_rel);
375 /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Write-Start] ▼ */
376 write_start(writebuffer->buffer, size);
377 // “sending” lock is released in USB callback
378 return true;
379}
380
381template <uint16_t MaxPacketSize>
383 std::atomic_signal_fence(mo_acq);
384 /* ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ [Timeout] ▲ */
385
386 // Check which buffer (if any) to send
387 wbuffer_t *sendbuffer = writing.send_timeout.exchange(nullptr, mo_acq);
388 if (!sendbuffer)
389 return;
390
391 // Schedule the active buffer to be sent immediately after the previous one
392 bool old_ready = sendbuffer->ready_to_send.exchange(true, mo_rlx);
393 if (old_ready)
394 // If the send flag was already set, there's nothing left for us to do
395 return;
396
397 // Try to get the send lock
398 wbuffer_t *expected = nullptr;
399 if (!writing.sending.compare_exchange_strong(expected, sendbuffer, mo_acq))
400 // If we failed to get the lock, he previous transmission is still in
401 // progress, so we can't send now, but sending of our buffer is
402 // scheduled using the send flag, so we can just return.
403 return;
404
405 // Swap the buffers
406 uint32_t sendbuf_idx = sendbuffer - writing.buffers;
407 CS_MIDI_USB_ASSERT(writing.buffers[!sendbuf_idx].size.load(mo_rlx) == 0);
408 writing.active_writebuffer.store(!sendbuf_idx, mo_rlx);
409
410 // Get the size of the buffer to send and atomically set it to reserved
411 uint32_t size = sendbuffer->size.exchange(SizeReserved, mo_acq_rel);
413 CS_MIDI_USB_ASSERT(size != 0);
414
415 // Actually send the buffer
416 std::atomic_signal_fence(mo_rel);
417 /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Write-Start] ▼ */
418 write_start_isr(sendbuffer->buffer, size);
419}
420
421template <uint16_t MaxPacketSize>
422void TeensyHostMIDI<MaxPacketSize>::in_callback(const Transfer_t *transfer) {
423 std::atomic_signal_fence(mo_acq);
424 /* ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ [Write-Start] ▲ */
425
426 write_finish(transfer);
427
428 // Release the “sending” lock
429 wbuffer_t *sent = writing.sending.exchange(nullptr, mo_rel);
430 uint32_t sent_idx = sent - writing.buffers;
431 uint32_t next_idx = !sent_idx;
432 wbuffer_t *next = &writing.buffers[next_idx];
433
434 // Check if the other buffer was scheduled to be sent immediately after the
435 // one we just sent
436 uint32_t next_ready = next->ready_to_send.load(mo_rlx);
437 if (next_ready) {
438 // Acquire the “sending” lock again
439 wbuffer_t *old_sending = writing.sending.exchange(next, mo_rlx);
440 // Since interrupt handlers are atomic blocks, acquiring the lock cannot
441 // fail. If multiple threads are used, an extra flag is needed to
442 // prevent the main thread and the timeout thread from stealing the lock
443 // after we released it.
444 CS_MIDI_USB_ASSERT(old_sending == nullptr);
445
446 // The transmission is done, reset the size of the buffer to 0 so the
447 // main thread can write to it again.
448 sent->ready_to_send.store(false, mo_rlx);
449 uint32_t oldsize = sent->size.exchange(0, mo_rel);
451
452 // Swap the buffers
453 writing.active_writebuffer.store(!next_idx, mo_rlx);
454
455 // Get the size of the buffer to send and atomically set it to reserved
456 uint32_t size = next->size.exchange(SizeReserved, mo_acq_rel);
458 CS_MIDI_USB_ASSERT(size != 0);
459
460 // Actually send the buffer
461 std::atomic_signal_fence(mo_rel);
462 /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Write-Start] ▼ */
463 write_start_isr(next->buffer, size);
464 } else {
465 // The transmission is done, reset the size of the buffer to 0 so the
466 // main thread can write to it again.
467 sent->ready_to_send.store(false, mo_rlx);
468 uint32_t oldsize = sent->size.exchange(0, mo_rel);
470 }
471}
472
473// ---------------------------------- READING ----------------------------------
474
475template <uint16_t MaxPacketSize>
477 // Check if there are any bytes available for reading
478 uint32_t available = reading.available.load(mo_acq);
479 if (available == 0)
480 return 0;
481
482 // Get the buffer with received data
483 uint32_t r = reading.read_idx.load(mo_rlx);
484 rbuffer_t &readbuffer = reading.buffers[r];
485
486 // Read the data from the buffer (data is at least as new as available)
487 uint32_t data;
488 memcpy(&data, &readbuffer.buffer[readbuffer.index], 4);
489
490 readbuffer.index += 4;
491 // If we've read all messages from this buffer
492 if (readbuffer.index == readbuffer.size) {
493 // Increment the read index (and wrap around)
494 r = (r + 1 == NumRxPackets) ? 0 : r + 1;
495 reading.read_idx.store(r, mo_rlx);
496 reading.available.fetch_sub(1, mo_rel);
497 // There is now space in the queue
498 // Check if the next read is already in progress
499 if (reading.reading.exchange(true, mo_acq) == false) {
500 // If not, start the next read now
501 uint32_t w = reading.write_idx.load(mo_rlx);
503 }
504 }
505
506 return data;
507}
508
509template <uint16_t MaxPacketSize>
510void TeensyHostMIDI<MaxPacketSize>::out_callback(const Transfer_t *transfer) {
512 // Check how many bytes were read
513 uint32_t num_bytes_read = read_finish(transfer);
514 CS_MIDI_USB_ASSERT(num_bytes_read % 4 == 0);
515
516 // If no bytes were read, start a new read into the same buffer
517 uint32_t w = reading.write_idx.load(mo_rlx);
518 if (num_bytes_read == 0) {
520 return;
521 }
522
523 // Otherwise, store how many bytes were read
524 rbuffer_t &writebuffer = reading.buffers[w];
525 writebuffer.index = 0;
526 writebuffer.size = num_bytes_read;
527 // Increment the write index (and wrap around)
528 w = (w + 1 == NumRxPackets) ? 0 : w + 1;
529 reading.write_idx.store(w, mo_rlx);
530
531 // Update number of available buffers in the queue
532 uint32_t available = reading.available.fetch_add(1, mo_acq_rel) + 1;
533 // If there's still space left in the queue, start the next read
534 if (available < NumRxPackets)
536 // Otherwise, release the “reading” lock
537 else
538 reading.reading.store(false, mo_rel);
539}
540
541template <uint16_t MaxPacketSize>
543template <uint16_t MaxPacketSize>
545
547
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
static constexpr auto mo_rel
#define CS_MIDI_USB_ASSERT(a)
static constexpr auto mo_rlx
static constexpr auto mo_acq_rel
static constexpr auto mo_acq
Teensy USB Host MIDI driver.
void disconnect() override
uint32_t read_finish(const Transfer_t *transfer)
void read_start(uint8_t *buffer, uint32_t size)
static constexpr uint32_t PacketSize
USB packet size. Must be a power of two.
std::atomic< uint32_t > size
uint32_t write_finish(const Transfer_t *transfer)
uint32_t write_nonblock(const uint32_t *msgs, uint32_t num_msgs)
Send multiple MIDI USB messages without blocking.
bool claim_if_midi(Device_t *device, int type, const uint8_t *descriptors, uint32_t len)
void timeout_callback()
void timer_event(USBDriverTimer *whichtimer) override
static constexpr uint32_t SizeReserved
uint32_t read()
Try reading a 4-byte MIDI USB message.
bool claim(Device_t *device, int type, const uint8_t *descriptors, uint32_t len) override
struct TeensyHostMIDI::Reading::Buffer buffers[NumRxPackets]
void send_now()
Try sending the buffered data now.
USBDriverTimer write_timeout
std::atomic< uint32_t > write_idx
std::atomic< uint32_t > active_writebuffer
void in_callback(const Transfer_t *transfer)
std::atomic< uint32_t > available
writebuf_size_tup read_writebuf_size()
void read_start_isr(uint8_t *buffer, uint32_t size)
struct TeensyHostMIDI::Reading reading
typename Reading::Buffer rbuffer_t
static constexpr size_t len(T(&)[N])
std::atomic< Buffer * > sending
uint32_t write_impl(const uint32_t *msgs, uint32_t num_msgs, bool nonblocking)
void write_start(uint8_t *buffer, uint32_t size)
typename Writing::Buffer wbuffer_t
static void tx_callback(const Transfer_t *transfer)
std::atomic< uint32_t > read_idx
std::atomic< bool > reading
bool send_now_impl_nonblock(uint32_t activebuf_idx)
void out_callback(const Transfer_t *transfer)
std::atomic< Buffer * > send_timeout
static constexpr uint32_t NumRxPackets
static void rx_callback(const Transfer_t *transfer)
struct TeensyHostMIDI::Writing::Buffer buffers[2]
void write(uint32_t msg)
Send a MIDI USB message.
strbuf_t mystring_bufs[1]
void write_start_isr(uint8_t *buffer, uint32_t size)
struct TeensyHostMIDI::Writing writing
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