LFO.hpp
1 #pragma once
2 #include <Engine/Node/SimpleApi.hpp>
3 
4 #include <ossia/detail/flicks.hpp>
5 #include <ossia/detail/math.hpp>
6 
7 #include <rnd/random.hpp>
8 #include <tuplet/tuple.hpp>
9 
10 #include <array>
11 #include <bitset>
12 #include <random>
13 #include <tuple>
14 #include <utility>
15 
16 namespace Nodes
17 {
18 
19 namespace LFO
20 {
21 static inline std::random_device& random_source()
22 {
23  static thread_local std::random_device d;
24  return d;
25 }
26 
27 namespace v1
28 {
29 struct Node
30 {
32  {
33  static const constexpr auto prettyName = "LFO (old)";
34  static const constexpr auto objectKey = "LFO";
35  static const constexpr auto category = "Control/Generators";
36  static const constexpr auto author = "ossia score";
37  static const constexpr auto tags = std::array<const char*, 0>{};
38  static const constexpr auto kind
39  = Process::ProcessCategory::Generator | Process::ProcessCategory::Deprecated;
40  static const constexpr auto description = "Low-frequency oscillator";
41  static const constexpr double recommended_height = 130.;
42  static const uuid_constexpr auto uuid
43  = make_uuid("0697b807-f588-49b5-926c-f97701edd0d8");
44 
45  static const constexpr value_out value_outs[]{"out"};
46 
47  static const constexpr auto controls = tuplet::tuple{
48  Control::Widgets::LFOFreqKnob(),
49  Control::FloatKnob{"Ampl.", 0., 1000., 0.},
50  Control::FloatKnob{"Fine", 0., 1., 0.5},
51  Control::FloatKnob{"Offset", -1000., 1000., 0.},
52  Control::FloatKnob{"Fine", -1., 1., 0.5},
53  Control::FloatKnob{"Jitter", 0., 1., 0.},
54  Control::FloatKnob{"Phase", -1., 1., 0.},
55  Control::Widgets::WaveformChooser(),
56  Control::Widgets::QuantificationChooser()};
57  };
58 
59  // Idea: save internal state for rewind... ? -> require Copyable
60  struct State
61  {
62  double phase{};
63  rnd::pcg rd;
64  };
65 
66  using control_policy = ossia::safe_nodes::precise_tick;
67 
68  static void
69  run(float freq, float ampl, float ampl_fine, float offset, float offset_fine,
70  float jitter, float custom_phase, const std::string& type, float quantif,
71  ossia::value_port& out, ossia::token_request tk, ossia::exec_state_facade st,
72  State& s)
73  {
74  constexpr const double sine_ratio = ossia::two_pi / ossia::flicks_per_second<double>;
75  const auto& waveform_map = Control::Widgets::waveformMap();
76  const auto elapsed = tk.model_read_duration().impl;
77 
78  if(quantif)
79  {
80  // Determine the frequency with the quantification
81  if(tk.unexpected_bar_change())
82  {
83  s.phase = 0;
84  }
85 
86  // If quantif == 1, we quantize to the bar
87  // => f = 0.5 hz
88  // If quantif == 1/4, we quantize to the quarter
89  // => f = 2hz
90  // -> sin(elapsed * freq * 2 * pi / fps)
91  // -> sin(elapsed * 4 * 2 * pi / fps)
92  freq = 1. / (2. * quantif);
93  }
94 
95  const auto ph_delta = elapsed * freq * sine_ratio;
96 
97  if(const auto it = waveform_map.find_key(type))
98  {
99  auto ph = s.phase;
100  if(jitter > 0)
101  {
102  ph += std::normal_distribution<float>(0., 0.25)(s.rd) * jitter;
103  }
104 
105  ampl += ampl_fine;
106  offset += offset_fine;
107 
108  using namespace Control::Widgets;
109 
110  const auto add_val = [&](auto new_val) {
111  const auto [tick_start, d] = st.timings(tk);
112  out.write_value(ampl * new_val + offset, tick_start);
113  };
114 
115  custom_phase = custom_phase * ossia::pi;
116  switch(*it)
117  {
118  case Sin:
119  add_val(std::sin(custom_phase + ph));
120  break;
121  case Triangle:
122  add_val(std::asin(std::sin(custom_phase + ph)) / ossia::half_pi);
123  break;
124  case Saw:
125  add_val(std::atan(std::tan(custom_phase + ph)) / ossia::half_pi);
126  break;
127  case Square:
128  add_val((std::sin(custom_phase + ph) > 0.f) ? 1.f : -1.f);
129  break;
130  case SampleAndHold: {
131  const auto start_s = std::sin(custom_phase + ph);
132  const auto end_s = std::sin(custom_phase + ph + ph_delta);
133  if((start_s > 0 && end_s <= 0) || (start_s <= 0 && end_s > 0))
134  {
135  add_val(std::uniform_real_distribution<float>(-1.f, 1.f)(s.rd));
136  }
137  break;
138  }
139  case Noise1:
140  add_val(std::uniform_real_distribution<float>(-1.f, 1.f)(s.rd));
141  break;
142  case Noise2:
143  add_val(std::normal_distribution<float>(0.f, 1.f)(s.rd));
144  break;
145  case Noise3:
146  add_val(std::clamp(std::cauchy_distribution<float>(0.f, 1.f)(s.rd), 0.f, 1.f));
147  break;
148  }
149  }
150 
151  s.phase += ph_delta;
152  }
153 
154  static void item(
155  Process::LogFloatSlider& freq, Process::FloatKnob& ampl,
156  Process::FloatKnob& ampl_fine, Process::FloatKnob& offset,
157  Process::FloatKnob& offset_fine, Process::FloatKnob& jitter,
158  Process::FloatKnob& phase, Process::Enum& type, Process::ComboBox& quantif,
159  const Process::ProcessModel& process, QGraphicsItem& parent, QObject& context,
160  const Process::Context& doc)
161  {
162  using namespace Process;
163  using namespace std;
164  using namespace tuplet;
165  const Process::PortFactoryList& portFactory
167  const auto h = 60;
168  const auto w = 50;
169 
170  const auto c0 = 10;
171  const auto c1 = 180;
172  const auto c2 = 230;
173 
174  auto c0_bg = new score::BackgroundItem{&parent};
175  c0_bg->setRect({0., 0., 170., 130.});
176  auto c1_bg = new score::BackgroundItem{&parent};
177  c1_bg->setRect({170., 0., 100., 130.});
178  auto c2_bg = new score::BackgroundItem{&parent};
179  c2_bg->setRect({270., 0., 60., 130.});
180 
181  auto freq_item = makeControl(
182  get<0>(Metadata::controls), freq, parent, context, doc, portFactory);
183  freq_item.root.setPos(c0, 0);
184 
185  auto quant_item = makeControlNoText(
186  get<8>(Metadata::controls), quantif, parent, context, doc, portFactory);
187  quant_item.root.setPos(90, 25);
188  quant_item.port.setPos(-10, 2);
189 
190  auto type_item = makeControlNoText(
191  get<7>(Metadata::controls), type, parent, context, doc, portFactory);
192  type_item.root.setPos(c0, h);
193  type_item.control.rows = 2;
194  type_item.control.columns = 4;
195  type_item.control.setRect(QRectF{0, 0, 104, 44});
196  type_item.control.setPos(10, 0);
197  type_item.port.setPos(0, 17);
198 
199  auto ampl_item = makeControl(
200  get<1>(Metadata::controls), ampl, parent, context, doc, portFactory);
201  ampl_item.root.setPos(c1, 0);
202 
203  auto ampl_fine_item = makeControl(
204  get<2>(Metadata::controls), ampl_fine, parent, context, doc, portFactory);
205  ampl_fine_item.root.setPos(c1 + w, 0);
206 
207  auto offset_item = makeControl(
208  get<3>(Metadata::controls), offset, parent, context, doc, portFactory);
209  offset_item.root.setPos(c1, h);
210 
211  auto offset_fine_item = makeControl(
212  get<4>(Metadata::controls), offset_fine, parent, context, doc, portFactory);
213  offset_fine_item.root.setPos(c1 + w, h);
214 
215  auto jitter_item = makeControl(
216  get<5>(Metadata::controls), jitter, parent, context, doc, portFactory);
217  jitter_item.root.setPos(c2 + w, 0);
218 
219  auto phase_item = makeControl(
220  get<6>(Metadata::controls), phase, parent, context, doc, portFactory);
221  phase_item.root.setPos(c2 + w, h);
222  }
223 
224  // With metaclasses, we should instead create structs with named members but
225  // where types are either controls, ports, items, inlets...
226 };
227 }
228 
229 namespace v2
230 {
231 struct Node
232 {
234  {
235  static const constexpr auto prettyName = "LFO";
236  static const constexpr auto objectKey = "LFO";
237  static const constexpr auto category = "Control/Generators";
238  static const constexpr auto author = "ossia score";
239  static const constexpr auto tags = std::array<const char*, 0>{};
240  static const constexpr auto kind = Process::ProcessCategory::Generator;
241  static const constexpr auto description = "Low-frequency oscillator";
242  static const constexpr double recommended_height = 130.;
243  static const uuid_constexpr auto uuid
244  = make_uuid("1e17e479-3513-44c8-a8a7-017be9f6ac8a");
245 
246  static const constexpr value_out value_outs[]{"out"};
247 
248  static const constexpr auto controls = tuplet::tuple{
249  Control::Widgets::LFOFreqKnob(),
250  Control::FloatKnob{"Ampl.", 0., 2., 0.5},
251  Control::FloatKnob{"Offset", -1., 1., 0.5},
252  Control::FloatKnob{"Jitter", 0., 1., 0.},
253  Control::FloatKnob{"Phase", -1., 1., 0.},
254  Control::Widgets::WaveformChooser(),
255  Control::Widgets::QuantificationChooser()};
256  };
257 
258  // Idea: save internal state for rewind... ? -> require Copyable
259  struct State
260  {
261  double phase{};
262  rnd::pcg rd{random_source()};
263  };
264 
265  using control_policy = ossia::safe_nodes::precise_tick;
266 
267  static void
268  run(float freq, float ampl, float offset, float jitter, float custom_phase,
269  const std::string& type, float quantif, ossia::value_port& out,
270  ossia::token_request tk, ossia::exec_state_facade st, State& s)
271  {
272  constexpr const double sine_ratio = ossia::two_pi / ossia::flicks_per_second<double>;
273  const auto& waveform_map = Control::Widgets::waveformMap();
274  const auto elapsed = tk.model_read_duration().impl;
275 
276  out.type = ossia::val_type::FLOAT;
277  out.domain = ossia::domain_base<float>{0., 1.};
278 
279  if(quantif)
280  {
281  // Determine the frequency with the quantification
282  if(tk.unexpected_bar_change())
283  {
284  s.phase = 0;
285  }
286 
287  // If quantif == 1, we quantize to the bar
288  // => f = 0.5 hz
289  // If quantif == 1/4, we quantize to the quarter
290  // => f = 2hz
291  // -> sin(elapsed * freq * 2 * pi / fps)
292  // -> sin(elapsed * 4 * 2 * pi / fps)
293  freq = 1. / (2. * quantif);
294  }
295 
296  const auto ph_delta = elapsed * freq * sine_ratio;
297 
298  if(const auto it = waveform_map.find_key(type))
299  {
300  auto ph = s.phase;
301  if(jitter > 0)
302  {
303  ph += std::normal_distribution<float>(0., 0.25)(s.rd) * jitter;
304  }
305 
306  using namespace Control::Widgets;
307 
308  const auto add_val = [&](auto new_val) {
309  const auto [tick_start, d] = st.timings(tk);
310  out.write_value(ampl * new_val + offset, tick_start);
311  };
312  switch(*it)
313  {
314  case Sin:
315  add_val(std::sin(custom_phase + ph));
316  break;
317  case Triangle:
318  add_val(std::asin(std::sin(custom_phase + ph)) / ossia::half_pi);
319  break;
320  case Saw:
321  add_val(std::atan(std::tan(custom_phase + ph)) / ossia::half_pi);
322  break;
323  case Square:
324  add_val((std::sin(custom_phase + ph) > 0.f) ? 1.f : -1.f);
325  break;
326  case SampleAndHold: {
327  const auto start_s = std::sin(custom_phase + ph);
328  const auto end_s = std::sin(custom_phase + ph + ph_delta);
329  if((start_s > 0 && end_s <= 0) || (start_s <= 0 && end_s > 0))
330  {
331  add_val(std::uniform_real_distribution<float>(-1.f, 1.f)(s.rd));
332  }
333  break;
334  }
335  case Noise1:
336  add_val(std::uniform_real_distribution<float>(-1.f, 1.f)(s.rd));
337  break;
338  case Noise2:
339  add_val(std::normal_distribution<float>(0.f, 1.f)(s.rd));
340  break;
341  case Noise3:
342  add_val(std::clamp(std::cauchy_distribution<float>(0.f, 1.f)(s.rd), 0.f, 1.f));
343  break;
344  }
345  }
346 
347  s.phase += ph_delta;
348  }
349 
350  static void item(
351  Process::LogFloatSlider& freq, Process::FloatKnob& ampl,
352  Process::FloatKnob& offset, Process::FloatKnob& jitter, Process::FloatKnob& phase,
353  Process::Enum& type, Process::ComboBox& quantif,
354  const Process::ProcessModel& process, QGraphicsItem& parent, QObject& context,
355  const Process::Context& doc)
356  {
357  using namespace Process;
358  using namespace std;
359  using namespace tuplet;
360  const Process::PortFactoryList& portFactory
362  const auto h = 60;
363  const auto w = 50;
364 
365  const auto c0 = 10;
366  const auto c1 = 180;
367  const auto c2 = 230;
368 
369  auto c0_bg = new score::BackgroundItem{&parent};
370  c0_bg->setRect({0., 0., 170., 130.});
371  auto c1_bg = new score::BackgroundItem{&parent};
372  c1_bg->setRect({170., 0., 100., 130.});
373  auto c2_bg = new score::BackgroundItem{&parent};
374  c2_bg->setRect({270., 0., 60., 130.});
375 
376  auto freq_item = makeControl(
377  get<0>(Metadata::controls), freq, parent, context, doc, portFactory);
378  freq_item.root.setPos(c0, 0);
379 
380  auto ampl_item = makeControl(
381  get<1>(Metadata::controls), ampl, parent, context, doc, portFactory);
382  ampl_item.root.setPos(c1, 0);
383 
384  auto offset_item = makeControl(
385  get<2>(Metadata::controls), offset, parent, context, doc, portFactory);
386  offset_item.root.setPos(c1, h);
387 
388  auto jitter_item = makeControl(
389  get<3>(Metadata::controls), jitter, parent, context, doc, portFactory);
390  jitter_item.root.setPos(c2 + w, 0);
391 
392  auto phase_item = makeControl(
393  get<4>(Metadata::controls), phase, parent, context, doc, portFactory);
394  phase_item.root.setPos(c2 + w, h);
395 
396  auto type_item = makeControlNoText(
397  get<5>(Metadata::controls), type, parent, context, doc, portFactory);
398  type_item.root.setPos(c0, h);
399  type_item.control.rows = 2;
400  type_item.control.columns = 4;
401  type_item.control.setRect(QRectF{0, 0, 104, 44});
402  type_item.control.setPos(10, 0);
403  type_item.port.setPos(0, 17);
404 
405  auto quant_item = makeControlNoText(
406  get<6>(Metadata::controls), quantif, parent, context, doc, portFactory);
407  quant_item.root.setPos(90, 25);
408  quant_item.port.setPos(-10, 2);
409  }
410 
411  // With metaclasses, we should instead create structs with named members but
412  // where types are either controls, ports, items, inlets...
413 };
414 }
415 }
416 
417 }
Definition: PortFactory.hpp:65
The Process class.
Definition: score-lib-process/Process/Process.hpp:61
Definition: RectItem.hpp:96
Base classes and tools to implement processes and layers.
Definition: JSONVisitor.hpp:1324
Utilities for OSSIA data structures.
Definition: DeviceInterface.hpp:33
Definition: score-lib-process/Control/Widgets.hpp:77
Definition: SimpleApi.hpp:32
Definition: LFO.hpp:32
Definition: LFO.hpp:30
Definition: LFO.hpp:234
Definition: LFO.hpp:232
Definition: ProcessContext.hpp:12
const T & interfaces() const
Access to a specific interface list.
Definition: ApplicationContext.hpp:67