Loading...
Searching...
No Matches
Quantifier.hpp
1#pragma once
2#include <Fx/Types.hpp>
3
4#include <halp/controls.hpp>
5#include <halp/meta.hpp>
6#include <halp/midi.hpp>
7
8namespace Nodes
9{
10namespace Quantifier
11{
12struct Node
13{
14 halp_meta(name, "Midi quantify (old)")
15 halp_meta(c_name, "Quantifier")
16 halp_meta(category, "Midi/Timing")
17 halp_meta(manual_url, "https://ossia.io/score-docs/processes/midi-utilities.html#quantifier")
18 halp_meta(author, "ossia score")
19 halp_meta(description, "Quantifies a MIDI input")
20 halp_flag(deprecated);
21 halp_meta(uuid, "b8e2e5ad-17e4-43de-8d79-660a29d5c4f4")
22
23 struct
24 {
25 halp::midi_bus<"in", libremidi::message> midi;
26 quant_selector<"Quantization"> start_quant;
27 halp::hslider_f32<"Tightness", halp::range{0.f, 1.f, 0.8f}> tightness;
28 duration_selector<"Duration"> duration;
29 halp::hslider_f32<"Tempo", halp::range{20., 300., 120.}> tempo;
30
31 } inputs;
32 struct
33 {
34 midi_out midi;
35 } outputs;
36
37 struct Note
38 {
39 uint8_t pitch{};
40 uint8_t vel{};
41 uint8_t chan{};
42 };
43
44 struct NoteIn
45 {
46 Note note{};
47 int64_t date{};
48 };
49 std::vector<NoteIn> to_start;
50 std::vector<NoteIn> running_notes;
51
52 // using control_policy = ossia::safe_nodes::default_tick_controls;
53 halp::setup setup;
54 void prepare(halp::setup s) { setup = s; }
55
56 using tick = halp::tick_flicks;
57 void sequence(Note new_note, int64_t date)
58 {
59 for(auto note : to_start)
60 if(note.note.pitch == new_note.pitch)
61 return;
62 for(auto note : running_notes)
63 if(note.note.pitch == new_note.pitch)
64 return;
65 to_start.push_back(NoteIn{new_note, date});
66 }
67 void operator()(const tick& tk)
68 {
69 double start_q_ratio = inputs.start_quant.value;
70 double precision = inputs.tightness.value;
71 double duration = inputs.duration.value;
72 double tempo = inputs.tempo.value;
73
74 // how much time does a whole note last at this tempo given the current sr
75 const auto whole_dur = 240.f / tempo; // in seconds
76 const auto whole_samples = whole_dur * setup.rate;
77
78 for(const libremidi::message& in : inputs.midi)
79 {
80 // FIXME note processor
81 if(!in.is_note_on_or_off())
82 {
83 outputs.midi.push_back(in);
84 continue;
85 }
86
87 Note note{in[1], in[2], (uint8_t)in.get_channel()};
88
89 if(in.get_message_type() == libremidi::message_type::NOTE_ON && note.vel != 0)
90 {
91 if(start_q_ratio == 0.f) // No quantification, start directly
92 {
93 auto no = libremidi::channel_events::note_on(note.chan, note.pitch, note.vel);
94 no.timestamp = in.timestamp;
95
96 outputs.midi.push_back(no);
97 if(duration > 0.f)
98 {
99 auto end = tk.position_in_frames + (int64_t)no.timestamp
100 + (int64_t)(whole_samples * duration);
101 this->running_notes.push_back({note, end});
102 }
103 else if(duration == 0.f)
104 {
105 // Stop at the next sample
106 auto noff = libremidi::channel_events::note_off(note.chan, note.pitch, note.vel);
107 noff.timestamp = no.timestamp;
108 outputs.midi.push_back(noff);
109 }
110 // else do nothing and just wait for a note off
111 }
112 else
113 {
114 // Find next time that matches the requested quantification
115 const auto start_q = whole_samples * start_q_ratio;
116 auto perf_date = int64_t(
117 start_q * int64_t(1 + (tk.position_in_frames + in.timestamp) / start_q));
118
119 //FIXME (1. - precision) * tk.date.impl * st.modelToSamples() + precision * perf_date;
120 int64_t actual_date = perf_date;
121
122 sequence(note, actual_date);
123 }
124 }
125 else
126 {
127 // Just stop
128 auto noff = libremidi::channel_events::note_off(note.chan, note.pitch, note.vel);
129 noff.timestamp = in.timestamp;
130 outputs.midi.push_back(noff);
131
132 for(auto it = running_notes.begin(); it != running_notes.end();)
133 if(it->note.pitch == note.pitch)
134 it = running_notes.erase(it);
135 else
136 ++it;
137 }
138 }
139
140 // TODO : also handle the case where we're quite close from the *previous*
141 // accessible value, eg we played a bit late
142 for(auto it = this->to_start.begin(); it != this->to_start.end();)
143 {
144 auto& note = *it;
145 // FIXME how does this follow live tempo changes ?
146 if(note.date >= tk.position_in_frames
147 && note.date < (tk.position_in_frames + tk.frames))
148 {
149 auto no = libremidi::channel_events::note_on(
150 note.note.chan, note.note.pitch, note.note.vel);
151 no.timestamp = note.date - tk.position_in_frames;
152 outputs.midi.push_back(no);
153
154 if(duration > 0.f)
155 {
156 auto end = note.date + (int64_t)(whole_samples * duration);
157 this->running_notes.push_back({note.note, end});
158 }
159 else if(duration == 0.f)
160 {
161 // Stop at the next sample
162 auto noff = libremidi::channel_events::note_off(note.note.chan, note.note.pitch, note.note.vel);
163 noff.timestamp = no.timestamp;
164 outputs.midi.push_back(noff);
165 }
166
167 it = this->to_start.erase(it);
168 }
169 else
170 {
171 ++it;
172 }
173 }
174
175 for(auto it = this->running_notes.begin(); it != this->running_notes.end();)
176 {
177 auto& note = *it;
178 if(note.date >= tk.position_in_frames
179 && note.date < (tk.position_in_frames + tk.frames))
180 {
181 auto noff = libremidi::channel_events::note_off(note.note.chan, note.note.pitch, note.note.vel);
182 noff.timestamp = note.date - tk.position_in_frames;
183 outputs.midi.push_back(noff);
184 it = this->running_notes.erase(it);
185 }
186 else
187 {
188 ++it;
189 }
190 }
191 }
192};
193}
194}
Definition Quantifier.hpp:38
Definition Quantifier.hpp:45
Definition Quantifier.hpp:13
Definition Types.hpp:49