14#include "utils/traits.h"
15#include "utils/units.h"
20constexpr int MAX_MIDI_EVENTS = 2560;
34template <
typename TimeT>
struct MidiEvent
37 using ExternalStorage = std::shared_ptr<const midi_byte_t[]>;
38 using TimeType = TimeT;
40 static constexpr size_t inline_capacity = InlineStorage{}.size ();
45 std::variant<InlineStorage, ExternalStorage> storage;
57 template <
typename OtherTimeType>
58 requires std::constructible_from<TimeType, OtherTimeType>
59 && (!std::same_as<TimeType, OtherTimeType>)
60 MidiEvent (
const MidiEvent<OtherTimeType> &other) noexcept
61 : storage (other.storage),
size_ (other.
size_),
66 std::span<const midi_byte_t> data () const noexcept [[clang::nonblocking]]
72 std::is_same_v<std::decay_t<
decltype (s)>, InlineStorage>)
82 void set_inline (std::span<const midi_byte_t> d)
noexcept [[clang::blocking]]
84 assert (d.size () <= inline_capacity);
85 size_ =
static_cast<uint16_t
> (d.size ());
86 if (!std::holds_alternative<InlineStorage> (storage))
87 storage = InlineStorage{};
88 auto &arr = std::get<InlineStorage> (storage);
89 std::ranges::copy (d, arr.begin ());
99 set_inline_rt (std::span<const midi_byte_t> d)
noexcept [[clang::nonblocking]]
101 assert (d.size () <= inline_capacity);
102 assert (std::holds_alternative<InlineStorage> (storage));
103 size_ =
static_cast<uint16_t
> (d.size ());
104 auto &arr = std::get<InlineStorage> (storage);
105 std::ranges::copy (d, arr.begin ());
109 set_external (std::shared_ptr<
const midi_byte_t[]> ptr, uint16_t sz)
noexcept
113 storage = std::move (ptr);
116 bool is_inline () const noexcept
118 return std::holds_alternative<InlineStorage> (storage);
121 friend bool operator== (
const MidiEvent &lhs,
const MidiEvent &rhs)
noexcept
123 if (lhs.time_ != rhs.time_ || lhs.size_ != rhs.size_)
125 const auto ld = lhs.data ();
126 const auto rd = rhs.data ();
127 return std::ranges::equal (ld, rd);
147template <
typename TimeType>
155 assert (channel <= 15);
156 const std::array<midi_byte_t, 3> raw = {
157 static_cast<midi_byte_t> (utils::midi::MIDI_CH1_NOTE_ON | channel),
163 assert (utils::midi::midi_is_note_on (ev.data ()));
172template <
typename TimeType>
176 assert (channel <= 15);
177 const std::array<midi_byte_t, 3> raw = {
178 static_cast<midi_byte_t> (utils::midi::MIDI_CH1_NOTE_OFF | channel),
184 assert (utils::midi::midi_is_note_off (ev.data ()));
193template <
typename TimeType>
201 assert (channel <= 15);
202 const std::array<midi_byte_t, 3> raw = {
203 static_cast<midi_byte_t> (0xB0 | channel), controller, value
217template <
typename TimeType>
221 assert (channel <= 15);
222 assert (pitchbend <= 16384);
223 const std::array<midi_byte_t, 3> raw = {
239template <
typename TimeType>
243 assert (channel <= 15);
244 const std::array<midi_byte_t, 2> raw = {
258template <
typename TimeType>
262 assert (channel <= 15);
263 const std::array<midi_byte_t, 3> raw = {
278template <
typename TimeType>
280make_raw_rt (std::span<const midi_byte_t> raw, TimeType time)
noexcept
281 [[clang::nonblocking]]
283 assert (raw.size () <= MidiEvent<TimeType>::inline_capacity);
296template <
typename TimeType>
298make_raw (std::span<const midi_byte_t> raw, TimeType time) [[clang::blocking]]
301 if (raw.size () <= MidiEvent<TimeType>::inline_capacity)
307 auto external = std::make_shared<midi_byte_t[]> (raw.size ());
308 std::ranges::copy (raw, external.get ());
310 std::move (external),
static_cast<uint16_t
> (raw.size ()));
322 template <
typename TimeType>
328 const auto ad = a.data ();
329 const auto bd = b.data ();
330 return !utils::midi::midi_is_note_on (ad) && utils::midi::midi_is_note_on (bd);
338template <std::ranges::random_access_range Container>
350template <std::ranges::random_access_range Container>
354 auto proj = [] (
const auto &ev) ->
const auto & {
return ev.time_; };
355 std::ranges::stable_sort (container, {}, proj);
363template <std::ranges::random_access_range Container>
367 assert (channel <= 15);
368 for (
auto &ev : container)
371 if (d.size () >= 1 && (d[0] & 0xF0) != 0xF0)
373 std::array<midi_byte_t, 8> raw{};
374 raw[0] =
static_cast<midi_byte_t> ((d[0] & 0xF0) | channel);
375 std::ranges::copy (d | std::views::drop (1), raw.begin () + 1);
377 std::span<const midi_byte_t>{ raw.data (), d.size () });
385 std::ranges::range SrcContainer>
389 const SrcContainer &src,
390 std::pair<TimeType, TimeType> range)
392 for (
const auto &ev : src)
394 if (ev.time_ >= range.first && ev.time_ < range.second)
std::uint8_t midi_byte_t
MIDI byte.
Factory functions and algorithms for MidiEvent containers.
void sort_with_note_off_priority(Container &container)
Sorts events by time, with noteOff before noteOn at the same timestamp.
MidiEvent< TimeType > make_raw_rt(std::span< const midi_byte_t > raw, TimeType time) noexcept
Creates a MIDI event from the given bytes, RT-safe.
MidiEvent< TimeType > make_note_off(midi_byte_t channel, midi_byte_t note_pitch, TimeType time)
Creates a note off event.
MidiEvent< TimeType > make_raw(std::span< const midi_byte_t > raw, TimeType time)
Creates a raw MIDI event from the given bytes.
void set_channel(Container &container, midi_byte_t channel)
Sets the MIDI channel on all events in the container.
MidiEvent< TimeType > make_channel_pressure(midi_byte_t channel, midi_byte_t value, TimeType time)
Creates a channel pressure (aftertouch) event.
MidiEvent< TimeType > make_note_on(midi_byte_t channel, midi_byte_t note_pitch, midi_byte_t velocity, TimeType time)
Creates a note on event.
void sort(Container &container)
Sorts events by time only.
MidiEvent< TimeType > make_all_notes_off(midi_byte_t channel, TimeType time)
Creates an all-notes-off event.
MidiEvent< TimeType > make_pitchbend(midi_byte_t channel, uint32_t pitchbend, TimeType time)
Creates a pitchbend event.
MidiEvent< TimeType > make_control_change(midi_byte_t channel, midi_byte_t controller, midi_byte_t value, TimeType time)
Creates a control change event.
Type-erased MIDI event with small-buffer optimization.
void set_inline_rt(std::span< const midi_byte_t > d) noexcept
RT-safe version of set_inline that only works on inline storage.
Comparator for sorting MidiEvents by time, with noteOff before noteOn at the same timestamp.