2#include <ossia/dataflow/graph_node.hpp>
3#include <ossia/dataflow/node_process.hpp>
4#include <ossia/dataflow/port.hpp>
5#include <ossia/detail/flat_multiset.hpp>
7#include <libremidi/ump_events.hpp>
14using midi_size_t = uint8_t;
18 time_value duration{};
21 midi_size_t velocity{};
24inline bool is_note_off(
const libremidi::ump& m)
noexcept
26 return (m.get_status_code() & 0xF0) == 0x80;
29inline time_value note_end(
const note_data& n)
noexcept
31 return n.start + n.duration;
36 using is_transparent = std::true_type;
37 bool operator()(
const note_data& lhs,
const note_data& rhs)
const
39 return lhs.start < rhs.start;
41 bool operator()(
const note_data& lhs, int64_t rhs)
const
43 return lhs.start.impl < rhs;
51 using is_transparent = std::true_type;
52 bool operator()(
const note_data& lhs,
const note_data& rhs)
const
54 return note_end(lhs) < note_end(rhs);
56 bool operator()(
const note_data& lhs, int64_t rhs)
const
58 return note_end(lhs).impl < rhs;
62class midi final :
public ossia::nonowning_graph_node
64 ossia::midi_outlet midi_out;
67 using note_set = ossia::flat_multiset<note_data, note_comparator>;
68 using note_end_set = ossia::flat_multiset<note_data, note_end_comparator>;
69 explicit midi(int64_t notes)
71 m_outlets.push_back(&midi_out);
72 int64_t to_reserve = std::max(notes * 1.1, 128.);
73 m_notes.reserve(to_reserve);
74 m_orig_notes.reserve(to_reserve);
75 m_by_end.reserve(to_reserve);
76 m_playing_notes.reserve(to_reserve);
77 m_to_stop.reserve(64);
78 m_to_resume.reserve(64);
81 ~midi()
override =
default;
83 std::string label() const noexcept
override {
return "midi"; }
85 void set_channel(
int c) { m_channel = c - 1; }
87 void add_note(note_data nd)
89 m_orig_notes.insert(nd);
94 if(nd.start >= m_prev_date)
102 template <
typename Set>
103 static bool erase_exact(Set& set,
const note_data& nd)
105 auto [first, last] = set.equal_range(nd);
106 for(
auto it = first; it != last; ++it)
108 if(it->pitch == nd.pitch)
117 template <
typename Set>
118 static bool contains_exact(
const Set& set,
const note_data& nd)
120 auto [first, last] = set.equal_range(nd);
121 for(
auto it = first; it != last; ++it)
122 if(it->pitch == nd.pitch)
127 void remove_note(note_data nd)
129 erase_exact(m_orig_notes, nd);
130 erase_exact(m_by_end, nd);
131 erase_exact(m_notes, nd);
132 if(erase_exact(m_playing_notes, nd))
133 m_to_stop.insert(nd);
136 void replace_notes(note_set&& notes)
138 for(
auto& note : m_playing_notes)
139 m_to_stop.insert(note);
140 m_playing_notes.clear();
145 swap(m_orig_notes, notes);
149 auto start_it = m_orig_notes.lower_bound(m_prev_date.impl);
150 if(start_it != m_orig_notes.end())
152 m_notes.tree().get_sequence_ref().assign(start_it, m_orig_notes.end());
156 void transport(ossia::time_value date)
158 requestTransport =
true;
159 m_transport_date = date;
162 void transport_impl(ossia::time_value date)
166 m_to_stop.insert(m_playing_notes.begin(), m_playing_notes.end());
167 m_playing_notes.clear();
175 for(
const note_data& n : m_orig_notes)
179 else if((n.start + n.duration) > date)
180 m_to_resume.insert(n);
186 void update_note(note_data oldNote, note_data newNote)
189 remove_note(oldNote);
193 void set_notes(note_set&& notes)
195 m_notes = std::move(notes);
196 m_orig_notes = m_notes;
199 auto max_it = m_notes.lower_bound({m_prev_date});
200 if(max_it != m_notes.begin())
201 m_notes.erase(m_notes.begin(), max_it);
205 bool requestTransport{};
208 void rebuild_end_index()
211 m_by_end.insert(m_orig_notes.begin(), m_orig_notes.end());
216 void resume_notes(ossia::midi_port& mp, int64_t tick_start)
218 for(
const note_data& note : m_to_resume)
220 mp.messages.push_back(
221 libremidi::from_midi1::note_on(m_channel, note.pitch, note.velocity));
222 mp.messages.back().timestamp = tick_start;
223 m_playing_notes.insert(note);
232 const ossia::token_request& t, ossia::midi_port& mp,
double samplesratio,
238 = tick_start + std::abs(t.physical_write_duration(samplesratio));
239 const auto stamp = [&](time_value at) {
241 t.to_physical_time_in_tick(at, samplesratio), tick_start,
242 std::max(tick_start, tick_end - 1));
249 const note_data lo{t.date, 0_tv};
250 const note_data hi{t.prev_date, 0_tv};
251 for(
auto it = m_by_end.upper_bound(lo), last = m_by_end.upper_bound(hi);
254 if(contains_exact(m_playing_notes, *it))
257 mp.messages.push_back(
258 libremidi::from_midi1::note_on(m_channel, it->pitch, it->velocity));
259 mp.messages.back().timestamp = stamp(note_end(*it));
261 m_playing_notes.insert(*it);
262 erase_exact(m_notes, *it);
269 for(
auto it = m_playing_notes.begin(); it != m_playing_notes.end();)
271 if(it->start > t.date)
273 mp.messages.push_back(
274 libremidi::from_midi1::note_off(m_channel, it->pitch, 0));
275 mp.messages.back().timestamp = stamp(it->start);
276 it = m_playing_notes.erase(it);
287 const auto first = m_orig_notes.upper_bound({t.date});
288 const auto last = m_orig_notes.upper_bound({t.prev_date});
289 for(
auto it = first; it != last; ++it)
291 erase_exact(m_notes, *it);
299 static int64_t stamp_in_tick(
300 const ossia::token_request& t, time_value at,
double samplesratio,
301 int64_t tick_start)
noexcept
304 = tick_start + std::abs(t.physical_write_duration(samplesratio));
306 t.to_physical_time_in_tick(at, samplesratio), tick_start,
307 std::max(tick_start, tick_end - 1));
314 void stop_finished_notes(
315 const ossia::token_request& t, ossia::midi_port& mp,
double samplesratio,
318 for(
auto it = m_playing_notes.begin(); it != m_playing_notes.end();)
320 const note_data& note = *it;
321 const auto end_time = note.start + note.duration;
323 if(end_time < t.date)
325 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
326 mp.messages.back().timestamp
327 = stamp_in_tick(t, end_time, samplesratio, tick_start);
329 it = m_playing_notes.erase(it);
338 void run(
const ossia::token_request& t, ossia::exec_state_facade e)
noexcept override
340 ossia::midi_port& mp = *midi_out;
345 const ossia::token_request& t;
346 ossia::midi_port& mp;
347 std::size_t first_message;
353 auto begin = mp.messages.begin() + first_message;
354 std::stable_sort(begin, mp.messages.end(), [](
const auto& lhs,
const auto& rhs) {
355 if(lhs.timestamp != rhs.timestamp)
356 return lhs.timestamp < rhs.timestamp;
357 return is_note_off(lhs) && !is_note_off(rhs);
360 self.m_prev_date = t.date;
362 if(self.requestTransport)
364 self.transport_impl(self.m_transport_date);
365 self.requestTransport =
false;
368 } guard{*
this, t, mp, mp.messages.size()};
370 const auto samplesratio = e.modelToSamples();
371 const auto tick_start = t.physical_start(samplesratio);
373 if(t.end_discontinuous)
375 auto& mess = mp.messages;
376 for(
auto note : m_playing_notes)
378 mess.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
379 mess.back().timestamp = tick_start;
381 for(
auto note : m_to_stop)
383 mess.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
384 mess.back().timestamp = tick_start;
386 m_playing_notes.clear();
391 for(
const note_data& note : m_to_stop)
393 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
394 mp.messages.back().timestamp = tick_start;
400 for(
auto& note : m_playing_notes)
402 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
403 mp.messages.back().timestamp = tick_start;
406 m_notes = m_orig_notes;
407 m_playing_notes.clear();
416 if(!t.backward() && m_notes.empty() && m_playing_notes.empty()
417 && m_to_resume.empty())
420 resume_notes(mp, tick_start);
424 rewind(t, mp, samplesratio, tick_start);
428 stop_finished_notes(t, mp, samplesratio, tick_start);
431 auto max_it = m_notes.lower_bound({t.date});
432 for(
auto it = m_notes.begin(); it < max_it;)
434 note_data& note =
const_cast<note_data&
>(*it);
435 auto start_time = note.start;
436 if(start_time >= t.prev_date && start_time < t.date)
439 mp.messages.push_back(
440 libremidi::from_midi1::note_on(m_channel, note.pitch, note.velocity));
441 mp.messages.back().timestamp
442 = stamp_in_tick(t, start_time, samplesratio, tick_start);
444 m_playing_notes.insert(note);
445 it = m_notes.erase(it);
446 max_it = std::lower_bound(
447 it, m_notes.end(), t.date.impl + 1, note_comparator{});
457 stop_finished_notes(t, mp, samplesratio, tick_start);
463 note_set m_orig_notes;
464 note_set m_playing_notes;
466 note_set m_to_resume;
467 note_end_set m_by_end;
468 time_value m_prev_date{};
469 time_value m_transport_date{};
474class midi_node_process final :
public ossia::node_process
477 using ossia::node_process::node_process;
479 void transport_impl(ossia::time_value date)
override
481 midi& n = *
static_cast<midi*
>(node.get());
487 midi& n = *
static_cast<midi*
>(node.get());
488 n.request(ossia::token_request{});
Forward playback finds the notes to start by their start date; going backwards it is their end date t...
Definition dataflow/nodes/midi.hpp:50