OSSIA
Open Scenario System for Interactive Application
faust_utils.hpp
1 #pragma once
2 #include <ossia/dataflow/graph_node.hpp>
3 #include <ossia/dataflow/port.hpp>
5 
6 #include <faust/gui/UI.h>
7 
8 #include <faust/dsp/poly-llvm-dsp.h>
9 
10 namespace ossia::nodes
11 {
12 
13 template <typename T>
14 struct faust_setup_ui : UI
15 {
16  faust_setup_ui(T& self) { }
17 };
18 
19 template <typename Node, bool Synth>
20 struct faust_exec_ui final : UI
21 {
22  Node& fx;
23  faust_exec_ui(Node& n)
24  : fx{n}
25  {
26  }
27 
28  void addButton(const char* label, FAUSTFLOAT* zone) override
29  {
30  if constexpr(Synth)
31  {
32  using namespace std::literals;
33  if(label == "Panic"sv || label == "gate"sv)
34  return;
35  }
36 
37  fx.root_inputs().push_back(new ossia::value_inlet);
38  fx.controls.push_back(
39  {fx.root_inputs().back()->template target<ossia::value_port>(), zone});
40  }
41 
42  void addCheckButton(const char* label, FAUSTFLOAT* zone) override
43  {
44  addButton(label, zone);
45  }
46 
47  void addVerticalSlider(
48  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min,
49  FAUSTFLOAT max, FAUSTFLOAT step) override
50  {
51  if constexpr(Synth)
52  {
53  using namespace std::literals;
54  if(label == "gain"sv || label == "freq"sv || label == "sustain"sv)
55  return;
56  }
57  fx.root_inputs().push_back(new ossia::value_inlet);
58  fx.controls.push_back(
59  {fx.root_inputs().back()->template target<ossia::value_port>(), zone});
60  }
61 
62  void addHorizontalSlider(
63  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min,
64  FAUSTFLOAT max, FAUSTFLOAT step) override
65  {
66  addVerticalSlider(label, zone, init, min, max, step);
67  }
68 
69  void addNumEntry(
70  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min,
71  FAUSTFLOAT max, FAUSTFLOAT step) override
72  {
73  addVerticalSlider(label, zone, init, min, max, step);
74  }
75 
76  void addHorizontalBargraph(
77  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override
78  {
79  fx.root_outputs().push_back(new ossia::value_outlet);
80  fx.displays.push_back(
81  {fx.root_outputs().back()->template target<ossia::value_port>(), zone});
82  }
83 
84  void addVerticalBargraph(
85  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override
86  {
87  addHorizontalBargraph(label, zone, min, max);
88  }
89 
90  void openTabBox(const char* label) override { }
91  void openHorizontalBox(const char* label) override { }
92  void openVerticalBox(const char* label) override { }
93  void closeBox() override { }
94  void declare(FAUSTFLOAT* zone, const char* key, const char* val) override { }
95  void
96  addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) override
97  {
98  }
99 };
100 
101 template <typename Clone>
102 struct faust_exec_ui_clone final : UI
103 {
104  Clone& self;
105  int i = 0;
106  int o = 0;
107  faust_exec_ui_clone(Clone& s)
108  : self{s}
109  {
110  }
111 
112  void addButton(const char* label, FAUSTFLOAT* zone) override
113  {
114  *zone = *self.controls[i].second;
115  self.controls[i++].second = zone;
116  }
117 
118  void addCheckButton(const char* label, FAUSTFLOAT* zone) override
119  {
120  addButton(label, zone);
121  }
122 
123  void addVerticalSlider(
124  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min,
125  FAUSTFLOAT max, FAUSTFLOAT step) override
126  {
127  *zone = *self.controls[i].second;
128  self.controls[i++].second = zone;
129  }
130 
131  void addHorizontalSlider(
132  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min,
133  FAUSTFLOAT max, FAUSTFLOAT step) override
134  {
135  addVerticalSlider(label, zone, init, min, max, step);
136  }
137 
138  void addNumEntry(
139  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min,
140  FAUSTFLOAT max, FAUSTFLOAT step) override
141  {
142  addVerticalSlider(label, zone, init, min, max, step);
143  }
144 
145  void addHorizontalBargraph(
146  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override
147  {
148  self.displays[o++].second = zone;
149  }
150 
151  void addVerticalBargraph(
152  const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override
153  {
154  addHorizontalBargraph(label, zone, min, max);
155  }
156 
157  void openTabBox(const char* label) override { }
158  void openHorizontalBox(const char* label) override { }
159  void openVerticalBox(const char* label) override { }
160  void closeBox() override { }
161  void declare(FAUSTFLOAT* zone, const char* key, const char* val) override { }
162  void
163  addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) override
164  {
165  }
166 };
167 
168 struct faust_node_utils
169 {
170  template <typename Node>
171  static void copy_controls(Node& self)
172  {
173  for(auto ctrl : self.controls)
174  {
175  auto& dat = ctrl.first->get_data();
176  if(!dat.empty())
177  {
178  *ctrl.second = ossia::convert<float>(dat.back().value);
179  }
180  }
181  }
182 
183  template <typename Node>
184  static void copy_displays(Node& self, int64_t ts)
185  {
186  for(auto ctrl : self.displays)
187  {
188  ctrl.first->write_value(*ctrl.second, ts);
189  }
190  }
191 
192  template <typename Node>
193  static void copy_input(
194  Node& self, int64_t d, int64_t n_in, float* inputs_, float** input_n,
195  const ossia::audio_port& audio_in)
196  {
197  // TODO offset !!!
198  for(int64_t i = 0; i < n_in; i++)
199  {
200  input_n[i] = inputs_ + i * d;
201  if(int64_t(audio_in.channels()) > i)
202  {
203  auto num_samples = std::min((int64_t)d, (int64_t)audio_in.channel(i).size());
204  for(int64_t j = 0; j < num_samples; j++)
205  {
206  input_n[i][j] = (float)audio_in.channel(i)[j];
207  }
208 
209  if(d > int64_t(audio_in.channel(i).size()))
210  {
211  for(int64_t j = audio_in.channel(i).size(); j < d; j++)
212  {
213  input_n[i][j] = 0.f;
214  }
215  }
216  }
217  else
218  {
219  for(int64_t j = 0; j < d; j++)
220  {
221  input_n[i][j] = 0.f;
222  }
223  }
224  }
225  }
226  template <typename Node>
227  static void copy_input_mono(
228  Node& self, int64_t d, int64_t i, float* input,
229  const ossia::audio_channel& audio_in)
230  {
231  // TODO offset !!!
232  auto num_samples = std::min((int64_t)d, (int64_t)audio_in.size());
233  for(int64_t j = 0; j < num_samples; j++)
234  {
235  input[j] = (float)audio_in[j];
236  }
237 
238  if(d > int64_t(audio_in.size()))
239  {
240  for(int64_t j = audio_in.size(); j < d; j++)
241  {
242  input[j] = 0.f;
243  }
244  }
245  }
246 
247  template <typename Node>
248  static void
249  init_output(Node& self, int64_t d, int64_t n_out, float* outputs_, float** output_n)
250  {
251  for(int64_t i = 0; i < n_out; i++)
252  {
253  output_n[i] = outputs_ + i * d;
254  for(int64_t j = 0; j < d; j++)
255  {
256  output_n[i][j] = 0.f;
257  }
258  }
259  }
260 
261  template <typename Node>
262  static void copy_output(
263  Node& self, int64_t d, int64_t n_out, float* outputs_, float** output_n,
264  ossia::audio_port& audio_out)
265  {
266  audio_out.set_channels(n_out);
267  for(int64_t i = 0; i < n_out; i++)
268  {
269  audio_out.channel(i).resize(d);
270  for(int64_t j = 0; j < d; j++)
271  {
272  audio_out.channel(i)[j] = (double)output_n[i][j];
273  }
274  }
275 
276  // TODO handle multichannel cleanly
277  if(n_out == 1)
278  {
279  audio_out.set_channels(2);
280  audio_out.channel(1) = audio_out.channel(0);
281  }
282  }
283 
284  template <typename Node, typename Dsp>
285  static void copy_midi(Node& self, Dsp& dsp, const ossia::midi_port& midi_in)
286  {
287  // TODO offset !!!
288 
289  for(const libremidi::message& mess : midi_in.messages)
290  {
291  switch(mess.get_message_type())
292  {
293  case libremidi::message_type::NOTE_ON: {
294  self.in_flight[mess[1]]++;
295  dsp.keyOn(mess[0], mess[1], mess[2]);
296  break;
297  }
298  case libremidi::message_type::NOTE_OFF: {
299  self.in_flight[mess[1]]--;
300  dsp.keyOff(mess[0], mess[1], mess[2]);
301  break;
302  }
303  case libremidi::message_type::CONTROL_CHANGE: {
304  dsp.ctrlChange(mess[0], mess[1], mess[2]);
305  break;
306  }
307  case libremidi::message_type::PITCH_BEND: {
308  dsp.pitchWheel(mess[0], mess.bytes[2] * 128 + mess.bytes[1]);
309  break;
310  }
311  default:
312  break;
313  // TODO continue...
314  }
315  }
316  }
317 
318  template <typename Node, typename DspPoly>
319  void all_notes_off(Node& self, DspPoly& dsp)
320  {
321  for(int k = 0; k < 128; k++)
322  while(self.in_flight[k]-- > 0)
323  dsp.keyOff(1, k, 0);
324  }
325 
327 
328  template <typename Node, typename Dsp>
329  static void do_exec(
330  Node& self, Dsp& dsp, const ossia::token_request& tk,
331  const ossia::exec_state_facade& e)
332  {
333  const auto [st, d] = e.timings(tk);
334  ossia::audio_port& audio_in
335  = self.root_inputs()[0]->template cast<ossia::audio_port>();
336  ossia::audio_port& audio_out
337  = self.root_outputs()[0]->template cast<ossia::audio_port>();
338 
339  const int64_t n_in = dsp.getNumInputs();
340  const int64_t n_out = dsp.getNumOutputs();
341  audio_in.set_channels(n_in);
342  audio_out.set_channels(n_out);
343 
344  if constexpr(std::is_same_v<FAUSTFLOAT, float>)
345  {
346  float* inputs_ = (float*)alloca(n_in * d * sizeof(float));
347  float* outputs_ = (float*)alloca(n_out * d * sizeof(float));
348 
349  float** input_n = (float**)alloca(sizeof(float*) * n_in);
350  float** output_n = (float**)alloca(sizeof(float*) * n_out);
351 
352  copy_input(self, d, n_in, inputs_, input_n, audio_in);
353  init_output(self, d, n_out, outputs_, output_n);
354  dsp.compute(d, input_n, output_n);
355  copy_output(self, d, n_out, outputs_, output_n, audio_out);
356  }
357  else
358  {
359  double** input_n = (double**)alloca(sizeof(double*) * n_in);
360  double** output_n = (double**)alloca(sizeof(double*) * n_out);
361  for(int i = 0; i < n_in; i++)
362  {
363  audio_in.channel(i).resize(e.bufferSize());
364  input_n[i] = audio_in.channel(i).data() + st;
365  }
366  if(BOOST_LIKELY(st == 0 && d == e.bufferSize()))
367  {
368  for(int i = 0; i < n_out; i++)
369  {
370  audio_out.channel(i).resize(e.bufferSize(), boost::container::default_init);
371  output_n[i] = audio_out.channel(i).data() + st;
372  }
373  }
374  else
375  {
376  for(int i = 0; i < n_out; i++)
377  {
378  audio_out.channel(i).resize(e.bufferSize());
379  output_n[i] = audio_out.channel(i).data() + st;
380  }
381  }
382 
383  dsp.compute(d, input_n, output_n);
384  }
385  }
386 
387  template <typename Node, typename Dsp>
388  static void exec(
389  Node& self, Dsp& dsp, const ossia::token_request& tk,
390  const ossia::exec_state_facade& e)
391  {
392  if(tk.forward())
393  {
394  const auto [st, d] = e.timings(tk);
395  copy_controls(self);
396 
397  if(d == 0)
398  return;
399  do_exec(self, dsp, tk, e);
400  copy_displays(self, st);
401  }
402  }
403 
404  template <typename Node, typename Dsp>
405  static void do_exec_mono_fx(
406  Node& self, Dsp& dsp, const ossia::token_request& tk,
407  const ossia::exec_state_facade& e)
408  {
409  const auto [st, d] = e.timings(tk);
410  if(d == 0)
411  return;
412 
413  ossia::audio_port& audio_in
414  = self.root_inputs()[0]->template cast<ossia::audio_port>();
415  ossia::audio_port& audio_out
416  = self.root_outputs()[0]->template cast<ossia::audio_port>();
417 
418  const int64_t n_in = audio_in.channels();
419  audio_out.set_channels(n_in);
420  while(self.clones.size() < n_in)
421  {
422  self.clones.emplace_back(dsp.clone(), self.clones[0]);
423  }
424 
425  {
426  for(int k = 0; k < self.controls.size(); ++k)
427  {
428  auto ctrl = self.controls[k];
429  auto& dat = ctrl.first->get_data();
430  if(!dat.empty())
431  {
432  float v = ossia::convert<float>(dat.back().value);
433  self.set_control(k, v);
434  }
435  }
436  }
437 
438  if constexpr(std::is_same_v<FAUSTFLOAT, float>)
439  {
440  float* input = (float*)alloca(d * sizeof(float));
441  memset(input, 0, d * sizeof(float));
442  float* output = (float*)alloca(d * sizeof(float));
443 
444  for(int i = 0; i < n_in; i++)
445  {
446  auto& in_chan = audio_in.channel(i);
447  auto& out_chan = audio_out.channel(i);
448  auto& clone = self.clones[i];
449  in_chan.resize(e.bufferSize());
450  out_chan.resize(e.bufferSize());
451 
452  copy_input_mono(self, d, n_in, input, in_chan);
453  memset(output, 0, d * sizeof(float));
454  for(int z = 0; z < d; z++)
455  {
456  assert(!std::isnan(input[z]));
457  assert(!std::isinf(input[z]));
458  }
459  clone.fx->compute(d, &input, &output);
460  for(int z = 0; z < d; z++)
461  {
462  if(std::fpclassify(output[z]) != FP_NORMAL)
463  output[z] = 0.f;
464  }
465 
466  std::copy_n(output, d, out_chan.data() + st);
467  for(int z = 0; z < e.bufferSize(); z++)
468  {
469  assert(!std::isnan(out_chan[z]));
470  assert(!std::isinf(out_chan[z]));
471  }
472  }
473  }
474  else
475  {
476  for(int i = 0; i < n_in; i++)
477  {
478  auto& in_chan = audio_in.channel(i);
479  auto& out_chan = audio_out.channel(i);
480  in_chan.resize(e.bufferSize());
481  out_chan.resize(e.bufferSize());
482 
483  double* input = in_chan.data() + st;
484  double* output = out_chan.data() + st;
485 
486  self.clones[i].fx->compute(d, &input, &output);
487  }
488  }
489  }
490 
491  template <typename Node, typename Dsp>
492  static void exec_mono_fx(
493  Node& self, Dsp& dsp, const ossia::token_request& tk,
494  const ossia::exec_state_facade& e)
495  {
496  if(tk.forward())
497  {
498  const auto [st, d] = e.timings(tk);
499 
500  if(d == 0)
501  return;
502  do_exec_mono_fx(self, dsp, tk, e);
503  copy_displays(self, st);
504  }
505  }
506 
508  template <typename Node, typename DspPoly>
509  void exec_synth(
510  Node& self, DspPoly& dsp, const ossia::token_request& tk,
511  const ossia::exec_state_facade& e)
512  {
513  if(tk.forward())
514  {
515  const auto [st, d] = e.timings(tk);
516 
517  auto& midi_in = self.root_inputs()[1]->template cast<ossia::midi_port>();
518 
519  copy_controls(self);
520  dsp.updateAllZones();
521  copy_midi(self, dsp, midi_in);
522 
523  if(d == 0)
524  return;
525  do_exec(self, dsp, tk, e);
526  copy_displays(self, st);
527  }
528  }
529 };
530 
531 // NOTE: all the code below taken and slightly modified from poly-dsp
532 // with small modifications to allow thread-safe update of GroupUI
533 // Keep up-to-date with updates in faust.. last update 2020-10-18
534 // GPLv3
535 // Copyright is the Faust team and Faust contributors
536 // Copyright (C) 2003-2017 GRAME, Centre National de Creation Musicale
537 class custom_dsp_poly_effect : public dsp_poly
538 {
539 
540 private:
541  mydsp_poly* fPolyDSP;
542 
543 public:
544  custom_dsp_poly_effect(mydsp_poly* dsp1, dsp* dsp2)
545  : dsp_poly(dsp2)
546  , fPolyDSP(dsp1)
547  {
548  }
549 
550  virtual ~custom_dsp_poly_effect()
551  {
552  // dsp_poly_effect is also a decorator_dsp, which will free fPolyDSP
553  }
554 
555  void updateAllZones() { fPolyDSP->fGroups.updateAllZones(); }
556  // MIDI API
557  MapUI* keyOn(int channel, int pitch, int velocity)
558  {
559  return fPolyDSP->keyOn(channel, pitch, velocity);
560  }
561  void keyOff(int channel, int pitch, int velocity)
562  {
563  fPolyDSP->keyOff(channel, pitch, velocity);
564  }
565  void keyPress(int channel, int pitch, int press)
566  {
567  fPolyDSP->keyPress(channel, pitch, press);
568  }
569  void chanPress(int channel, int press) { fPolyDSP->chanPress(channel, press); }
570  void ctrlChange(int channel, int ctrl, int value)
571  {
572  fPolyDSP->ctrlChange(channel, ctrl, value);
573  }
574  void ctrlChange14bits(int channel, int ctrl, int value)
575  {
576  fPolyDSP->ctrlChange14bits(channel, ctrl, value);
577  }
578  void pitchWheel(int channel, int wheel) { fPolyDSP->pitchWheel(channel, wheel); }
579  void progChange(int channel, int pgm) { fPolyDSP->progChange(channel, pgm); }
580 };
581 
582 struct custom_dsp_poly_factory : public dsp_factory
583 {
584  dsp_factory* fProcessFactory;
585  dsp_factory* fEffectFactory;
586 
587  std::vector<std::string> getWarningMessages() { return {}; }
588 
589  std::string getEffectCode(const std::string& dsp_content)
590  {
591  std::stringstream effect_code;
592  effect_code << "adapt(1,1) = _; adapt(2,2) = _,_; adapt(1,2) = _ <: _,_; "
593  "adapt(2,1) = _,_ :> _;";
594  effect_code << "adaptor(F,G) = adapt(outputs(F),inputs(G)); dsp_code = "
595  "environment{ "
596  << dsp_content << " };";
597  effect_code << "process = adaptor(dsp_code.process, dsp_code.effect) : "
598  "dsp_code.effect;";
599  return effect_code.str();
600  }
601 
602  custom_dsp_poly_factory(
603  dsp_factory* process_factory = NULL, dsp_factory* effect_factory = NULL)
604  : fProcessFactory(process_factory)
605  , fEffectFactory(effect_factory)
606  {
607  }
608 
609  virtual ~custom_dsp_poly_factory() = default;
610 
611  virtual std::string getName() { return fProcessFactory->getName(); }
612  virtual std::string getSHAKey() { return fProcessFactory->getSHAKey(); }
613  virtual std::string getDSPCode() { return fProcessFactory->getDSPCode(); }
614  virtual std::string getCompileOptions()
615  {
616  return fProcessFactory->getCompileOptions();
617  }
618  virtual std::vector<std::string> getLibraryList()
619  {
620  return fProcessFactory->getLibraryList();
621  }
622  virtual std::vector<std::string> getIncludePathnames()
623  {
624  return fProcessFactory->getIncludePathnames();
625  }
626 
627  virtual void setMemoryManager(dsp_memory_manager* manager)
628  {
629  fProcessFactory->setMemoryManager(manager);
630  if(fEffectFactory)
631  {
632  fEffectFactory->setMemoryManager(manager);
633  }
634  }
635  virtual dsp_memory_manager* getMemoryManager()
636  {
637  return fProcessFactory->getMemoryManager();
638  }
639 
640  /* Create a new polyphonic DSP instance with global effect, to be deleted
641  * with C++ 'delete'
642  *
643  * @param nvoices - number of polyphony voices, should be at least 1
644  * @param control - whether voices will be dynamically allocated and
645  * controlled (typically by a MIDI controller). If false all voices are always
646  * running.
647  * @param group - if true, voices are not individually accessible, a global
648  * "Voices" tab will automatically dispatch a given control on all voices,
649  * assuming GUI::updateAllGuis() is called. If false, all voices can be
650  * individually controlled.
651  */
652  custom_dsp_poly_effect* createPolyDSPInstance(int nvoices, bool control, bool group)
653  {
654  auto dsp_poly
655  = new mydsp_poly(fProcessFactory->createDSPInstance(), nvoices, control, group);
656  if(fEffectFactory)
657  {
658  // the 'dsp_poly' object has to be controlled with MIDI, so kept
659  // separated from new dsp_sequencer(...) object
660  return new custom_dsp_poly_effect(
661  dsp_poly, new dsp_sequencer(dsp_poly, fEffectFactory->createDSPInstance()));
662  }
663  else
664  {
665  return new custom_dsp_poly_effect(dsp_poly, dsp_poly);
666  }
667  }
668 
669  /* Create a new DSP instance, to be deleted with C++ 'delete' */
670  dsp* createDSPInstance() { return fProcessFactory->createDSPInstance(); }
671 };
672 struct custom_llvm_dsp_poly_factory : public custom_dsp_poly_factory
673 {
674  custom_llvm_dsp_poly_factory(
675  const std::string& name_app, const std::string& dsp_content, int argc,
676  const char* argv[], const std::string& target, std::string& error_msg,
677  int opt_level = -1)
678  {
679  fProcessFactory = createDSPFactoryFromString(
680  name_app, dsp_content, argc, argv, target, error_msg);
681  if(fProcessFactory)
682  {
683  fEffectFactory = createDSPFactoryFromString(
684  name_app, getEffectCode(dsp_content), argc, argv, target, error_msg);
685  if(!fEffectFactory)
686  {
687  std::cerr << "llvm_dsp_poly_factory : fEffectFactory " << error_msg;
688  // The error message is not really needed...
689  error_msg = "";
690  }
691  }
692  else
693  {
694  std::cerr << "llvm_dsp_poly_factory : fProcessFactory " << error_msg;
695  throw std::bad_alloc();
696  }
697  }
698 
699  virtual ~custom_llvm_dsp_poly_factory()
700  {
701  deleteDSPFactory(static_cast<llvm_dsp_factory*>(fProcessFactory));
702  deleteDSPFactory(static_cast<llvm_dsp_factory*>(fEffectFactory));
703  }
704 };
705 
706 static custom_llvm_dsp_poly_factory* createCustomPolyDSPFactoryFromString(
707  const std::string& name_app, const std::string& dsp_content, int argc,
708  const char* argv[], const std::string& target, std::string& error_msg,
709  int opt_level = -1)
710 {
711  try
712  {
713  return new custom_llvm_dsp_poly_factory(
714  name_app, dsp_content, argc, argv, target, error_msg, opt_level);
715  }
716  catch(...)
717  {
718  return NULL;
719  }
720 }
721 
722 }
constexpr OSSIA_INLINE auto min(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
min function tailored for values
Definition: math.hpp:125
constexpr OSSIA_INLINE auto max(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
max function tailored for values
Definition: math.hpp:96