Faust is a functional programming language specifically designed for Digital Signal Processing (DSL). The Faust compiler can be used to generate highly optimized C++ code. In this tutorial, we show how to use Faust to make C++ DSP modules and how to integrate them to your JUCE projects.
This page contains very little information about the Faust language itself. If you wish to learn more about Faust, you can refer to the following resources:
Also, if you'd like to develop your own Faust programs, we recommend you to use FaustLive. FaustLive installation packages are available for OSX, Windows and Ubuntu and can be downloaded here. FaustLive can also be built from scratch by getting a snapshot of the source code. That's actually the best thing to do if you wanna get the latest version.
To get more information on how to use FaustLive, we recommend you to check the CCRMA Faust Online Course.
First, download and compile the Faust compiler. If you want to get the latest version, use git, otherwise download the latest stable release (version 0.9.90 as of Sept. 23, 2016). Please, don't use a package manager to install Faust on your system as they might not have a recent version of Faust required for this tutorial. Faust has no dependencies so this step should go very smoothly. If you're lazy and don't want to install Faust, you can use the Faust online compiler.
Make sure that Faust was properly installed on your system by typing: faust
in a terminal. If
you get an error message looking like ERROR : no files specified; for help type "faust --help
,
then you're good, otherwise, try to find the problem or send a message to Tim or Romain.
You're now ready to write your first Faust code! Create a new text file with you favorite editor (like not
TextEdit on the Mac...) and give
it the .dsp
extension (e.g. myCode.dsp
). Write the following code in it:
process = +;
process
is the most fundamental element of a Faust code and should always be declared. It corresponds
to the highest level of your algorithm and gives access to the inputs and outputs of the object to be generated.
This simple program basically adds 2 signals together and output the result. Let's convert it to a C++ class
by executing the following command:
faust myCode.dsp
By default, Faust prints the result to the standard output that can be redirected to a file simply by adding
the -o
option:
faust myCode.dsp -o myCode.cpp
Open myCode.cpp
. You will find a class called mydsp
. Look for it's compute
method:
virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
FAUSTFLOAT* input0 = inputs[0];
FAUSTFLOAT* input1 = inputs[1];
FAUSTFLOAT* output0 = outputs[0];
for (int i = 0; (i < count); i = (i + 1)) {
output0[i] = FAUSTFLOAT((float(input0[i]) + float(input1[i])));
}
}
This method can be used to compute one block of audio by specifying the block size (count
),
the input buffer and the output buffer. Note that these are double arrays where:
inputs[channelNumber][sampleNumber]
. Note that if you're using Faust to generate a DSP
function that will be used in a separate for
loop and you need it to compute things sample by sample, just
set code
to 1.
If we analyze the code, we can see that it does exactly what we want: it takes 2 inputs, adds them and copy the result to the output buffer.
One of the nice things about Faust is that it comes with as series of libraries implementing hundreds of
DSP algorithms that can be converted to C++ and used in your project. Let's create a new Faust file that we'll
call saw.dsp
. Copy and paste the following code in it implementing a simple sawtooth synthesizer:
import("stdfaust.lib");
frequency = nentry("freq",440,20,20000,0.01) : si.smoo;
level = nentry("gain",1,0,1,0.01) : si.smoo;
onoff = button("gate") : si.smoo;
process = hgroup("saw",os.sawtooth(frequency) * level * onoff);
stdfaust.lib
gives you access to all the Faust standard libraries from a single place by
providing a bunch of environment variables such as si
(signal.lib
) and
os
(oscillator.lib
). For more information on this, check the
Faust libraries documentation.
Faust allows users to declare UI elements which is not a feature that we'll use here. However, to make
a parameter "public" on the C++ side, a UI element has to be associated to it. For instance, we can see that
"numerical entries" (nentry
) are associated to the freq
and the gain
parameters and that a button controls gate
. In our case, it doesn't matter what kind of UI element
you use to control a parameter. In fact, freq
and gain
could well be controlled
by a button and the end result would be the same for us. The only thing that matters here is the name that
you give to the UI element (e.g freq
for the frequency parameter) because that's what we will use
on the C++ side to control the parameter it is associated to (more details are given about this below).
The :
(A : B
) operator is used to connect 2 Faust elements together (as long
as the number of outputs of A
is the same as number of inputs of B
). If
B
has more inputs than A
has outputs, they could be connected together using
the <:
operator that will split the output of A
and connect them to all
the inputs of B
(A <: B
). On the other hand, :>
does the opposite
and can be used to merge (add) signals together. If you want to learn more about the Faust syntax,
you can read the Faust documentation.
In the Faust code above, all the UI elements are connected to the smoo
function which is just
an exponential smoothing by a unity-dc-gain one-pole
lowpass. The rest of the code should be pretty obvious... We'll see later why we declared an
hgroup
here.
OK, now let's convert that to something useful usable in a JUCE project. Faust comes with a series
of C++ wrappers that can be used to better integrate a Faust generated DSP object. We wont go into
too many details here, so just download this C++ file (the Faust C++ wrapper)
and put it in the same folder as saw.dsp
. Now run the following command:
faust -a arch.cpp -i -cn Saw saw.dsp -o Saw.h
-a
applies the C++ wrapper to the generated C++ code, -i
inlines all the includes
of the C++ wrapper in Saw.h
and -cn Saw
changes the Faust class name to "Saw".
At this point, I recommend that you just look at the code of the
example project. In MainComponent.cpp
, 2 objects are created:
Saw saw;
MapUI sawControl;
MapUI
provides a high level interface to the Faust object based on its UI. Thus
sawControl
must be associated to saw
. This is done in
prepareToPlay
:
saw.buildUserInterface(&sawControl);
After that, the various parameters of saw
can be controlled using the
setParamValue
method:
sawControl.setParamValue("/saw/freq",1000);
Now you can see why it was important to declare UI elements in our Faust code and that
hgroup
allowed us to specify the root of the path of the different parameters. In case you're
not sure about the path of the parameters of the object that you created, you can add something like:
for(int i=0; i < sawControl.getParamsCount(); i++){
std::cout << sawControl.getParamAdress(i) << "\n";
}
to the C++ code of your project to print the path of all the parameters. The rest of the example code should speak by itself.
Faust allows you to generate a browsable block diagram corresponding to the code that you wrote. This
feature is very useful for debugging. To get the block diagram corresponding to saw.dsp
,
just run:
faust2svg saw.dsp
The result should look like this (click on the image to browse it):
At this point, we recommend you to explore the Faust libraries and try different functions: see what
they do and how you could integrate them to your projects. The Examples
folder of the Faust
distribution contains lots of examples of Faust codes. Don't forget to check the different resources
provided at the top of this page. Finally, we're here to help, so feel free to ask
if you have any question. Happy coding!