Zrythm v2.0.0-alpha.1
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
midi_event.h
1// SPDX-FileCopyrightText: © 2018-2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <algorithm>
7#include <cstdint>
8#include <cstring>
9#include <memory>
10#include <span>
11#include <variant>
12
13#include "utils/midi.h"
14#include "utils/traits.h"
15#include "utils/units.h"
16
17namespace zrythm::dsp
18{
20constexpr int MAX_MIDI_EVENTS = 2560;
21
34template <typename TimeT> struct MidiEvent
35{
36 using InlineStorage = std::array<midi_byte_t, sizeof (const midi_byte_t *)>;
37 using ExternalStorage = std::shared_ptr<const midi_byte_t[]>;
38 using TimeType = TimeT;
39
40 static constexpr size_t inline_capacity = InlineStorage{}.size ();
41
42 // Note: ExternalStorage thread safety needs auditing before use in
43 // RT contexts. Inline storage (channel messages) is always RT-safe.
44
45 std::variant<InlineStorage, ExternalStorage> storage;
46 uint16_t size_{};
47 TimeType time_{};
48
49 MidiEvent () = default;
50
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_),
62 time_ (static_cast<TimeType> (other.time_))
63 {
64 }
65
66 std::span<const midi_byte_t> data () const noexcept [[clang::nonblocking]]
67 {
68 return {
69 std::visit (
70 [] (const auto &s) -> const midi_byte_t * {
71 if constexpr (
72 std::is_same_v<std::decay_t<decltype (s)>, InlineStorage>)
73 return s.data ();
74 else
75 return s.get ();
76 },
77 storage),
78 size_
79 };
80 }
81
82 void set_inline (std::span<const midi_byte_t> d) noexcept [[clang::blocking]]
83 {
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 ());
90 }
91
98 void
99 set_inline_rt (std::span<const midi_byte_t> d) noexcept [[clang::nonblocking]]
100 {
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 ());
106 }
107
108 void
109 set_external (std::shared_ptr<const midi_byte_t[]> ptr, uint16_t sz) noexcept
110 [[clang::blocking]]
111 {
112 size_ = sz;
113 storage = std::move (ptr);
114 }
115
116 bool is_inline () const noexcept
117 {
118 return std::holds_alternative<InlineStorage> (storage);
119 }
120
121 friend bool operator== (const MidiEvent &lhs, const MidiEvent &rhs) noexcept
122 {
123 if (lhs.time_ != rhs.time_ || lhs.size_ != rhs.size_)
124 return false;
125 const auto ld = lhs.data ();
126 const auto rd = rhs.data ();
127 return std::ranges::equal (ld, rd);
128 }
129};
130
132using SampleBasedMidiEvent = MidiEvent<units::sample_t>;
134using TickBasedMidiEvent = MidiEvent<units::precise_tick_t>;
136using RealtimeMidiEvent = MidiEvent<units::sample_u32_t>;
137
139namespace midi_event
140{
141
147template <typename TimeType>
150 midi_byte_t channel,
151 midi_byte_t note_pitch,
152 midi_byte_t velocity,
153 TimeType time)
154{
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),
158 note_pitch, velocity
159 };
161 ev.set_inline_rt (raw);
162 ev.time_ = time;
163 assert (utils::midi::midi_is_note_on (ev.data ()));
164 return ev;
165}
166
172template <typename TimeType>
174make_note_off (midi_byte_t channel, midi_byte_t note_pitch, TimeType time)
175{
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),
179 note_pitch, 0
180 };
182 ev.set_inline_rt (raw);
183 ev.time_ = time;
184 assert (utils::midi::midi_is_note_off (ev.data ()));
185 return ev;
186}
187
193template <typename TimeType>
196 midi_byte_t channel,
197 midi_byte_t controller,
198 midi_byte_t value,
199 TimeType time)
200{
201 assert (channel <= 15);
202 const std::array<midi_byte_t, 3> raw = {
203 static_cast<midi_byte_t> (0xB0 | channel), controller, value
204 };
206 ev.set_inline_rt (raw);
207 ev.time_ = time;
208 return ev;
209}
210
217template <typename TimeType>
219make_pitchbend (midi_byte_t channel, uint32_t pitchbend, TimeType time)
220{
221 assert (channel <= 15);
222 assert (pitchbend <= 16384);
223 const std::array<midi_byte_t, 3> raw = {
224 static_cast<midi_byte_t> (0xE0 | channel),
225 static_cast<midi_byte_t> (pitchbend & 0x7F),
226 static_cast<midi_byte_t> ((pitchbend >> 7) & 0x7F)
227 };
229 ev.set_inline_rt (raw);
230 ev.time_ = time;
231 return ev;
232}
233
239template <typename TimeType>
241make_channel_pressure (midi_byte_t channel, midi_byte_t value, TimeType time)
242{
243 assert (channel <= 15);
244 const std::array<midi_byte_t, 2> raw = {
245 static_cast<midi_byte_t> (0xD0 | channel), value
246 };
248 ev.set_inline_rt (raw);
249 ev.time_ = time;
250 return ev;
251}
252
258template <typename TimeType>
260make_all_notes_off (midi_byte_t channel, TimeType time)
261{
262 assert (channel <= 15);
263 const std::array<midi_byte_t, 3> raw = {
264 static_cast<midi_byte_t> (0xB0 | channel), 0x7B, 0
265 };
267 ev.set_inline_rt (raw);
268 ev.time_ = time;
269 return ev;
270}
271
278template <typename TimeType>
280make_raw_rt (std::span<const midi_byte_t> raw, TimeType time) noexcept
281 [[clang::nonblocking]]
282{
283 assert (raw.size () <= MidiEvent<TimeType>::inline_capacity);
285 ev.set_inline_rt (raw);
286 ev.time_ = time;
287 return ev;
288}
289
296template <typename TimeType>
298make_raw (std::span<const midi_byte_t> raw, TimeType time) [[clang::blocking]]
299{
301 if (raw.size () <= MidiEvent<TimeType>::inline_capacity)
302 {
303 ev.set_inline_rt (raw);
304 }
305 else
306 {
307 auto external = std::make_shared<midi_byte_t[]> (raw.size ());
308 std::ranges::copy (raw, external.get ());
309 ev.set_external (
310 std::move (external), static_cast<uint16_t> (raw.size ()));
311 }
312 ev.time_ = time;
313 return ev;
314}
315
321{
322 template <typename TimeType>
323 bool
324 operator() (const MidiEvent<TimeType> &a, const MidiEvent<TimeType> &b) const
325 {
326 if (a.time_ != b.time_)
327 return a.time_ < b.time_;
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);
331 }
332};
333
338template <std::ranges::random_access_range Container>
339void
340sort_with_note_off_priority (Container &container)
341{
342 std::ranges::stable_sort (container, NoteOffBeforeNoteOnCompare{});
343}
344
350template <std::ranges::random_access_range Container>
351void
352sort (Container &container)
353{
354 auto proj = [] (const auto &ev) -> const auto & { return ev.time_; };
355 std::ranges::stable_sort (container, {}, proj);
356}
357
363template <std::ranges::random_access_range Container>
364void
365set_channel (Container &container, midi_byte_t channel)
366{
367 assert (channel <= 15);
368 for (auto &ev : container)
369 {
370 auto d = ev.data ();
371 if (d.size () >= 1 && (d[0] & 0xF0) != 0xF0)
372 {
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);
376 ev.set_inline_rt (
377 std::span<const midi_byte_t>{ raw.data (), d.size () });
378 }
379 }
380}
381
382template <
383 typename TimeType,
385 std::ranges::range SrcContainer>
386void
387append_in_range (
388 DestContainer &dest,
389 const SrcContainer &src,
390 std::pair<TimeType, TimeType> range)
391{
392 for (const auto &ev : src)
393 {
394 if (ev.time_ >= range.first && ev.time_ < range.second)
395 dest.push_back (ev);
396 }
397}
398
399} // namespace midi_event
400
401extern template struct MidiEvent<units::sample_t>;
402extern template struct MidiEvent<units::precise_tick_t>;
403extern template struct MidiEvent<units::sample_u32_t>;
404
405} // namespace zrythm::dsp
MIDI utils.
std::uint8_t midi_byte_t
MIDI byte.
Definition midi.h:43
Factory functions and algorithms for MidiEvent containers.
Definition midi_event.h:140
void sort_with_note_off_priority(Container &container)
Sorts events by time, with noteOff before noteOn at the same timestamp.
Definition midi_event.h:340
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.
Definition midi_event.h:280
MidiEvent< TimeType > make_note_off(midi_byte_t channel, midi_byte_t note_pitch, TimeType time)
Creates a note off event.
Definition midi_event.h:174
MidiEvent< TimeType > make_raw(std::span< const midi_byte_t > raw, TimeType time)
Creates a raw MIDI event from the given bytes.
Definition midi_event.h:298
void set_channel(Container &container, midi_byte_t channel)
Sets the MIDI channel on all events in the container.
Definition midi_event.h:365
MidiEvent< TimeType > make_channel_pressure(midi_byte_t channel, midi_byte_t value, TimeType time)
Creates a channel pressure (aftertouch) event.
Definition midi_event.h:241
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.
Definition midi_event.h:149
void sort(Container &container)
Sorts events by time only.
Definition midi_event.h:352
MidiEvent< TimeType > make_all_notes_off(midi_byte_t channel, TimeType time)
Creates an all-notes-off event.
Definition midi_event.h:260
MidiEvent< TimeType > make_pitchbend(midi_byte_t channel, uint32_t pitchbend, TimeType time)
Creates a pitchbend event.
Definition midi_event.h:219
MidiEvent< TimeType > make_control_change(midi_byte_t channel, midi_byte_t controller, midi_byte_t value, TimeType time)
Creates a control change event.
Definition midi_event.h:195
Type-erased MIDI event with small-buffer optimization.
Definition midi_event.h:35
void set_inline_rt(std::span< const midi_byte_t > d) noexcept
RT-safe version of set_inline that only works on inline storage.
Definition midi_event.h:99
Comparator for sorting MidiEvents by time, with noteOff before noteOn at the same timestamp.
Definition midi_event.h:321