Loading...
Searching...
No Matches
LFO_v3.hpp
1#pragma once
2#include <Fx/LFO_v2.hpp>
3#include <Fx/Types.hpp>
4
5#include <ossia/detail/flicks.hpp>
6#include <ossia/detail/math.hpp>
7
8#include <halp/controls.hpp>
9#include <halp/layout.hpp>
10#include <halp/meta.hpp>
11#include <halp/midi.hpp>
12#include <rnd/random.hpp>
13#include <tuplet/tuple.hpp>
14
15#include <random>
16
17namespace Nodes::LFO::v3
18{
21enum Waveform
22{
23 Sin,
24 Triangle,
25 Saw,
26 RampDown,
27 Square,
28 SquareOnChange,
29 SampleAndHoldEveryTick,
30 SampleAndHold,
31 Noise1,
32 Noise2,
33 Noise3,
34 Drift
35};
36
37struct Node
38{
39 halp_meta(name, "LFO")
40 halp_meta(c_name, "LFO")
41 halp_meta(category, "Control/Generators")
42 halp_meta(author, "ossia score")
43 halp_meta(manual_url, "https://ossia.io/score-docs/processes/lfo.html#lfo")
44 halp_meta(description, "Low-frequency oscillator")
45 halp_meta(recommended_height, 130.)
46 halp_meta(uuid, "0b1b1816-c33e-4796-a16d-5aab27fe600f");
47
48 struct ins
49 {
51 halp::time_chooser<"Period", halp::range{0.01, 60., 1.}> period;
56 halp::knob_f32<"Shape", halp::range{0., 1., 0.5}> shape;
58 halp::impulse_button<"Retrigger"> retrigger;
59 halp::knob_f32<"Ampl.", halp::range{0., 2., 0.5}> ampl;
60 halp::knob_f32<"Offset", halp::range{-1., 1., 0.5}> offset;
63 halp::knob_f32<"Jitter", halp::range{0., 180., 0.}> jitter;
67 halp::knob_f32<"Phase", halp::range{0., 360., 0.}> phase;
68 struct : halp::enum_t<Waveform, "Waveform">
69 {
70 static constexpr auto pixmaps()
71 {
72 return std::array<const char*, 24>{
73 ":/icons/wave_sin_off.png",
74 ":/icons/wave_sin_on.png",
75 ":/icons/wave_triangle_off.png",
76 ":/icons/wave_triangle_on.png",
77 ":/icons/wave_saw_off.png",
78 ":/icons/wave_saw_on.png",
79 ":/icons/wave_ramp_down_off.png",
80 ":/icons/wave_ramp_down_on.png",
81 ":/icons/wave_square_ticks_off.png",
82 ":/icons/wave_square_ticks_on.png",
83 ":/icons/wave_square_changes_off.png",
84 ":/icons/wave_square_changes_on.png",
85 ":/icons/wave_sample_and_hold_ticks_off.png",
86 ":/icons/wave_sample_and_hold_ticks_on.png",
87 ":/icons/wave_sample_and_hold_changes_off.png",
88 ":/icons/wave_sample_and_hold_changes_on.png",
89 ":/icons/wave_noise1_off.png",
90 ":/icons/wave_noise1_on.png",
91 ":/icons/wave_noise2_off.png",
92 ":/icons/wave_noise2_on.png",
93 ":/icons/wave_noise3_off.png",
94 ":/icons/wave_noise3_on.png",
95 ":/icons/wave_drift_off.png",
96 ":/icons/wave_drift_on.png"};
97 }
98 } waveform;
102 halp::toggle<"Lock to bars"> lock_to_bars;
103 } inputs;
104 struct
105 {
106 // FIXME output range ???
107 struct : halp::val_port<"Out", std::optional<float>>
108 {
109 struct range
110 {
111 float min = 0.;
112 float max = 1.;
113 };
114 } out;
115 } outputs;
116
120 double phase{};
121 int64_t cycles{};
124 double sync_origin{};
125 rnd::pcg rd{random_source()};
126 uint32_t seed{uint32_t(rd())};
127
128 // The stepped waveforms: the value held, and whether it was already sent.
129 float held{};
130 int last_square{}; // -1, +1, or 0 before the first tick
131 bool held_valid{};
132
133 static double frac(double x) noexcept { return x - std::floor(x); }
134
138 static double skewed_position(double x, double rise) noexcept
139 {
140 const double r = std::clamp(rise, 0.02, 0.98);
141 const double u = frac(x + 0.25); // 0 at the trough
142 return u < r ? 0.5 * u / r : 0.5 + 0.5 * (u - r) / (1. - r);
143 }
144 static double sine(double x, double rise) noexcept
145 {
146 return -std::cos(ossia::two_pi * skewed_position(x, rise));
147 }
148 static double triangle(double x, double rise) noexcept
149 {
150 const double w = skewed_position(x, rise); // 0 trough, 0.5 peak
151 return w < 0.5 ? -1. + 4. * w : 3. - 4. * w;
152 }
154 static double saw(double x, double shape) noexcept
155 {
156 const double e = std::exp2((0.5 - std::clamp(shape, 0., 1.)) * 4.); // 4 .. 1/4
157 return 2. * std::pow(frac(x + 0.5), e) - 1.;
158 }
160 static int step(double x, double duty) noexcept
161 {
162 return frac(x) < std::clamp(duty, 0., 1.) ? 1 : -1;
163 }
164
166 static float lattice(int64_t i, uint32_t seed) noexcept
167 {
168 uint64_t h = uint64_t(i) * 0x9E3779B97F4A7C15ull ^ (uint64_t(seed) << 17);
169 h ^= h >> 31;
170 h *= 0xBF58476D1CE4E5B9ull;
171 h ^= h >> 29;
172 return float(h >> 40) / float(1 << 23) - 1.f;
173 }
174
176 static float drift(double cycles, double roughness, uint32_t seed) noexcept
177 {
178 auto octave = [&](double t, uint32_t sd) {
179 const double f = std::floor(t);
180 const double u = t - f;
181 const double k = u * u * (3. - 2. * u); // smoothstep
182 const float a = lattice(int64_t(f), sd), b = lattice(int64_t(f) + 1, sd);
183 return a + (b - a) * float(k);
184 };
185 const double r = std::clamp(roughness, 0., 1.);
186 const float v = octave(cycles, seed) + float(r) * 0.5f * octave(cycles * 2.3, seed ^ 0x5bd1e995u)
187 + float(r * r) * 0.25f * octave(cycles * 5.1, seed ^ 0x27d4eb2du);
188 return std::clamp(v / float(1. + 0.5 * r + 0.25 * r * r), -1.f, 1.f);
189 }
190
191 using tick = halp::tick_flicks;
192 void operator()(const halp::tick_flicks& tk)
193 {
194 const auto ampl = inputs.ampl.value;
195 const auto offset = inputs.offset.value;
196 const auto jitter = inputs.jitter.value;
197 const auto type = inputs.waveform.value;
198 const double shape = inputs.shape.value;
199 const double period = std::max(1e-4, (double)inputs.period.value);
200 const bool sync = inputs.period.sync;
201 const bool locked = sync && inputs.lock_to_bars.value;
202
203 if(inputs.retrigger)
204 {
205 phase = 0.;
206 cycles = 0;
207 sync_origin = tk.start_position_in_quarters;
208 held_valid = false;
209 last_square = 0;
210 seed = uint32_t(rd());
211 }
212 else if(sync && !locked && tk.unexpected_bar_change())
213 {
214 phase = 0.;
215 cycles = 0;
216 }
217
218 // Cycles elapsed at the start and at the end of the tick. Locked to the
219 // bars, they are read off the musical position: the cycles stay on the
220 // bars through tempo changes, jumps and loops, with no drift from
221 // accumulating. Otherwise they accumulate from the start, synced in
222 // quarters. The binding hands a synced period over in seconds at the
223 // current tempo: back to quarter notes.
224 const double quarters_per_cycle = period * tk.tempo / 60.;
225 double c0{}, c1{};
226 if(locked)
227 {
228 if(quarters_per_cycle > 0.)
229 {
230 c0 = (tk.start_position_in_quarters - sync_origin) / quarters_per_cycle;
231 c1 = (tk.end_position_in_quarters - sync_origin) / quarters_per_cycle;
232 }
233 }
234 else
235 {
236 c0 = double(cycles) + phase;
237 if(!sync)
238 c1 = c0
239 + double(tk.model_read_duration())
240 / (period * ossia::flicks_per_second<double>);
241 else if(quarters_per_cycle > 0.)
242 c1 = c0
243 + (tk.end_position_in_quarters - tk.start_position_in_quarters)
244 / quarters_per_cycle;
245 else
246 c1 = c0;
247 }
248
249 double shift = inputs.phase.value;
250 if(jitter > 0)
251 shift += std::uniform_real_distribution<float>(-jitter, jitter)(this->rd);
252 shift /= 360.;
253 c0 += shift;
254 c1 += shift;
255 const double x = frac(c0);
256
257 // Every waveform, the noises included, spans [-1; 1]: Ampl. and Offset
258 // place them all the same way.
259 const auto add_val
260 = [&](auto new_val) { outputs.out.value = ampl * new_val + offset; };
261
262 // Sample and hold: a new value at each of the two steps of a cycle.
263 const auto crossed = [&] {
264 return std::floor(c0) != std::floor(c1) || step(c0, shape) != step(c1, shape);
265 };
266
267 switch(type)
268 {
269 case Sin:
270 add_val(sine(x, shape));
271 break;
272 case Triangle:
273 add_val(triangle(x, shape));
274 break;
275 case Saw:
276 add_val(saw(x, shape));
277 break;
278 case RampDown:
279 add_val(-saw(x, shape));
280 break;
281 case Square:
282 add_val(float(step(x, shape)));
283 break;
284 case SquareOnChange: {
285 const int s = step(x, shape);
286 if(s != last_square)
287 add_val(float(s));
288 last_square = s;
289 break;
290 }
291 case SampleAndHold:
292 if(!held_valid || crossed())
293 {
294 held = std::uniform_real_distribution<float>(-1.f, 1.f)(this->rd);
295 held_valid = true;
296 add_val(held);
297 }
298 break;
299 case SampleAndHoldEveryTick:
300 if(!held_valid || crossed())
301 {
302 held = std::uniform_real_distribution<float>(-1.f, 1.f)(this->rd);
303 held_valid = true;
304 }
305 add_val(held);
306 break;
307 case Noise1:
308 add_val(std::uniform_real_distribution<float>(-1.f, 1.f)(this->rd));
309 break;
310 case Noise2:
311 add_val(std::clamp(std::normal_distribution<float>(0.f, 0.4f)(this->rd), -1.f, 1.f));
312 break;
313 case Noise3:
314 add_val(std::clamp(std::cauchy_distribution<float>(0.f, 0.1f)(this->rd), -1.f, 1.f));
315 break;
316 case Drift:
317 add_val(drift(c0, shape, seed));
318 break;
319 }
320
321 // Whole cycles move to the counter, so the phase keeps its precision
322 // however long it runs, and in either direction. Locked, the count follows
323 // the position so that unlocking carries on from there; locking snaps the
324 // cycle to the bars.
325 const double end = locked ? c1 - shift : phase + (c1 - c0);
326 const double whole = std::floor(end);
327 phase = end - whole;
328 cycles = locked ? int64_t(whole) : cycles + int64_t(whole);
329 }
330
331 struct ui
332 {
333 // Toggles go on a strip underneath so that the node stays narrow.
334 halp_meta(layout, halp::layouts::vbox)
335 struct
336 {
337 halp_meta(layout, halp::layouts::hbox)
338 struct
339 {
340 halp_meta(layout, halp::layouts::vbox)
341 halp_meta(background, halp::colors::background_mid)
342 halp::control<&ins::waveform> w;
343 } shape;
344
345 struct
346 {
347 halp_meta(layout, halp::layouts::vbox)
348 halp_meta(background, halp::colors::background_mid)
349 struct
350 {
351 halp_meta(layout, halp::layouts::hbox)
352 halp::control<&ins::period> t;
353 halp::control<&ins::ampl> a;
354 halp::control<&ins::shape> s;
355 } top;
356 struct
357 {
358 halp_meta(layout, halp::layouts::hbox)
359 halp::control<&ins::phase> p;
360 halp::control<&ins::offset> o;
361 halp::control<&ins::jitter> j;
362 } bottom;
363 } knobs;
364 } main;
365
366 struct
367 {
368 halp_meta(layout, halp::layouts::hbox)
369 halp_meta(background, halp::colors::background_mid)
370 halp::control<&ins::retrigger> r;
371 halp::control<&ins::lock_to_bars> l;
372 } toggles;
373 };
374};
375}
Definition LFO_v3.hpp:49
halp::toggle<"Lock to bars"> lock_to_bars
Definition LFO_v3.hpp:102
halp::knob_f32<"Shape", halp::range{0., 1., 0.5}> shape
Definition LFO_v3.hpp:56
halp::knob_f32<"Jitter", halp::range{0., 180., 0.}> jitter
Definition LFO_v3.hpp:63
halp::time_chooser<"Period", halp::range{0.01, 60., 1.}> period
One cycle: in seconds, or synced to a note value.
Definition LFO_v3.hpp:51
halp::impulse_button<"Retrigger"> retrigger
Back to the start of the cycle.
Definition LFO_v3.hpp:58
halp::knob_f32<"Phase", halp::range{0., 360., 0.}> phase
Definition LFO_v3.hpp:67
Definition LFO_v3.hpp:332
Definition LFO_v3.hpp:38
static float lattice(int64_t i, uint32_t seed) noexcept
A random value in [-1; 1] for each integer, the same every time.
Definition LFO_v3.hpp:166
static double saw(double x, double shape) noexcept
One ramp a cycle, 0 at x = 0; bent by the shape: linear at 0.5.
Definition LFO_v3.hpp:154
static int step(double x, double duty) noexcept
The two steps of a cycle: the first until duty, the second after.
Definition LFO_v3.hpp:160
double sync_origin
Definition LFO_v3.hpp:124
static double skewed_position(double x, double rise) noexcept
Definition LFO_v3.hpp:138
double phase
Definition LFO_v3.hpp:120
static float drift(double cycles, double roughness, uint32_t seed) noexcept
Smooth value noise: one wander per cycle, finer detail by roughness.
Definition LFO_v3.hpp:176
Definition MIDISync.hpp:128