Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
buffer.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <cstdint>
5#include <vector>
6#include <algorithm>
7#include <initializer_list>
8#include <limits>
9
10namespace pulp::midi {
11
32public:
33 MidiBuffer() = default;
34 MidiBuffer(const MidiBuffer& other)
35 : ump_(other.ump_),
36 sysex_copy_payload_capacity_(other.sysex_copy_payload_capacity_),
37 limit_to_reserved_capacity_(other.limit_to_reserved_capacity_),
38 dropped_events_(other.dropped_events_),
39 dropped_sysex_events_(other.dropped_sysex_events_) {
40 copy_event_storage(other);
41 reserve_copied_sysex_payloads(other);
42 }
44 if (this != &other) {
45 sysex_.clear();
46 ump_ = other.ump_;
47 sysex_copy_payload_capacity_ = other.sysex_copy_payload_capacity_;
48 limit_to_reserved_capacity_ = other.limit_to_reserved_capacity_;
49 dropped_events_ = other.dropped_events_;
50 dropped_sysex_events_ = other.dropped_sysex_events_;
51 copy_event_storage(other);
52 reserve_copied_sysex_payloads(other);
53 }
54 return *this;
55 }
56 MidiBuffer(MidiBuffer&& other) noexcept
57 : events_(std::move(other.events_)),
58 sort_index_(std::move(other.sort_index_)),
59 sort_reorder_(std::move(other.sort_reorder_)),
60 sysex_copy_payload_pool_(std::move(other.sysex_copy_payload_pool_)),
61 sysex_(std::move(other.sysex_)),
62 ump_(other.ump_),
63 sysex_copy_payload_capacity_(other.sysex_copy_payload_capacity_),
64 limit_to_reserved_capacity_(other.limit_to_reserved_capacity_),
65 dropped_events_(other.dropped_events_),
66 dropped_sysex_events_(other.dropped_sysex_events_) {
67 rebind_sysex_payloads_to_pool();
68 other.ump_ = nullptr;
69 other.sysex_copy_payload_capacity_ = 0;
70 other.limit_to_reserved_capacity_ = false;
71 other.dropped_events_ = 0;
72 other.dropped_sysex_events_ = 0;
73 }
74 MidiBuffer& operator=(MidiBuffer&& other) noexcept {
75 if (this != &other) {
76 sysex_.clear();
77 events_ = std::move(other.events_);
78 sort_index_ = std::move(other.sort_index_);
79 sort_reorder_ = std::move(other.sort_reorder_);
80 sysex_copy_payload_pool_ = std::move(other.sysex_copy_payload_pool_);
81 sysex_ = std::move(other.sysex_);
82 ump_ = other.ump_;
83 sysex_copy_payload_capacity_ = other.sysex_copy_payload_capacity_;
84 limit_to_reserved_capacity_ = other.limit_to_reserved_capacity_;
85 dropped_events_ = other.dropped_events_;
86 dropped_sysex_events_ = other.dropped_sysex_events_;
87 rebind_sysex_payloads_to_pool();
88 other.ump_ = nullptr;
89 other.sysex_copy_payload_capacity_ = 0;
90 other.limit_to_reserved_capacity_ = false;
91 other.dropped_events_ = 0;
92 other.dropped_sysex_events_ = 0;
93 }
94 return *this;
95 }
96
98 bool add(const MidiEvent& event) {
99 if (!can_append_event()) {
100 record_event_drop();
101 return false;
102 }
103 events_.push_back(event);
104 return true;
105 }
106
108 bool add(MidiEvent&& event) {
109 if (!can_append_event()) {
110 record_event_drop();
111 return false;
112 }
113 events_.push_back(std::move(event));
114 return true;
115 }
116
118 void clear() {
119 events_.clear();
120 dropped_events_ = 0;
121 }
122 bool empty() const { return events_.empty(); }
123 std::size_t size() const { return events_.size(); }
124 void reserve_events(std::size_t capacity) {
125 events_.reserve(capacity);
126 reserve_sort_scratch(capacity);
127 }
128 void reserve_sysex(std::size_t capacity) { sysex_.reserve(capacity); }
129
131 void reserve(std::size_t event_capacity,
132 std::size_t sysex_capacity = 0,
133 std::size_t sysex_copy_payload_capacity = 0) {
134 events_.reserve(event_capacity);
135 reserve_sort_scratch(event_capacity);
136 sysex_.reserve(sysex_capacity);
140 }
141 }
142
148 void set_realtime_capacity_limit(bool enabled = true) {
149 limit_to_reserved_capacity_ = enabled;
150 }
151 bool realtime_capacity_limited() const { return limit_to_reserved_capacity_; }
152 std::size_t event_capacity() const { return events_.capacity(); }
153 std::size_t sysex_capacity() const { return sysex_.capacity(); }
154 std::size_t sysex_copy_payload_capacity() const {
155 return sysex_copy_payload_capacity_;
156 }
157 std::uint32_t dropped_event_count() const { return dropped_events_; }
158 std::uint32_t dropped_sysex_count() const { return dropped_sysex_events_; }
159
177 void sort() {
178 const std::size_t n = events_.size();
179 if (n < 2) return;
180 sort_index_.resize(n);
181 for (std::size_t i = 0; i < n; ++i) {
182 sort_index_[i] = static_cast<std::uint32_t>(i);
183 }
184 const std::vector<MidiEvent>& ev = events_;
185 std::sort(sort_index_.begin(), sort_index_.end(),
186 [&ev](std::uint32_t a, std::uint32_t b) {
187 if (ev[a].sample_offset != ev[b].sample_offset) {
188 return ev[a].sample_offset < ev[b].sample_offset;
189 }
190 return a < b; // tie-break = original insertion order
191 });
192 sort_reorder_.resize(n);
193 for (std::size_t i = 0; i < n; ++i) {
194 sort_reorder_[i] = std::move(events_[sort_index_[i]]);
195 }
196 events_.swap(sort_reorder_);
197 }
198
199 auto begin() { return events_.begin(); }
200 auto end() { return events_.end(); }
201 auto begin() const { return events_.begin(); }
202 auto end() const { return events_.end(); }
203
204 const MidiEvent& operator[](std::size_t index) const { return events_[index]; }
205
212 void attach_ump(class UmpBuffer* ump) { ump_ = ump; }
213
217 const class UmpBuffer* ump() const { return ump_; }
218 class UmpBuffer* ump() { return ump_; }
219
220 // ── SysEx sidecar ────────────────────────────────────────────────────
221 //
222 // choc::midi::ShortMessage is fixed 3 bytes; system-exclusive payloads
223 // can run to kilobytes. Sysex therefore travels in a parallel vector
224 // whose entries are referenced by sample_offset the same way MidiEvent
225 // entries are. Format adapters that carry sysex (CoreMIDI, VST3 event
226 // list with kData type, CLAP CLAP_EVENT_MIDI_SYSEX) populate this
227 // alongside the short-message stream; plugins that don't care can
228 // ignore it.
230 public:
231 SysexPayload() = default;
232 SysexPayload(std::initializer_list<uint8_t> values) : bytes_(values) {}
233 SysexPayload(std::vector<uint8_t> bytes)
234 : bytes_(std::move(bytes)) {}
235
237 : bytes_(other.bytes_),
238 realtime_pool_backed_(other.realtime_pool_backed_) {}
240 if (this != &other) {
241 recycle_now();
242 bytes_ = other.bytes_;
243 realtime_pool_backed_ = other.realtime_pool_backed_;
244 recycle_pool_ = nullptr;
245 recycle_capacity_ = 0;
246 }
247 return *this;
248 }
249
250 ~SysexPayload() { recycle_now(); }
251
254
255 SysexPayload& operator=(std::initializer_list<uint8_t> values) {
256 mutable_bytes().assign(values.begin(), values.end());
257 return *this;
258 }
259 SysexPayload& operator=(std::vector<uint8_t> bytes) {
260 recycle_now();
261 bytes_ = std::move(bytes);
262 realtime_pool_backed_ = false;
263 return *this;
264 }
265
266 std::size_t size() const { return view().size(); }
267 bool empty() const { return view().empty(); }
268 std::size_t capacity() const { return view().capacity(); }
269 const uint8_t* data() const { return view().data(); }
270 uint8_t* data() { return mutable_bytes().data(); }
271 const uint8_t& front() const { return view().front(); }
272 uint8_t& front() { return mutable_bytes().front(); }
273 const uint8_t& back() const { return view().back(); }
274 uint8_t& back() { return mutable_bytes().back(); }
275 const uint8_t& operator[](std::size_t index) const {
276 return view()[index];
277 }
278 uint8_t& operator[](std::size_t index) {
279 return mutable_bytes()[index];
280 }
281 auto begin() const { return view().begin(); }
282 auto end() const { return view().end(); }
283 auto begin() { return mutable_bytes().begin(); }
284 auto end() { return mutable_bytes().end(); }
285 void reserve(std::size_t capacity) { mutable_bytes().reserve(capacity); }
286 void resize(std::size_t size) { mutable_bytes().resize(size); }
287 void clear() { mutable_bytes().clear(); }
288 void push_back(uint8_t byte) { mutable_bytes().push_back(byte); }
289
290 std::vector<uint8_t> to_vector() const { return view(); }
291
292 friend bool operator==(const SysexPayload& lhs,
293 const std::vector<uint8_t>& rhs) {
294 return lhs.view() == rhs;
295 }
296 friend bool operator==(const std::vector<uint8_t>& lhs,
297 const SysexPayload& rhs) {
298 return lhs == rhs.view();
299 }
300 friend bool operator!=(const SysexPayload& lhs,
301 const std::vector<uint8_t>& rhs) {
302 return !(lhs == rhs);
303 }
304 friend bool operator!=(const std::vector<uint8_t>& lhs,
305 const SysexPayload& rhs) {
306 return !(lhs == rhs);
307 }
308
309 private:
310 friend class MidiBuffer;
311
312 const std::vector<uint8_t>& view() const { return bytes_; }
313 std::vector<uint8_t>& mutable_bytes() { return bytes_; }
314 std::vector<uint8_t> release_owned_vector() {
315 recycle_pool_ = nullptr;
316 realtime_pool_backed_ = false;
317 return std::move(bytes_);
318 }
319 bool realtime_pool_backed() const { return realtime_pool_backed_; }
320 void set_realtime_pool_backed(
321 bool backed,
322 std::vector<std::vector<uint8_t>>* recycle_pool = nullptr,
323 std::size_t recycle_capacity = 0) {
324 realtime_pool_backed_ = backed;
325 recycle_pool_ = backed ? recycle_pool : nullptr;
326 recycle_capacity_ = backed ? recycle_capacity : 0;
327 }
328 void recycle_now() {
329 if (recycle_pool_ != nullptr && recycle_capacity_ > 0
330 && bytes_.capacity() >= recycle_capacity_
331 && recycle_pool_->size() < recycle_pool_->capacity()) {
332 bytes_.clear();
333 recycle_pool_->push_back(std::move(bytes_));
334 }
335 recycle_pool_ = nullptr;
336 recycle_capacity_ = 0;
337 realtime_pool_backed_ = false;
338 }
339
340 std::vector<uint8_t> bytes_;
341 std::vector<std::vector<uint8_t>>* recycle_pool_ = nullptr;
342 std::size_t recycle_capacity_ = 0;
343 bool realtime_pool_backed_ = false;
344 };
345
346 struct SysexEvent {
348 int32_t sample_offset = 0;
349 double timestamp = 0.0;
350 };
351
352 bool add_sysex(std::vector<uint8_t> data, int32_t sample_offset = 0, double ts = 0.0) {
353 if (!can_append_sysex()) {
354 record_sysex_drop();
355 return false;
356 }
357 sysex_.emplace_back();
358 auto& event = sysex_.back();
359 event.data = std::move(data);
360 event.sample_offset = sample_offset;
361 event.timestamp = ts;
362 return true;
363 }
364 bool add_sysex(SysexEvent&& event) {
365 if (event.data.realtime_pool_backed()) {
366 return add_sysex_copy(event.data.data(),
367 event.data.size(),
368 event.sample_offset,
369 event.timestamp);
370 }
371 if (!can_append_sysex()) {
372 record_sysex_drop();
373 return false;
374 }
375 sysex_.emplace_back();
376 auto& dst = sysex_.back();
377 dst.data = event.data.release_owned_vector();
378 dst.sample_offset = event.sample_offset;
379 dst.timestamp = event.timestamp;
380 return true;
381 }
382 bool add_sysex_copy(const uint8_t* data,
383 std::size_t size,
384 int32_t sample_offset = 0,
385 double ts = 0.0) {
386 if (limit_to_reserved_capacity_) {
387 return add_sysex_copy_realtime(data, size, sample_offset, ts);
388 }
389 if (!can_append_sysex()) {
390 record_sysex_drop();
391 return false;
392 }
393 return add_sysex(std::vector<uint8_t>(data, data + size),
394 sample_offset,
395 ts);
396 }
397 void reserve_sysex_copy_payloads(std::size_t payload_count,
398 std::size_t payload_capacity) {
399 sysex_copy_payload_capacity_ = payload_capacity;
400 sysex_copy_payload_pool_.clear();
401 sysex_copy_payload_pool_.reserve(payload_count);
402 for (std::size_t i = 0; i < payload_count; ++i) {
403 std::vector<uint8_t> payload;
404 payload.reserve(payload_capacity);
405 sysex_copy_payload_pool_.push_back(std::move(payload));
406 }
407 }
408 void clear_sysex() {
409 for (auto& event : sysex_) {
410 recycle_sysex_payload(event.data.release_owned_vector());
411 }
412 sysex_.clear();
413 dropped_sysex_events_ = 0;
414 }
415 std::size_t sysex_size() const { return sysex_.size(); }
416 const std::vector<SysexEvent>& sysex() const { return sysex_; }
417 std::vector<SysexEvent>& sysex() { return sysex_; }
418
419private:
420 bool can_append_event() const {
421 return !limit_to_reserved_capacity_ || events_.size() < events_.capacity();
422 }
423 bool can_append_sysex() const {
424 return !limit_to_reserved_capacity_ || sysex_.size() < sysex_.capacity();
425 }
426 bool add_sysex_copy_realtime(const uint8_t* data,
427 std::size_t size,
428 int32_t sample_offset,
429 double ts) {
430 if (!can_append_sysex() || sysex_copy_payload_pool_.empty()
431 || size > sysex_copy_payload_capacity_) {
432 record_sysex_drop();
433 return false;
434 }
435 auto payload = std::move(sysex_copy_payload_pool_.back());
436 sysex_copy_payload_pool_.pop_back();
437 payload.resize(size);
438 std::copy(data, data + size, payload.begin());
439 sysex_.emplace_back();
440 auto& event = sysex_.back();
441 event.data = std::move(payload);
442 event.data.set_realtime_pool_backed(true,
443 &sysex_copy_payload_pool_,
444 sysex_copy_payload_capacity_);
445 event.sample_offset = sample_offset;
446 event.timestamp = ts;
447 return true;
448 }
449 void recycle_sysex_payload(std::vector<uint8_t>&& payload) {
450 if (sysex_copy_payload_capacity_ == 0
451 || payload.capacity() < sysex_copy_payload_capacity_
452 || sysex_copy_payload_pool_.size()
453 >= sysex_copy_payload_pool_.capacity()) {
454 return;
455 }
456 payload.clear();
457 sysex_copy_payload_pool_.push_back(std::move(payload));
458 }
459 void copy_event_storage(const MidiBuffer& other) {
460 events_.clear();
461 events_.reserve(other.events_.capacity());
462 events_ = other.events_;
463 sysex_.clear();
464 sysex_.reserve(other.sysex_.capacity());
465 sysex_ = other.sysex_;
466 }
467 void reserve_copied_sysex_payloads(const MidiBuffer& other) {
468 sysex_copy_payload_pool_.clear();
469 if (sysex_copy_payload_capacity_ == 0) {
470 return;
471 }
472 for (auto& event : sysex_) {
473 if (event.data.size() <= sysex_copy_payload_capacity_) {
474 event.data.reserve(sysex_copy_payload_capacity_);
475 event.data.set_realtime_pool_backed(
476 event.data.realtime_pool_backed(),
477 event.data.realtime_pool_backed()
478 ? &sysex_copy_payload_pool_
479 : nullptr,
480 event.data.realtime_pool_backed()
481 ? sysex_copy_payload_capacity_
482 : 0);
483 }
484 }
485 sysex_copy_payload_pool_.reserve(
486 other.sysex_copy_payload_pool_.capacity());
487 for (std::size_t i = 0; i < other.sysex_copy_payload_pool_.size(); ++i) {
488 std::vector<uint8_t> payload;
489 payload.reserve(sysex_copy_payload_capacity_);
490 sysex_copy_payload_pool_.push_back(std::move(payload));
491 }
492 }
493 void rebind_sysex_payloads_to_pool() {
494 for (auto& event : sysex_) {
495 if (event.data.realtime_pool_backed()) {
496 event.data.set_realtime_pool_backed(true,
497 &sysex_copy_payload_pool_,
498 sysex_copy_payload_capacity_);
499 }
500 }
501 }
502 static void saturating_increment(std::uint32_t& value) {
503 if (value < std::numeric_limits<std::uint32_t>::max()) {
504 ++value;
505 }
506 }
507 void record_event_drop() { saturating_increment(dropped_events_); }
508 void record_sysex_drop() { saturating_increment(dropped_sysex_events_); }
509
510 // Size the insertion-stable sort()'s scratch (index list + reorder buffer)
511 // to the event capacity so sort() is allocation-free for realtime callers.
512 // events_.swap(sort_reorder_) means the two reorder buffers ping-pong, so
513 // BOTH must hold the capacity for the swap to stay alloc-free across blocks.
514 void reserve_sort_scratch(std::size_t capacity) {
515 sort_index_.reserve(capacity);
516 sort_reorder_.reserve(capacity);
517 }
518
519 std::vector<MidiEvent> events_;
520 // Scratch for the insertion-stable sort(). Not part of the buffer's logical
521 // contents — excluded from copy/move/clear; reserved by reserve()/
522 // reserve_events() and resized in place by sort().
523 std::vector<std::uint32_t> sort_index_;
524 std::vector<MidiEvent> sort_reorder_;
525 std::vector<std::vector<uint8_t>> sysex_copy_payload_pool_;
526 std::vector<SysexEvent> sysex_;
527 class UmpBuffer* ump_ = nullptr;
528 std::size_t sysex_copy_payload_capacity_ = 0;
529 bool limit_to_reserved_capacity_ = false;
530 std::uint32_t dropped_events_ = 0;
531 std::uint32_t dropped_sysex_events_ = 0;
532};
533
534} // namespace pulp::midi
Definition buffer.hpp:229
SysexPayload(std::initializer_list< uint8_t > values)
Definition buffer.hpp:232
SysexPayload(const SysexPayload &other)
Definition buffer.hpp:236
const uint8_t * data() const
Definition buffer.hpp:269
SysexPayload & operator=(std::initializer_list< uint8_t > values)
Definition buffer.hpp:255
auto end() const
Definition buffer.hpp:282
friend bool operator==(const SysexPayload &lhs, const std::vector< uint8_t > &rhs)
Definition buffer.hpp:292
~SysexPayload()
Definition buffer.hpp:250
auto end()
Definition buffer.hpp:284
void resize(std::size_t size)
Definition buffer.hpp:286
std::size_t capacity() const
Definition buffer.hpp:268
SysexPayload(SysexPayload &&)=delete
const uint8_t & front() const
Definition buffer.hpp:271
std::size_t size() const
Definition buffer.hpp:266
auto begin() const
Definition buffer.hpp:281
SysexPayload & operator=(SysexPayload &&)=delete
const uint8_t & operator[](std::size_t index) const
Definition buffer.hpp:275
SysexPayload(std::vector< uint8_t > bytes)
Definition buffer.hpp:233
const uint8_t & back() const
Definition buffer.hpp:273
void push_back(uint8_t byte)
Definition buffer.hpp:288
std::vector< uint8_t > to_vector() const
Definition buffer.hpp:290
SysexPayload & operator=(const SysexPayload &other)
Definition buffer.hpp:239
uint8_t & operator[](std::size_t index)
Definition buffer.hpp:278
friend class MidiBuffer
Definition buffer.hpp:310
friend bool operator!=(const SysexPayload &lhs, const std::vector< uint8_t > &rhs)
Definition buffer.hpp:300
bool empty() const
Definition buffer.hpp:267
uint8_t & back()
Definition buffer.hpp:274
friend bool operator==(const std::vector< uint8_t > &lhs, const SysexPayload &rhs)
Definition buffer.hpp:296
friend bool operator!=(const std::vector< uint8_t > &lhs, const SysexPayload &rhs)
Definition buffer.hpp:304
void clear()
Definition buffer.hpp:287
void reserve(std::size_t capacity)
Definition buffer.hpp:285
uint8_t * data()
Definition buffer.hpp:270
auto begin()
Definition buffer.hpp:283
uint8_t & front()
Definition buffer.hpp:272
SysexPayload & operator=(std::vector< uint8_t > bytes)
Definition buffer.hpp:259
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
std::size_t size() const
Definition buffer.hpp:123
bool add(MidiEvent &&event)
Append a MIDI event to the buffer.
Definition buffer.hpp:108
void set_realtime_capacity_limit(bool enabled=true)
When enabled, add() and move-based add_sysex() drop once reserved capacity is full instead of growing...
Definition buffer.hpp:148
std::size_t event_capacity() const
Definition buffer.hpp:152
bool add_sysex(SysexEvent &&event)
Definition buffer.hpp:364
const std::vector< SysexEvent > & sysex() const
Definition buffer.hpp:416
bool empty() const
Definition buffer.hpp:122
bool add_sysex(std::vector< uint8_t > data, int32_t sample_offset=0, double ts=0.0)
Definition buffer.hpp:352
MidiBuffer & operator=(const MidiBuffer &other)
Definition buffer.hpp:43
std::size_t sysex_copy_payload_capacity() const
Definition buffer.hpp:154
auto end()
Definition buffer.hpp:200
void attach_ump(class UmpBuffer *ump)
Attach a UmpBuffer sidecar carrying MIDI 2.0 packets that can't be represented as choc::midi::ShortMe...
Definition buffer.hpp:212
void clear()
Remove all events.
Definition buffer.hpp:118
MidiBuffer & operator=(MidiBuffer &&other) noexcept
Definition buffer.hpp:74
std::vector< SysexEvent > & sysex()
Definition buffer.hpp:417
auto begin()
Definition buffer.hpp:199
std::uint32_t dropped_sysex_count() const
Definition buffer.hpp:158
MidiBuffer(MidiBuffer &&other) noexcept
Definition buffer.hpp:56
auto begin() const
Definition buffer.hpp:201
std::size_t sysex_size() const
Definition buffer.hpp:415
void sort()
Sort events by sample_offset for sample-accurate processing.
Definition buffer.hpp:177
const MidiEvent & operator[](std::size_t index) const
Definition buffer.hpp:204
bool realtime_capacity_limited() const
Definition buffer.hpp:151
const class UmpBuffer * ump() const
Attached UmpBuffer or nullptr.
Definition buffer.hpp:217
std::size_t sysex_capacity() const
Definition buffer.hpp:153
void clear_sysex()
Definition buffer.hpp:408
bool add_sysex_copy(const uint8_t *data, std::size_t size, int32_t sample_offset=0, double ts=0.0)
Definition buffer.hpp:382
void reserve_sysex(std::size_t capacity)
Definition buffer.hpp:128
void reserve_sysex_copy_payloads(std::size_t payload_count, std::size_t payload_capacity)
Definition buffer.hpp:397
bool add(const MidiEvent &event)
Append a MIDI event to the buffer.
Definition buffer.hpp:98
std::uint32_t dropped_event_count() const
Definition buffer.hpp:157
MidiBuffer(const MidiBuffer &other)
Definition buffer.hpp:34
void reserve_events(std::size_t capacity)
Definition buffer.hpp:124
class UmpBuffer * ump()
Definition buffer.hpp:218
auto end() const
Definition buffer.hpp:202
void reserve(std::size_t event_capacity, std::size_t sysex_capacity=0, std::size_t sysex_copy_payload_capacity=0)
Preallocate storage for realtime callers that append during process().
Definition buffer.hpp:131
A time-ordered list of UMP events for one audio block.
Definition ump_buffer.hpp:41
Definition custom_node_events.hpp:26
@ other
Definition udev_monitor.hpp:26
@ i
Definition formant_filter_bank.hpp:20
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
STL namespace.
Definition buffer.hpp:346
double timestamp
absolute time in seconds
Definition buffer.hpp:349
int32_t sample_offset
sample position within the block
Definition buffer.hpp:348
SysexPayload data
full F0 .. F7 payload
Definition buffer.hpp:347
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32