Loading...
Searching...
No Matches
StepNode.hpp
1#pragma once
2#include <ossia/dataflow/graph_node.hpp>
3#include <ossia/dataflow/port.hpp>
4#include <ossia/dataflow/token_request.hpp>
5#include <ossia/detail/flicks.hpp>
6#include <ossia/detail/pod_vector.hpp>
7#include <ossia/network/value/value_conversion.hpp>
8
9#include <algorithm>
10#include <atomic>
11#include <cmath>
12#include <limits>
13#include <vector>
14
15namespace Media::Step
16{
28class step_node final : public ossia::nonowning_graph_node
29{
30public:
31 ossia::value_inlet sequence_select;
32 ossia::value_inlet switch_quantification;
33 ossia::value_inlet duration;
34 ossia::value_outlet out;
35
38 std::vector<ossia::float_vector> sequences;
39
40 int current_sequence{};
43 double switch_rate{1.};
44
46 double step_duration{0.0625};
47 bool synced{true};
48
50 int current{};
52 std::atomic_int last{-1};
53
56 double origin{};
58 int64_t next_date{std::numeric_limits<int64_t>::min()};
59
60 step_node()
61 {
62 m_inlets.push_back(&sequence_select);
63 m_inlets.push_back(&switch_quantification);
64 m_inlets.push_back(&duration);
65 m_outlets.push_back(&out);
66 }
67
68 std::string label() const noexcept override { return "Step"; }
69
70 void request_sequence(int idx) noexcept
71 {
72 if(sequences.empty())
73 return;
74 idx = std::clamp(idx, 0, int(sequences.size()) - 1);
75 pending_sequence = (idx == current_sequence) ? -1 : idx;
76 }
77
79 void set_duration(const ossia::value& v) noexcept
80 {
81 switch(v.get_type())
82 {
83 case ossia::val_type::FLOAT:
84 case ossia::val_type::INT:
85 case ossia::val_type::BOOL:
86 step_duration = ossia::convert<float>(v);
87 synced = false;
88 break;
89 default: {
90 auto vec = ossia::convert<ossia::vec2f>(v);
91 step_duration = vec[0];
92 synced = vec[1] != 0.f;
93 break;
94 }
95 }
96 }
97
98 void read_controls() noexcept
99 {
100 if(auto& d = sequence_select->get_data(); !d.empty())
101 request_sequence(ossia::convert<int>(d.back().value));
102 if(auto& d = switch_quantification->get_data(); !d.empty())
103 switch_rate = ossia::convert<float>(d.back().value);
104 if(auto& d = duration->get_data(); !d.empty())
105 set_duration(d.back().value);
106 }
107
108 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
109 {
110 // Before any early return: the inlets are cleared once the node has run.
111 read_controls();
112
113 if(tk.paused() || sequences.empty())
114 return;
115 current_sequence = std::clamp(current_sequence, 0, int(sequences.size()) - 1);
116 if(pending_sequence >= int(sequences.size()))
117 pending_sequence = -1;
118
119 const double ratio = st.modelToSamples();
120 const int64_t tick_start = tk.physical_start(ratio);
121 const bool rewinding = tk.backward();
122
123 if(tk.prev_date.impl != next_date)
124 follow_jump(tk);
125 next_date = tk.date.impl;
126
127 constexpr auto never = std::numeric_limits<int64_t>::max();
128 int64_t switch_at = never;
129 if(pending_sequence >= 0)
130 {
131 if(switch_rate <= 0.)
132 {
133 switch_at = tick_start;
134 }
135 else
136 {
137 const auto points = tk.get_quantification_dates(switch_rate);
138 if(!points.empty())
139 switch_at = tick_start + tk.physical_position(points.front().position, ratio);
140 }
141 }
142
143 play_steps(tk, ratio, tick_start, rewinding, std::numeric_limits<int64_t>::min(), switch_at);
144 if(switch_at != never)
145 {
146 current_sequence = pending_sequence;
147 pending_sequence = -1;
148 current = 0;
149 play_steps(tk, ratio, tick_start, rewinding, switch_at, never);
150 }
151 }
152
155 double step_quarters(const ossia::token_request& tk) const noexcept
156 {
157 const double rate = 1. / step_duration;
158 if(rate > 1.)
159 return 4. / rate;
160 const auto& sig = tk.signature;
161 const double bar
162 = (sig.upper > 0 && sig.lower > 0) ? 4. * sig.upper / sig.lower : 4.;
163 return bar / rate;
164 }
165
168 void follow_jump(const ossia::token_request& tk) noexcept
169 {
170 // Quarter notes since the start of the process, at this tick's tempo.
171 const double elapsed
172 = tk.prev_date.impl / ossia::flicks_per_second<double> * tk.tempo / 60.;
173 origin = tk.musical_start_position - elapsed;
174
175 const int n = sequences.empty() ? 0 : int(sequences[current_sequence].size());
176 if(n == 0 || !(step_duration > 0.) || !std::isfinite(step_duration))
177 return;
178
179 // Steps whose time has come before this tick, the one on its start excluded.
180 double before{};
181 if(synced)
182 {
183 const double q = step_quarters(tk);
184 if(q > 0.)
185 before = std::ceil((tk.musical_start_position - origin) / q - 1e-9);
186 }
187 else
188 {
189 const double d = step_duration * ossia::flicks_per_second<double>;
190 before = std::ceil(tk.prev_date.impl / d);
191 }
192 if(std::isfinite(before) && before >= 0.)
193 current = int(std::fmod(before, double(n)));
194 }
195
198 const ossia::token_request& tk, double ratio, int64_t tick_start, bool rewinding,
199 int64_t from, int64_t to) noexcept
200 {
201 if(!(step_duration > 0.) || !std::isfinite(step_duration))
202 return;
203
204 auto play = [&](int64_t sample) {
205 if(sample >= from && sample < to)
206 play_step(sample, rewinding);
207 };
208
209 if(synced)
210 {
211 // k steps from the start, in tick order both ways: the musical positions
212 // of [start; end[ going forward, ]end; start] rewinding.
213 const double q = step_quarters(tk);
214 const double ms = tk.musical_start_position;
215 const double me = tk.musical_end_position;
216 if(!(q > 0.) || ms == me)
217 return;
218 constexpr double eps = 1e-9;
219 int64_t k = rewinding ? int64_t(std::floor((ms - origin) / q + eps))
220 : int64_t(std::ceil((ms - origin) / q - eps));
221 // A tick never holds more steps than it has samples.
222 for(int guard = 0; guard < 4096; guard++, k += rewinding ? -1 : 1)
223 {
224 const double m = origin + k * q;
225 if(rewinding ? (m <= me + eps) : (m >= me - eps))
226 break;
227 play(tick_start + tk.physical_position(m, ratio));
228 }
229 }
230 else
231 {
232 // k * d from the start of the interval.
233 const double d = step_duration * ossia::flicks_per_second<double>;
234 const int64_t lo = std::min(tk.prev_date.impl, tk.date.impl);
235 const int64_t hi = std::max(tk.prev_date.impl, tk.date.impl);
236 // [prev; date[ going forward, ]date; prev] rewinding: the same steps
237 // are crossed on the way out and on the way back.
238 int64_t k0 = rewinding ? int64_t(std::floor(lo / d)) + 1 : int64_t(std::ceil(lo / d));
239 int64_t k1 = rewinding ? int64_t(std::floor(hi / d)) : int64_t(std::ceil(hi / d)) - 1;
240 // A tick never holds more steps than it has samples.
241 k1 = std::min(k1, k0 + 4096);
242 if(!rewinding)
243 {
244 for(int64_t k = k0; k <= k1; k++)
245 play(tk.to_physical_time_in_tick(ossia::time_value{int64_t(k * d)}, ratio));
246 }
247 else
248 {
249 for(int64_t k = k1; k >= k0; k--)
250 play(tk.to_physical_time_in_tick(ossia::time_value{int64_t(k * d)}, ratio));
251 }
252 }
253 }
254
255 void play_step(int64_t sample, bool rewinding) noexcept
256 {
257 const auto& seq = sequences[current_sequence];
258 const int n = seq.size();
259 if(n == 0)
260 return;
261 // Rewinding mirrors going forward: step back first, so going out and back
262 // over the same ground crosses the same steps.
263 if(rewinding)
264 current = (current + n - 1) % n;
265 if(current < 0 || current >= n)
266 current = 0;
267 out->write_value(seq[current], sample);
268 last.store(current, std::memory_order_relaxed);
269 if(!rewinding)
270 current = (current + 1) % n;
271 }
272};
273}
Plays the steps of one of several sequences.
Definition StepNode.hpp:29
double step_duration
Seconds, or a fraction of a whole note when synced.
Definition StepNode.hpp:46
int pending_sequence
Sequence the next quantification point switches to; -1 when none waits.
Definition StepNode.hpp:42
std::vector< ossia::float_vector > sequences
Definition StepNode.hpp:38
void set_duration(const ossia::value &v) noexcept
A plain number is seconds; {value, mode}: mode 0 seconds, else synced.
Definition StepNode.hpp:79
std::atomic_int last
The step that played last; read by the UI thread.
Definition StepNode.hpp:52
double step_quarters(const ossia::token_request &tk) const noexcept
Definition StepNode.hpp:155
void play_steps(const ossia::token_request &tk, double ratio, int64_t tick_start, bool rewinding, int64_t from, int64_t to) noexcept
Plays the steps whose sample falls in [from; to[.
Definition StepNode.hpp:197
double origin
Definition StepNode.hpp:56
int64_t next_date
Where the next tick starts if time goes on; anything else is a jump.
Definition StepNode.hpp:58
void follow_jump(const ossia::token_request &tk) noexcept
Definition StepNode.hpp:168
int current
The step that plays next.
Definition StepNode.hpp:50