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