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>
12using midi_size_t = uint8_t;
16 time_value duration{};
19 midi_size_t velocity{};
24 using is_transparent = std::true_type;
25 bool operator()(
const note_data& lhs,
const note_data& rhs)
const
27 return lhs.start < rhs.start;
29 bool operator()(
const note_data& lhs, int64_t rhs)
const
31 return lhs.start.impl < rhs;
35class midi final :
public ossia::nonowning_graph_node
37 ossia::midi_outlet midi_out;
40 using note_set = ossia::flat_multiset<note_data, note_comparator>;
41 explicit midi(int64_t notes)
43 m_outlets.push_back(&midi_out);
44 int64_t to_reserve = std::max(notes * 1.1, 128.);
45 m_notes.reserve(to_reserve);
46 m_orig_notes.reserve(to_reserve);
47 m_playing_notes.reserve(to_reserve);
48 m_to_stop.reserve(64);
51 ~midi()
override =
default;
53 std::string label() const noexcept
override {
return "midi"; }
55 void set_channel(
int c) { m_channel = c - 1; }
57 void add_note(note_data nd)
59 m_orig_notes.insert(nd);
60 if(nd.start > m_prev_date)
66 void remove_note(note_data nd)
68 m_orig_notes.erase(nd);
70 auto it = m_playing_notes.find(nd);
71 if(it != m_playing_notes.end())
74 m_playing_notes.erase(it);
78 void replace_notes(note_set&& notes)
80 for(
auto& note : m_playing_notes)
81 m_to_stop.insert(note);
82 m_playing_notes.clear();
85 swap(m_orig_notes, notes);
88 auto start_it = m_orig_notes.lower_bound(m_prev_date.impl);
89 if(start_it != m_orig_notes.end())
91 m_notes.tree().get_sequence_ref().assign(start_it, m_orig_notes.end());
97 requestTransport =
true;
98 m_transport_date = date;
104 m_to_stop.insert(m_playing_notes.begin(), m_playing_notes.end());
105 m_playing_notes.clear();
108 if(date < m_prev_date)
112 m_notes = m_orig_notes;
116 auto min_it = m_orig_notes.lower_bound({date});
117 auto max_it = m_orig_notes.lower_bound({m_prev_date});
119 if(min_it != m_orig_notes.end())
120 m_notes.insert(min_it, max_it);
123 for(
auto it = m_orig_notes.begin(); it != min_it; ++it)
125 if((it->start + it->duration) > date)
132 else if(date > m_prev_date)
135 auto min_it = m_notes.lower_bound({date});
136 if(min_it != m_notes.begin() && min_it != m_notes.end())
138 std::advance(min_it, -1);
139 m_notes.erase(m_notes.begin(), min_it);
147 void update_note(note_data oldNote, note_data newNote)
150 remove_note(oldNote);
154 void set_notes(note_set&& notes)
156 m_notes = std::move(notes);
157 m_orig_notes = m_notes;
159 auto max_it = m_notes.lower_bound({m_prev_date});
160 if(max_it != m_notes.begin())
161 m_notes.erase(m_notes.begin(), max_it);
165 bool requestTransport{};
169 void run(
const ossia::token_request& t, ossia::exec_state_facade e)
noexcept override
174 const ossia::token_request& t;
177 self.m_prev_date = t.date;
179 if(self.requestTransport)
181 self.transport_impl(self.m_transport_date);
182 self.requestTransport =
false;
187 ossia::midi_port& mp = *midi_out;
188 const auto samplesratio = e.modelToSamples();
189 const auto tick_start = t.physical_start(samplesratio);
191 if(t.end_discontinuous)
193 auto& mess = mp.messages;
194 for(
auto note : m_playing_notes)
196 mess.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
197 mess.back().timestamp = 0;
199 for(
auto note : m_to_stop)
201 mess.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
202 mess.back().timestamp = 0;
204 m_playing_notes.clear();
209 for(
const note_data& note : m_to_stop)
211 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
212 mp.messages.back().timestamp = tick_start;
218 for(
auto& note : m_playing_notes)
220 mp.messages.push_back(libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
221 mp.messages.back().timestamp = tick_start;
224 m_notes = m_orig_notes;
225 m_playing_notes.clear();
231 if(m_notes.empty() && m_playing_notes.empty())
235 auto it = m_notes.begin();
237 while(it != m_notes.end() && it->start < t.date)
240 mp.messages.push_back(
241 libremidi::from_midi1::note_on(m_channel, note.pitch, note.velocity));
242 mp.messages.back().timestamp = tick_start;
243 m_playing_notes.insert(note);
244 it = m_notes.erase(it);
253 for(
auto it = m_playing_notes.begin(); it != m_playing_notes.end();)
255 note_data& note =
const_cast<note_data&
>(*it);
256 auto end_time = note.start + note.duration;
258 if(t.in_range({end_time}))
260 mp.messages.push_back(
261 libremidi::from_midi1::note_off(m_channel, note.pitch, 0));
262 mp.messages.back().timestamp
263 = t.to_physical_time_in_tick(end_time, samplesratio);
265 it = m_playing_notes.erase(it);
274 auto max_it = m_notes.lower_bound({t.date});
275 for(
auto it = m_notes.begin(); it < max_it;)
277 note_data& note =
const_cast<note_data&
>(*it);
278 auto start_time = note.start;
279 if(start_time >= t.prev_date && start_time < t.date)
282 mp.messages.push_back(
283 libremidi::from_midi1::note_on(m_channel, note.pitch, note.velocity));
284 mp.messages.back().timestamp
285 = t.to_physical_time_in_tick(start_time, samplesratio);
287 m_playing_notes.insert(note);
288 it = m_notes.erase(it);
289 max_it = std::lower_bound(
290 it, m_notes.end(), t.date.impl + 1, note_comparator{});
302 note_set m_orig_notes;
303 note_set m_playing_notes;
305 time_value m_prev_date{};
306 time_value m_transport_date{};
311class midi_node_process final :
public ossia::node_process
314 using ossia::node_process::node_process;
318 midi& n = *
static_cast<midi*
>(node.get());
324 midi& n = *
static_cast<midi*
>(node.get());
325 n.request(ossia::token_request{});
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30