## More complex example than the WDF model ?

Started by sletz, Aug 24, 2023, 01:21 PM

#### sletz

I'm experimenting emulating Ciaramella using the Faust signal API. Do you guys have some more complex examples like the WDF one here: https://github.com/paolomarrone/Zampogna/blob/main/examples/lp_wdf/lp_wdf.crm and not using the if construct ?
Thanks.

#### stefano

Hi @sletz, here is one slightly more complex example from @chowdsp: https://github.com/jatinchowdhury18/wdf-bakeoff/blob/main/src/ciaramella_wdf/lpf2.crm.

If you need something more complex just ask.
Personal website: https://dangelo.audio/

#### sletz

Thanks, I'll try this one. But yes I'm interested in other demanding use-cases, outside of WDF ?

#### stefano

WDFs are by far the worst offenders, but we actually use Ciaramella mostly for state-space systems.

Here's a simple state-space diode clipper written in Ciaramella:

`y = diode_clipper (x) { Is = 1e-16 VT = 0.026 R = 2.2e3 C = 0.01e-6 k1 = 1.0 / (R * C) k4 = 1.0 / VT B0 = 2 * fs B1 = -B0 A1 = 1 k2 = (C * R) / (B0 * C * R + 1.0) k3 = (Is * R) / (B0 * C * R + 1.0) k5 = log((Is * R) / ((B0 * C * R + 1.0) * VT)) k6 = B1 - A1 * B0 p_z1 = delay1(p) q = k1 * x - p_z1 r = sign(q) w = k2 * q + k3 * r y = w - VT * r * omega(k4 * r * w + k5) p = k6 * x - A1 * p_z1 @p = 0 }`
Note that Zampogna doesn't implement the required functions but still treats unknown block ids as static functions, so for example you actually get decent C output from the web IDE. You need to supply the missing functions yourself.

In this case, the model is described in this this paper and here's the original GNU Octave code.
Personal website: https://dangelo.audio/

#### sletz

#4
Thanks. Here is what I've tried:

- I'm using the Faust signal API, a way to "plug" at an intermediate stage in the compilation chain: https://faustdoc.grame.fr/tutorials/signal-api/

- the standard way to describe recursive signals is to use sigSelf/sigRecursion or sigSelfN/sigRecursionN but that always add a one sample delay in the loop.

- so I have added new functions sigSelfZero/sigSelfZeroN that do not add this one sample delay in the loop. The idea is that now the delay has to be added explicitly using sigDelay1 (in our API).

- then I tried to manually convert several Ciamarella examples in Faust signal API (which is actually quite straightforward), compile them to look at the generated C++ code, and run them with a noise test signal along the Ciamarella C++ generated code.

- I did that on 5 examples and 4 are behaving the same as Ciamarella C++ code. One is completely failing with incorrect C++ generated code. The ones that work are somewhat generating suboptimal code (compared to the Faust code generation "standard") and this probably reveal a more generic issue. You can look at the test code here: https://github.com/grame-cncm/faust/blob/ciaramella/tools/benchmark/ciaramella.cpp, and the test program log here: https://gist.github.com/sletz/992d1f0ce6096600542c3ca1eb4fff34

- TBH, I'm not even sure the Faust code generator is ready for this kind of "outside of what it is designed for" kind of use-case. But it is still interesting to see that a lot of tested DSP just work, and I think we should dig that a bit more on our side. I'll discuss this with Yann.

#### stefano

BTW, @paolo is working hard on a new version of Ciaramella which will be the first stable one. This will introduce a few new things, most notably types (float, int, bool) and importing/wrapping of C code into Ciaramella blocks. The whole compilation process remains similar, so same no-delay feedback etc.

I don't know how interesting that is to you at the moment, but at least you know where we are going.
Personal website: https://dangelo.audio/

#### sletz

#6
Thanks for the infos.

Actually I was able to make the lp_filter2 example work by correcting the generated Faust signal API tree in lp_filter2 function, generating the Faust C++ and manually editing and fixing the Faust C++ class because of incorrect variable dependancies (so we definitively have a problem in our code generator in this kind of use-case). Pushed on our ciaramella branch: https://github.com/grame-cncm/faust/commit/599de3fc61fd67bdbfe5d698f483e4ec471d22d4

So the good news is that this idea of translating ciaramella => Faust signal API seems doable which definitively opens interesting possible applications, like
- taking profit of several code generators we have (scalar, vector, parallel, see https://faustdoc.grame.fr/manual/compiler/)
- targeting all backends we have developed
- so basically targeting the whole Faust ecosystem...

#### stefano

Ok, that's good news.

Once Ciaramella is stable and the Faust signal API can support it, we'll be finally to evaluate adding it as a stable target.
Personal website: https://dangelo.audio/

#### sletz

Thanks. On the contrary, what would be simpler (possibly the simplest ?) WDF examples that I could test to better understand the code generation issue we see?

#### sletz

Quote from: stefano on Aug 26, 2023, 05:39 PMa new version of Ciaramella which will be the first stable one

I see this new "mem" operator in https://github.com/paolomarrone/Zampogna/blob/dev/test/test_syntax.js. Is this to implement delay lines? Any more documentation on that ?

#### stefano

Quote from: sletz on Aug 27, 2023, 08:54 PMThanks. On the contrary, what would be simpler (possibly the simplest ?) WDF examples that I could test to better understand the code generation issue we see?

The simplest example I can think of is the simple lowpass filter example in the web IDE (lp_filter, not lp_filter3).

Quote from: sletz on Aug 28, 2023, 09:55 AM
Quote from: stefano on Aug 26, 2023, 05:39 PMa new version of Ciaramella which will be the first stable one

I see this new "mem" operator in https://github.com/paolomarrone/Zampogna/blob/dev/test/test_syntax.js. Is this to implement delay lines? Any more documentation on that ?

No documentation yet. So far, it's meant to implement delays and lookup tables.
Personal website: https://dangelo.audio/

#### sletz

About delays, in case it helps, in Faust:
- we have a general delay operator https://faustdoc.grame.fr/manual/syntax/#time-expression_1
- which is part of the box API (https://faustdoc.grame.fr/tutorials/box-api/#defining-a-delay-expression) and then signal API (https://faustdoc.grame.fr/tutorials/signal-api/#defining-delayed-signals)
- so that the user does not have to explicitly think about how the delay line is finally implemented by the compiler, but then can possibly control it, if needed, for specific CPU/memory optimisation purposes. This allow for nice optimisation capabilities we discovered over the years see: https://faustdoc.grame.fr/manual/optimizing/#delay-lines-implementation-and-dsp-memory-size

#### sletz

And this for rtable/rwtable: https://faustdoc.grame.fr/manual/syntax/#table-primitives, also available in box and signal API.

#### paolo

Hello @sletz,

that work sounds promising. Adding a Ciaramella -> Faust Signal API is surely a good thing.

Concerning those bugs, did you take into account the need of scheduling? Once you use the SigSelfZero and SigDelay1 functions you will lose the direct information of where the delay is in the loop. In the scheduling phase of Zampogna we just apply a DFS starting from the general outputs and the delay inputs: this allows to detect delay-free loops and find out one of the computable orders. You need to find that order at some point for sure.

Quote from: sletz on Aug 28, 2023, 09:55 AM
Quote from: stefano on Aug 26, 2023, 05:39 PMa new version of Ciaramella which will be the first stable one

I see this new "mem" operator in https://github.com/paolomarrone/Zampogna/blob/dev/test/test_syntax.js. Is this to implement delay lines? Any more documentation on that ?

No doc yet, implementation is in progress.
Yes, it is to implement delays in general, even the unitary one.
The only constraint is that readings happen before writings.

#### sletz

Quote from: paolo on Aug 28, 2023, 10:35 AMOnce you use the SigSelfZero and SigDelay1 functions you will lose the direct information of where the delay is in the loop. In the scheduling phase of Zampogna we just apply a DFS starting from the general outputs and the delay inputs: this allows to detect delay-free loops and find out one of the computable orders.

Yes, I'm thinking of this kind of question. And yes probably the way the Faust box language usually works (so by explicitly adding the one sample delay) possibly makes this step unneeded "by construction". I'll have to check that with Yann.

The interesting theoretical point is that if we add this scheduling phase in the Faust signal language (so when needed), then we open all kind of interesting new use-cases because we would have a more general framework. Like "trying to plug Ciaramella language on top of Faust signal API" discussion reveals. And this is really helpful for us.