Lately, I have been looking into faust – functional audio stream, an interesting language for DSP developed by the guys at GRAME, mainly Yann Orlarey and Stéphane Letz. This is a purely functional language for writing programs that can be compiled into C++ code for various platforms (PD, Csound, SC3 plugins; standalone applications; LLVM; mobile apps; etc). It has also a nice feature which lets you compile Mathematical documents from it to describe the DSP process implemented in the program.
The main thing about this is that we have to get into the functional mode of thinking in order to use and appreciate the system. For instance, typical imperative code will not be directly translatable to faust. Here are some basic elements of the language:
1) The entry point in faust is a ‘process’:
process = ...
2) There are a basic set of language primitives: arithmetic and comparison operators (including bitwise operations); means of foreign (C++) function, variable and constant access; identity and cut ‘boxes’ (for passing or stopping signals); memory (single-sample) and delay operators; tables (read-only and read-write); selection switch; GUI components (slider, button, number box, etc.).
3) Programs combine these operations using one of five compositions: serial (:); parallel (,); split (<: merge=””>); recursion (~).</:>
and that’s it (there are some further language elements, but with these we can already do a lot).
Now the complication starts. Let’s program a simple sinewave signal. The first thing we need to do is to get a way to represent time in samples. In an imperative way, we could do this with a loop like this
for(i=0; i < end; i++) ...
Well, there are no loop primitives here. So what can we do? We use recursion. Here’s the time passing in faust:
time = (+(1) ~ _ ) - 1;
The recursion operator ~ combines a signal identity ( _ ), which can be understood as the output of the process, and the function +(1), which adds one to it. The recursive composition implies a 1-sample delay as the signal is fed back. So (+(1) ~ _ ) gives us a sequence 1,2,3,… . The subtraction by 1 is to bring the start of the sequence to 0, as we normally treat time sequences as 0-based.
Now, with time in hand, we can generate a sinewave signal. We need a sine function, which we can obtain as a C++ function using a primitive:
sine = ffunction(float sinf (float), <math.h>, "");</math.h>
Similarly we can define PI and SR constants
PI = 3.1415926535897932385;
SR = 44100.;
With this, we have a faust program that produces a 440Hz sinewave:
process = time : *(2*PI/SR) : *(440) : sine;
You can see the (extreme) use of the serial composition operator. Start with the sample counter, multiply it by 2*PI/SR so the signal now goes from 0 to 2*PI in 44100 samples, then multiply it by 440Hz to make the signal go to multiples of 2*PI 440 times every 44100 samples. This signal then is sent to the sine function.
For those of you who find this too painful, faust allows a more usual function notation as syntatic sugar (not allowed for diabetics):
process = sine(2*PI*440*time/SR);
So there you go, Faust. I will hopefully bring some further examples to the mix in later posts.