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());
158 requestTransport =
true;
159 m_transport_date = 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);
300 void stop_finished_notes(
301 const ossia::token_request& t, ossia::midi_port& mp,
double samplesratio,
304 for(
auto it = m_playing_notes.begin(); it != m_playing_notes.end();)
306 const note_data& note = *it;
307 const auto end_time = note.start + note.duration;
309 if(end_time < t.date)
311 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
312 mp.messages.back().timestamp
313 = std::max(tick_start, t.to_physical_time_in_tick(end_time, samplesratio));
315 it = m_playing_notes.erase(it);
324 void run(
const ossia::token_request& t, ossia::exec_state_facade e)
noexcept override
326 ossia::midi_port& mp = *midi_out;
331 const ossia::token_request& t;
332 ossia::midi_port& mp;
333 std::size_t first_message;
339 auto begin = mp.messages.begin() + first_message;
340 std::stable_sort(begin, mp.messages.end(), [](
const auto& lhs,
const auto& rhs) {
341 if(lhs.timestamp != rhs.timestamp)
342 return lhs.timestamp < rhs.timestamp;
343 return is_note_off(lhs) && !is_note_off(rhs);
346 self.m_prev_date = t.date;
348 if(self.requestTransport)
350 self.transport_impl(self.m_transport_date);
351 self.requestTransport =
false;
354 } guard{*
this, t, mp, mp.messages.size()};
356 const auto samplesratio = e.modelToSamples();
357 const auto tick_start = t.physical_start(samplesratio);
359 if(t.end_discontinuous)
361 auto& mess = mp.messages;
362 for(
auto note : m_playing_notes)
364 mess.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
365 mess.back().timestamp = tick_start;
367 for(
auto note : m_to_stop)
369 mess.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
370 mess.back().timestamp = tick_start;
372 m_playing_notes.clear();
377 for(
const note_data& note : m_to_stop)
379 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
380 mp.messages.back().timestamp = tick_start;
386 for(
auto& note : m_playing_notes)
388 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
389 mp.messages.back().timestamp = tick_start;
392 m_notes = m_orig_notes;
393 m_playing_notes.clear();
402 if(!t.backward() && m_notes.empty() && m_playing_notes.empty()
403 && m_to_resume.empty())
406 resume_notes(mp, tick_start);
410 rewind(t, mp, samplesratio, tick_start);
414 stop_finished_notes(t, mp, samplesratio, tick_start);
417 auto max_it = m_notes.lower_bound({t.date});
418 for(
auto it = m_notes.begin(); it < max_it;)
420 note_data& note =
const_cast<note_data&
>(*it);
421 auto start_time = note.start;
422 if(start_time >= t.prev_date && start_time < t.date)
425 mp.messages.push_back(
426 libremidi::from_midi1::note_on(m_channel, note.pitch, note.velocity));
427 mp.messages.back().timestamp
428 = t.to_physical_time_in_tick(start_time, samplesratio);
430 m_playing_notes.insert(note);
431 it = m_notes.erase(it);
432 max_it = std::lower_bound(
433 it, m_notes.end(), t.date.impl + 1, note_comparator{});
443 stop_finished_notes(t, mp, samplesratio, tick_start);
449 note_set m_orig_notes;
450 note_set m_playing_notes;
452 note_set m_to_resume;
453 note_end_set m_by_end;
454 time_value m_prev_date{};
455 time_value m_transport_date{};
460class midi_node_process final :
public ossia::node_process
463 using ossia::node_process::node_process;
467 midi& n = *
static_cast<midi*
>(node.get());
473 midi& n = *
static_cast<midi*
>(node.get());
474 n.request(ossia::token_request{});
Definition dataflow/nodes/midi.hpp:50
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30