MathMapping.hpp
1 #pragma once
2 #include <Fx/MathGenerator.hpp>
3 
4 namespace Nodes
5 {
6 template <typename State>
8 {
9  static void store_output(State& self, const ossia::value& v)
10  {
11  switch(v.get_type())
12  {
13  case ossia::val_type::NONE:
14  break;
15  case ossia::val_type::FLOAT:
16  self.po = *v.target<float>();
17  break;
18  case ossia::val_type::VEC2F: {
19  auto& vec = *v.target<ossia::vec2f>();
20  self.pov.assign(vec.begin(), vec.end());
21  break;
22  }
23  case ossia::val_type::VEC3F: {
24  auto& vec = *v.target<ossia::vec3f>();
25  self.pov.assign(vec.begin(), vec.end());
26  break;
27  }
28  case ossia::val_type::VEC4F: {
29  auto& vec = *v.target<ossia::vec4f>();
30  self.pov.assign(vec.begin(), vec.end());
31  break;
32  }
33  case ossia::val_type::LIST: {
34  auto& arr = *v.target<std::vector<ossia::value>>();
35  if(!arr.empty())
36  {
37  self.pov.clear();
38  for(auto& v : arr)
39  self.pov.push_back(ossia::convert<float>(v));
40  }
41  break;
42  }
43  // Only these types are used now as per ossia::math_expression::result()
44  default:
45  break;
46  }
47  }
48 
49  static void exec_scalar(int64_t timestamp, State& self, ossia::value_port& output)
50  {
51  auto res = self.expr.result();
52 
53  self.px = self.x;
54  store_output(self, res);
55 
56  output.write_value(res, timestamp);
57  }
58 
59  static void exec_array(
60  int64_t timestamp, State& self, ossia::value_port& output,
61  bool vector_size_did_change)
62  {
63  if(self.xv.empty())
64  return;
65 
66  if(vector_size_did_change)
67  {
68  self.expr.remove_vector("xv");
69  self.expr.add_vector("xv", self.xv);
70  self.expr.recompile();
71  }
72 
73  auto res = self.expr.result();
74  store_output(self, res);
75 
76  // Save the previous input
77  {
78  bool old_prev = self.pxv.size();
79  self.pxv.assign(self.xv.begin(), self.xv.end());
80  bool new_prev = self.pxv.size();
81 
82  if(old_prev != new_prev)
83  {
84  self.expr.remove_vector("pxv");
85  self.expr.add_vector("pxv", self.pxv);
86  self.expr.recompile();
87  }
88  }
89 
90  output.write_value(std::move(res), timestamp);
91  }
92 
93  static void run_scalar(
94  const ossia::value_port& input, ossia::value_port& output,
95  const ossia::token_request& tk, ossia::exec_state_facade st, State& self)
96  {
97  auto ratio = st.modelToSamples();
98  auto parent_dur = tk.parent_duration.impl * ratio;
99  for(const ossia::timed_value& v : input.get_data())
100  {
101  int64_t new_time = tk.prev_date.impl * ratio + v.timestamp;
102  setMathExpressionTiming(self, new_time, self.last_value_time, parent_dur);
103  self.last_value_time = new_time;
104 
105  switch(v.value.get_type())
106  {
107  case ossia::val_type::NONE:
108  break;
109  case ossia::val_type::IMPULSE:
110  break;
111  case ossia::val_type::INT:
112  self.x = *v.value.target<int>();
113  break;
114  case ossia::val_type::FLOAT:
115  self.x = *v.value.target<float>();
116  break;
117  case ossia::val_type::BOOL:
118  self.x = *v.value.target<bool>() ? 1.f : 0.f;
119  break;
120  case ossia::val_type::STRING:
121  self.x = ossia::convert<float>(v.value);
122  break;
123  case ossia::val_type::VEC2F:
124  self.x = (*v.value.target<ossia::vec2f>())[0];
125  break;
126  case ossia::val_type::VEC3F:
127  self.x = (*v.value.target<ossia::vec3f>())[0];
128  break;
129  case ossia::val_type::VEC4F:
130  self.x = (*v.value.target<ossia::vec4f>())[0];
131  break;
132  case ossia::val_type::LIST: {
133  auto& arr = *v.value.target<std::vector<ossia::value>>();
134  if(!arr.empty())
135  self.x = ossia::convert<float>(arr[0]);
136  break;
137  }
138  }
139 
140  GenericMathMapping::exec_scalar(v.timestamp, self, output);
141  }
142  }
143 
144  static void run_array(
145  const ossia::value_port& input, ossia::value_port& output,
146  const ossia::token_request& tk, ossia::exec_state_facade st, State& self)
147  {
148  auto ratio = st.modelToSamples();
149  auto parent_dur = tk.parent_duration.impl * ratio;
150  for(const ossia::timed_value& v : input.get_data())
151  {
152  int64_t new_time = tk.prev_date.impl * ratio + v.timestamp;
153  setMathExpressionTiming(self, new_time, self.last_value_time, parent_dur);
154  self.last_value_time = new_time;
155 
156  auto array_run_scalar = [&](float in) {
157  auto old_size = self.xv.size();
158  self.xv.assign(1, in);
159  auto new_size = 1U;
160  GenericMathMapping::exec_array(v.timestamp, self, output, old_size != new_size);
161  };
162 
163  switch(v.value.get_type())
164  {
165  case ossia::val_type::NONE:
166  break;
167  case ossia::val_type::IMPULSE:
168  GenericMathMapping::exec_array(v.timestamp, self, output, false);
169  break;
170  case ossia::val_type::INT:
171  array_run_scalar(*v.value.target<int>());
172  break;
173  case ossia::val_type::FLOAT:
174  array_run_scalar(*v.value.target<float>());
175  break;
176  case ossia::val_type::BOOL:
177  array_run_scalar(*v.value.target<bool>() ? 1.f : 0.f);
178  break;
179  case ossia::val_type::STRING:
180  array_run_scalar(ossia::convert<float>(v.value));
181  break;
182  case ossia::val_type::VEC2F: {
183  auto& arr = *v.value.target<ossia::vec2f>();
184  auto old_size = self.xv.size();
185  self.xv.assign(arr.begin(), arr.end());
186  auto new_size = 2U;
187  GenericMathMapping::exec_array(
188  v.timestamp, self, output, old_size != new_size);
189  break;
190  }
191  case ossia::val_type::VEC3F: {
192  auto& arr = *v.value.target<ossia::vec3f>();
193  auto old_size = self.xv.size();
194  self.xv.assign(arr.begin(), arr.end());
195  auto new_size = 3U;
196  GenericMathMapping::exec_array(
197  v.timestamp, self, output, old_size != new_size);
198  break;
199  }
200  case ossia::val_type::VEC4F: {
201  auto& arr = *v.value.target<ossia::vec4f>();
202  auto old_size = self.xv.size();
203  self.xv.assign(arr.begin(), arr.end());
204  auto new_size = 4U;
205  GenericMathMapping::exec_array(
206  v.timestamp, self, output, old_size != new_size);
207  break;
208  }
209  case ossia::val_type::LIST: {
210  auto& arr = *v.value.target<std::vector<ossia::value>>();
211  auto old_size = self.xv.size();
212  self.xv.resize(arr.size());
213  auto new_size = arr.size();
214  for(std::size_t i = 0; i < arr.size(); i++)
215  {
216  self.xv[i] = ossia::convert<float>(arr[i]);
217  }
218  GenericMathMapping::exec_array(
219  v.timestamp, self, output, old_size != new_size);
220  break;
221  }
222  }
223  }
224  }
225 };
226 
227 namespace MathMapping
228 {
229 struct Node
230 {
232  {
233  static const constexpr auto prettyName = "Expression Value Filter";
234  static const constexpr auto objectKey = "MathMapping";
235  static const constexpr auto category = "Control/Mappings";
236  static const constexpr auto author = "ossia score, ExprTK (Arash Partow)";
237  static const constexpr auto kind = Process::ProcessCategory::Mapping;
238  static const constexpr auto description
239  = "Applies a math expression to an input.\n"
240  "Available variables: a,b,c, t (samples), dt (delta), pos (position "
241  "in parent), x (value)\n"
242  "See the documentation at http://www.partow.net/programming/exprtk";
243  static const constexpr auto tags = std::array<const char*, 0>{};
244  static const uuid_constexpr auto uuid
245  = make_uuid("ae84e8b6-74ff-4259-aeeb-305d95cdfcab");
246 
247  static const constexpr value_in value_ins[]{value_in{"in", false}};
248  static const constexpr value_out value_outs[]{"out"};
249 
250  static const constexpr auto controls = tuplet::make_tuple(
251  Control::LineEdit("Expression (ExprTK)", "cos(t) + log(pos * (1+abs(x)) / dt)"),
252  Control::FloatSlider("Param (a)", 0., 1., 0.5),
253  Control::FloatSlider("Param (b)", 0., 1., 0.5),
254  Control::FloatSlider("Param (c)", 0., 1., 0.5));
255  };
256  struct State
257  {
258  State()
259  {
260  xv.resize(1024);
261  pxv.resize(1024);
262  pov.resize(1024);
263  expr.add_vector("xv", xv);
264  expr.add_vector("pxv", pxv);
265  expr.add_vector("pov", pov);
266 
267  expr.add_variable("x", x);
268  expr.add_variable("px", px);
269  expr.add_variable("po", po);
270 
271  expr.add_variable("t", cur_time);
272  expr.add_variable("dt", cur_deltatime);
273  expr.add_variable("pos", cur_pos);
274  expr.add_variable("fs", fs);
275 
276  expr.add_variable("a", a);
277  expr.add_variable("b", b);
278  expr.add_variable("c", c);
279  expr.add_variable("pa", pa);
280  expr.add_variable("pb", pb);
281  expr.add_variable("pc", pc);
282 
283  expr.add_variable("m1", m1);
284  expr.add_variable("m2", m2);
285  expr.add_variable("m3", m3);
286  expr.add_constants();
287 
288  expr.register_symbol_table();
289  }
290  std::vector<double> xv;
291  std::vector<double> pxv;
292  std::vector<double> pov;
293 
294  double x{};
295  double px{};
296  double po{};
297 
298  double cur_time{};
299  double cur_deltatime{};
300  double cur_pos{};
301  double fs{44100};
302 
303  double a{}, b{}, c{};
304  double pa{}, pb{}, pc{};
305 
306  double m1{}, m2{}, m3{};
307 
308  ossia::math_expression expr;
309  int64_t last_value_time{};
310 
311  bool ok = false;
312  };
313 
314  using control_policy = ossia::safe_nodes::last_tick;
315  static void
316  run(const ossia::value_port& input, const std::string& expr, float a, float b, float c,
317  ossia::value_port& output, ossia::token_request tk, ossia::exec_state_facade st,
318  State& self)
319  {
320  if(!self.expr.set_expression(expr))
321  return;
322 
323  self.a = a;
324  self.b = b;
325  self.c = c;
326  self.fs = st.sampleRate();
327 
328  if(self.expr.has_variable("xv"))
329  GenericMathMapping<State>::run_array(input, output, tk, st, self);
330  else
331  GenericMathMapping<State>::run_scalar(input, output, tk, st, self);
332 
333  self.pa = a;
334  self.pb = b;
335  self.pc = c;
336  }
337 
338  template <typename... Args>
339  static void item(Args&&... args)
340  {
341  Nodes::mathItem(Metadata::controls, std::forward<Args>(args)...);
342  }
343 };
344 }
345 
346 namespace MathAudioFilter
347 {
348 struct Node
349 {
351  {
352  static const constexpr auto prettyName = "Expression Audio Filter";
353  static const constexpr auto objectKey = "MathAudioFilter";
354  static const constexpr auto category = "Audio/Utilities";
355  static const constexpr auto author = "ossia score, ExprTK (Arash Partow)";
356  static const constexpr auto tags = std::array<const char*, 0>{};
357  static const constexpr auto kind = Process::ProcessCategory::AudioEffect;
358  static const constexpr auto description
359  = "Applies a math expression to an audio input.\n"
360  "Available variables: a,b,c, t (samples), fs (sampling frequency), "
361  "\n"
362  "x (value), px (previous value)\n"
363  "See the documentation at http://www.partow.net/programming/exprtk";
364  static const uuid_constexpr auto uuid
365  = make_uuid("13e1f4b0-1c2c-40e6-93ad-dfc91aac5335");
366 
367  static const constexpr audio_in audio_ins[]{"in"};
368  static const constexpr audio_out audio_outs[]{"out"};
369 
370  static const constexpr auto controls = tuplet::make_tuple(
372  "Expression (ExprTK)",
373  "var n := x[];\n"
374  "\n"
375  "for (var i := 0; i < n; i += 1) {\n"
376  " var dist := tan(x[i]*log(1 + 200 * a));\n"
377  " out[i] := clamp(-1, dist, 1);\n"
378  "}\n"),
379  Control::FloatSlider("Param (a)", 0., 1., 0.5),
380  Control::FloatSlider("Param (b)", 0., 1., 0.5),
381  Control::FloatSlider("Param (c)", 0., 1., 0.5));
382  };
383 
384  struct State
385  {
386  State()
387  {
388  cur_in.reserve(8);
389  cur_out.reserve(8);
390  prev_in.reserve(8);
391  m1.reserve(8);
392  m2.reserve(8);
393  m3.reserve(8);
394  cur_in.resize(2);
395  cur_out.resize(2);
396  prev_in.resize(2);
397  m1.resize(2);
398  m2.resize(2);
399  m3.resize(2);
400 
401  expr.add_vector("x", cur_in);
402  expr.add_vector("out", cur_out);
403  expr.add_vector("px", prev_in);
404  expr.add_variable("t", cur_time);
405  expr.add_variable("a", p1);
406  expr.add_variable("b", p2);
407  expr.add_variable("c", p3);
408  expr.add_vector("m1", m1);
409  expr.add_vector("m2", m2);
410  expr.add_vector("m3", m3);
411  expr.add_variable("fs", fs);
412  expr.add_constants();
413 
414  expr.register_symbol_table();
415  }
416 
417  void reset_symbols(std::size_t N)
418  {
419  if(N == cur_in.size())
420  return;
421 
422  expr.remove_vector("x");
423  expr.remove_vector("out");
424  expr.remove_vector("px");
425  expr.remove_vector("m1");
426  expr.remove_vector("m2");
427  expr.remove_vector("m3");
428 
429  cur_in.resize(N);
430  cur_out.resize(N);
431  prev_in.resize(N);
432  m1.resize(N);
433  m2.resize(N);
434  m3.resize(N);
435 
436  expr.add_vector("x", cur_in);
437  expr.add_vector("out", cur_out);
438  expr.add_vector("px", prev_in);
439  expr.add_vector("m1", m1);
440  expr.add_vector("m2", m2);
441  expr.add_vector("m3", m3);
442 
443  expr.update_symbol_table();
444  }
445 
446  std::vector<double> cur_in{};
447  std::vector<double> cur_out{};
448  std::vector<double> prev_in{};
449  double cur_time{};
450  double p1{}, p2{}, p3{};
451  std::vector<double> m1, m2, m3;
452  double fs{44100};
453  ossia::math_expression expr;
454  bool ok = false;
455  };
456 
457  using control_policy = ossia::safe_nodes::last_tick;
458  static void
459  run(const ossia::audio_port& input, const std::string& expr, float a, float b, float c,
460  ossia::audio_port& output, ossia::token_request tk, ossia::exec_state_facade st,
461  State& self)
462  {
463  if(tk.date > tk.prev_date)
464  {
465  self.fs = st.sampleRate();
466  if(!self.expr.set_expression(expr))
467  return;
468 
469  const auto samplesRatio = st.modelToSamples();
470  const auto [tick_start, count] = st.timings(tk);
471 
472  if(input.empty())
473  return;
474 
475  const auto min_count
476  = std::min((int64_t)input.channel(0).size() - tick_start, count);
477 
478  const int chans = input.channels();
479  self.reset_symbols(chans);
480  output.set_channels(chans);
481 
482  for(int j = 0; j < chans; j++)
483  {
484  auto& out = output.channel(j);
485  out.resize(st.bufferSize(), boost::container::default_init);
486  }
487 
488  self.p1 = a;
489  self.p2 = b;
490  self.p3 = c;
491  const auto start_sample = (tk.prev_date * samplesRatio).impl;
492  for(int64_t i = 0; i < min_count; i++)
493  {
494  for(int j = 0; j < chans; j++)
495  {
496  self.cur_in[j] = input.channel(j)[tick_start + i];
497  }
498  self.cur_time = start_sample + i;
499 
500  // Compute the value
501  self.expr.value();
502 
503  // Apply the output
504  for(int j = 0; j < chans; j++)
505  {
506  output.channel(j)[tick_start + i] = self.cur_out[j];
507  }
508  std::swap(self.cur_in, self.prev_in);
509  }
510  }
511  }
512 
513  template <typename... Args>
514  static void item(Args&&... args)
515  {
516  Nodes::mathItem(Metadata::controls, std::forward<Args>(args)...);
517  }
518 };
519 }
520 
521 namespace MicroMapping
522 {
523 struct Node
524 {
526  {
527  static const constexpr auto prettyName = "Micromap";
528  static const constexpr auto objectKey = "MicroMapping";
529  static const constexpr auto category = "Control/Mappings";
530  static const constexpr auto author = "ossia score, ExprTK (Arash Partow)";
531  static const constexpr auto kind = Process::ProcessCategory::Mapping;
532  static const constexpr auto description = "Applies a math expression to an input.";
533  static const constexpr auto tags = std::array<const char*, 0>{};
534  static const uuid_constexpr auto uuid
535  = make_uuid("25c64b87-a44a-4fed-9f60-0a48906fd3ec");
536 
537  static const constexpr value_in value_ins[]{value_in{"in", false}};
538  static const constexpr value_out value_outs[]{"out"};
539 
540  static const constexpr auto controls
541  = tuplet::make_tuple(Control::LineEdit("Expression", "x / 127"));
542  };
543  struct State
544  {
545  State()
546  {
547  xv.resize(1024);
548  pxv.resize(1024);
549  pov.resize(1024);
550  expr.add_vector("xv", xv);
551  expr.add_vector("pxv", pxv);
552  expr.add_vector("pov", pov);
553 
554  expr.add_variable("x", x);
555  expr.add_variable("px", px);
556  expr.add_variable("po", po);
557 
558  expr.add_variable("t", cur_time);
559  expr.add_variable("dt", cur_deltatime);
560  expr.add_variable("pos", cur_pos);
561  expr.add_constants();
562 
563  expr.register_symbol_table();
564  }
565 
566  std::vector<double> xv;
567  std::vector<double> pxv;
568  std::vector<double> pov;
569 
570  double x{};
571  double px{};
572  double po{};
573 
574  double cur_time{};
575  double cur_deltatime{};
576  double cur_pos{};
577 
578  ossia::math_expression expr;
579  int64_t last_value_time{};
580 
581  //bool ok = false;
582  };
583 
584  using control_policy = ossia::safe_nodes::last_tick;
585 
586  static void
587  run(const ossia::value_port& input, const std::string& expr, ossia::value_port& output,
588  const ossia::token_request& tk, ossia::exec_state_facade st, State& self)
589  {
590  if(!self.expr.set_expression(expr))
591  return;
592 
593  if(self.expr.has_variable("xv"))
594  GenericMathMapping<State>::run_array(input, output, tk, st, self);
595  else
596  GenericMathMapping<State>::run_scalar(input, output, tk, st, self);
597  }
598 
599  template <typename... Args>
600  static void item(Args&&... args)
601  {
602  Nodes::miniMathItem(Metadata::controls, std::forward<Args>(args)...);
603  }
604 };
605 }
606 }
607 
608 namespace Control
609 {
610 template <>
611 struct HasCustomUI<Nodes::MathAudioFilter::Node> : std::true_type
612 {
613 };
614 template <>
615 struct HasCustomUI<Nodes::MathAudioGenerator::Node> : std::true_type
616 {
617 };
618 template <>
619 struct HasCustomUI<Nodes::MathMapping::Node> : std::true_type
620 {
621 };
622 template <>
623 struct HasCustomUI<Nodes::MicroMapping::Node> : std::true_type
624 {
625 };
626 template <>
627 struct HasCustomUI<Nodes::MathGenerator::Node> : std::true_type
628 {
629 };
630 }
Utilities for OSSIA data structures.
Definition: DeviceInterface.hpp:33
Definition: score-lib-process/Control/Widgets.hpp:77
Definition: score-plugin-engine/Engine/Node/Layer.hpp:25
Definition: score-lib-process/Control/Widgets.hpp:417
Definition: SimpleApi.hpp:32
Definition: MathMapping.hpp:8
Definition: MathMapping.hpp:351
Definition: MathMapping.hpp:349
Definition: MathMapping.hpp:232
Definition: MathMapping.hpp:230
Definition: MathMapping.hpp:526
Definition: MathMapping.hpp:524