fpp is a standalone Perl script with no dependencies which allows ANY C/C++ code in .dsp as long as you are targeting C/C++ in scalar mode.
Just change your Makefiles / scripts to run fpp instead of faust, fpp will call faust internally.
See also INSTALL.txt
$ cat /tmp/test.dsp
// suboptimal re-implementation of the 'mem' primitive.
z = FPP(input)
{
// declare the state variable.
// '$' at the start of the name ensures that each instance
// gets its own variable.
decl: float $z;
// initialize the state variable in instanceConstants()
init: $z = 0;
// this goes to compute()
exec:
float ret = $z;
// another use-case for '$' at the start of name,
// access the value of the `input` argument.
$z = $input;
ret;
};
// similar to process = @(2),@(2);
process = par(i,2, z:z);
$ fpp -s /tmp/test.dsp
float z__01;
float z__02;
float z__03;
float z__04;
int fSampleRate;
void instanceConstants(int sample_rate)
{
fSampleRate = sample_rate;
z__01 = 0;
z__02 = 0;
z__03 = 0;
z__04 = 0;
}
void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs)
{
FAUSTFLOAT* input0 = inputs[0];
FAUSTFLOAT* input1 = inputs[1];
FAUSTFLOAT* output0 = outputs[0];
FAUSTFLOAT* output1 = outputs[1];
for (int i0 = 0; i0 < count; i0 = i0 + 1) {
output0[i0] = FAUSTFLOAT(({ float ret = z__02; z__02 = ({ float ret = z__01; z__01 = input0[i0]; ret; }); ret; }));
output1[i0] = FAUSTFLOAT(({ float ret = z__04; z__04 = ({ float ret = z__03; z__03 = input1[i0]; ret; }); ret; }));
}
}
Suppose we have a 'foo' library with the following API:
// must be called only once
void foo_init_lib(sample_rate);
struct foo_state { ... };
void foo_init_state(struct foo_state *, float fc0);
float foo_compute(struct foo_state *, float inp);
To simplify, let's assume that this filter can't be modulated and therefore fc0 should be a constant known at init time (e.g. ma.SR/4).
How can we use this lib in faust? My best attempt:
#include <foo.h>
class faust_foo : public dsp {
foo_state state;
float faust_foo_init(int sr, float fc0)
{
foo_init_lib(sr);
foo_init_state(&state, fc0);
return 0; // unused
}
float faust_foo_compute(float inp)
{
return foo_compute(&state, inp);
}
};
import("stdfaust.lib");
foo(fc0, inp) = foo_compute(inp) : attach(foo_init(ma.SR, fc0))
with {
foo_init = ffunction(float faust_foo_init(int, float), "faust_foo.h","");
foo_compute = ffunction(float faust_foo_compute(float), "faust_foo.h","");
};
process = foo(ma.SR/4);
Solved? Not really:
-
This is cumbersome and error prone.
-
faust thinks that foo() is a pure function, so for example
process = 1 : foo(ma.SR/4); // step response
won't work.
-
But the main problem is that foo() can be used only once in .dsp, e.g.
process = foo(10),foo(20);
will be happily compiled and output garbage.
this .dsp code
import("stdfaust.lib");
// can be used just like any other faust function
foo = FPP(fc0, inp) {
INIT: foo_init_lib($(ma.SR));
decl: struct foo_state $state;
init: foo_init_state(&$state, $fc0);
exec: foo_compute(&$state, $inp);
};
process = foo(10),foo(20);
compiles to
struct foo_state state__01;
struct foo_state state__02;
int fSampleRate;
void instanceConstants(int sample_rate)
{
fSampleRate = sample_rate;
float fConst0 = std::min<float>(1.92e+05f, std::max<float>(1.0f, float(fSampleRate)));
foo_init_lib(fConst0);
foo_init_state(&state__01, 10);
foo_init_state(&state__02, 20);
}
void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs)
{
FAUSTFLOAT* input0 = inputs[0];
FAUSTFLOAT* input1 = inputs[1];
FAUSTFLOAT* output0 = outputs[0];
FAUSTFLOAT* output1 = outputs[1];
for (int i0 = 0; i0 < count; i0 = i0 + 1) {
output0[i0] = FAUSTFLOAT(({ foo_compute(&state__01, input0[i0]); }));
output1[i0] = FAUSTFLOAT(({ foo_compute(&state__02, input1[i0]); }));
}
}
and this is what we need.