Starter Codes

Tutorial

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!