A typical Faust program is usually divided into 3 sections:
The only required element of a Faust program is its process
call. Hence,
process = 1;
is a valid Faust program always outputting 1 (DC offset).
The “hello world of computer music” (a sine wave oscillator) can be easily implemented in Faust by calling the sine wave oscillator function (os.osc
):
import("stdfaust.lib");
freq = 440;
process = os.osc(freq);
stdfaust.lib
gives us access here to os.osc
and freq
is a variable that we created to control the single parameter of os.osc
which is its frequency.
Try to run this program in the Faust Web IDE by copying it and pasting it in the editor window and by clicking on the “run” button. You should hear a sound coming out of the speakers of your computer.
Currently, we’re missing an interface for our Faust program so its parameter(s) cannot be controlled in real time. We can modify the definition of freq
so that its value can be changed by a slider:
freq = hslider("freq",440,50,2000,0.01);
Make this modification in the program in the web IDE and see what happens. hslider
stands for “Horizontal Slider,” freq
is the name of the slider in the generated interface, 440 its default value, 50 its minimum value, 2000 its maximum value, and 0.01 its precision (here every new step in the slider will increment or decrement the value of freq
by 0.01). Our code now contains all the elements of a standard Faust program (lib imports, interface, and mapping/DSP) and should look like this:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
process = os.osc(freq);
On “traditional” musical instruments, a musical note is configured with at least 3 parameters: its pitch, its gain/velocity and the fact that it’s on or off. Currently, our Faust program implements only one of these parameters which is the pitch (freq
). To control the status of the instrument (on or off) we can add a button and multiply its output by os.osc
:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gate = button("gate");
process = os.osc(freq)*gate;
Try it!
Since button
outputs 1 when it is pressed and 0 when it is not pressed, multiplying it by os.osc
will allow us to turn the synthesizer on and off.
Finally, a gain
slider can be easily added using the same approach:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
process = os.osc(freq)*gain*gate;
The timbre (the sound) produced by a sine wave is quite boring. A simple way to generate sounds with a richer timbre is to use other waveforms such as square, sawtooth or triangle waves which are respectively:
os.square(freq)
os.sawtooth(freq)
os.triangle(freq)
in Faust.
Try to replace os.osc
in the previous Faust program to see how these waveforms impact the timbre of the generated sound.
The envelope of a sound describes how its gain evolves in time. It has a huge impact on how we perceive the sound. The two main kinds of envelope generators are AR (Attack immediately followed by a Release) and ADSR (Attack Decay Sustain Release). In that last case, the durations of the attack, the decay and the release are constant while sustain will depend on the status of note (on or off). The previous program can be easily modified to integrate an envelope generator:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = en.ar(0.1,0.2,gate)*gain;
process = os.osc(freq)*envelope;
where 0.1 in en.ar
is the duration of the attack in seconds and 0.2 the duration of the release,
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.1,0.2,0.9,0.3,gate)*gain;
process = os.osc(freq)*envelope;
where 0.1 in en.adsr
is the duration of the attack in seconds, 0.2 the duration of the decay, 0.9 the gain of the sustain and 0.3 the duration of the release.
Try different values for en.ar
and en.adsr
to see how they impact the generated sound. AR envelope generators are typically used for percussive instruments like drums, etc. while ADSR is perfect for wind instruments with a sustained tone.
Faust synthesizers can be easily made polyphonic in the Web IDE by changing the number of “Poly Voices” on the left panel. Choosing 16 there will create 16 parallel polyphonic voices. In order for a synthesizer to be polyphonic, the freq
, gain
, and gate
parameters must be declared. They are automatically mapped by Faust to MIDI note on/off events (gate
), MIDI pitches (freq
), and velocity (gain
).
The Faust program written in the previous section is compatible with this system. Currently, the only Web browser supporting MIDI is Google Chrome. However you should be able to use your computer’s built-in keyboard to control a Faust program in Firefox (and Chrome). So if you actually want to use MIDI, you’ll have to use Chrome and a MIDI keyboard. If you don’t own one, you can use a virtual MIDI keyboard such as VMPK (Windows, Linux, MacOS) or MidiKeys (MacOS). Once again, using your computer’s built-in keyboard is an option as well, just make sure that you select the right source in the top right menu in the Faust Web IDE. If you choose this option, the “a” to “l” row are notes, the “q” to “p” row are sharps and flats, and the “z” and “x” key allow you to shift octaves up and down. Make sure to click outside of the editing area before pressing the keys to control the sound.
The DSP code written in the process
line will be duplicated for each voice of the synth. Audio effects applying to all polyphonic voices can be added by declaring the effect
line. Hence,
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.1,0.2,0.9,0.3,gate)*gain;
process = os.sawtooth(freq)*envelope <: _,_;
effect = dm.zita_light;
will apply a reverb (dm.zita_light
) on all poly voices of the synth by instantiating the reverb only once which will significantly save computation.
Note that <: _,_
was added to the process line to split the output of the sawtooth oscillator into 2 signals because zita_light
has a stereo input.
Warning: be careful not to leave the polyphonic mode on (reset it back to Mono) as it might prevent future Faust programs from running. You also probably noticed that activating polyphony disables the user interface elements associated to the freq
, gate
, and gain
parameters.
The previous synthesizer is pretty boring, partly because the timbre of the sound it generates is constant and doesn’t evolve in time. In the “real world” sound produced by musical instruments always have an evolving timbre. A simple way to solve this problem here is to use subtractive synthesis which consists in filtering a sound with a dense harmonic content. Highpass, lowpass and bandpass filters can be used for that. Faust comes with various types of such filters:
fi.lowpass(order,freq)
fi.highpass(order,freq)
fi.resonlp(freq,q,gain)
fi.resonbp(freq,q,gain)
fi.resonhp(freq,q,gain)
For more information on each of them, please refer to the Faust libraries documentation.
The previous Faust program can be quickly modified to implement a subtractive synthesizer using a resonant lowpass filter:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
ctfreq = hslider("ctfreq",1000,50,5000,0.01) : si.smoo;
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.1,0.2,0.9,0.3,gate)*gain;
process = os.sawtooth(freq)*envelope : fi.resonlp(ctfreq,5,1)*0.3 <: _,_;
effect = dm.zita_light;
In that case, the cutoff frequency of the filter is controlled by a user interface element. Its output value is smoothed using si.smoo
to prevent abrupt changes/clicks in the signal. Note that we multiplied the overall gain of the synth by 0.3 because resonlp
tends to increase the gain because of its resonance. In order to dynamically change the value of this parameter, it could be directly associated to the envelope generator using some kind of mapping:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.1,0.2,0.9,0.3,gate)*gain;
ctfreq = envelope*5000+100;
process = os.sawtooth(freq)*envelope : fi.resonlp(ctfreq,5,1)*0.3 <: _,_;
effect = dm.zita_light;
You can get very creative with subtractive synthesis by trying out different kind of filters and oscillators, combining them and mapping them in different ways.
A simple way to automatize the triggering of the synthesizer written in the previous section is to replace the value of gate
currently controlled by a button by a pulse generator (ba.pulse
in Faust). Its single parameter is the period in samples at which pulses are being generated. This value can be easily formatted from a BPM using the sampling rate:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
BPM = 60/hslider("BPM",60,1,1000,0.01)*ma.SR;
gate = checkbox("gate")*ba.pulse(BPM);
envelope = en.ar(0.1,0.2,gate)*gain;
ctfreq = envelope*5000+100;
process = os.sawtooth(freq)*envelope : fi.resonlp(ctfreq,5,1)*0.3 <: _,_;
Note that the button
was also replaced by a checkbox
which acts as a toggle here.
To conclude this tutorial, we’re going to create 3 instances of our synth in parallel and add them together to create a chord generator:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
gain = hslider("gain",1,0,1,0.01);
third = hslider("third",0,0,1,0.01);
BPM = 60/hslider("BPM",300,1,1000,0.01)*ma.SR;
gate = checkbox("gate")*ba.pulse(BPM);
envelope = en.ar(0.1,0.2,gate)*gain;
ctfreq = envelope*5000+100;
chord(0) = 1;
chord(1) = 1.2+third;
chord(2) = 1.5;
process = sum(i,3,os.sawtooth(freq*chord(i))*envelope : fi.resonlp(ctfreq,5,1))*0.3 <: _,_;
Here, sum
allows to duplicate and add 3 instances of our synth. i
is a variable containing the instance number (i.e., 0, 1, 2). chord
is a ratio that is used to change the value of the fundamental frequency in function of the instance number. Finally, third
allows to tune up the pitch of the “third” of the chord. Note that we multiplied
The Faust distribution comes with a series of libraries implementing a wide range of synthesizers, audio effects, etc. The source code of the Faust libraries can be found in the Faust Libraries repository on GitHub: https://github.com/grame-cncm/faustlibraries. Also, the documentation of the Faust libraries is available on the Faust libraries documentation website: https://faustlibraries.grame.fr/
Each .lib
file contains the source code of a specific library.
All standard Faust libraries are accessible through a series of prefixes declared in stdfaust.lib
. For example, to call the sine oscillator function (osc
) declared in oscillators.lib
, one might write:
import("oscillators.lib");
process = osc(440);
and alternatively:
import("stdfaust.lib");
process = os.osc(440);
Some functions of the Faust libraries host their own user interface. Most of these functions are declared in demos.lib
and physmodels.lib
. For example, a ready-to-use clarinet physical model can be found physmodels.lib
and called with just 2 lines of code:
import("stdfaust.lib");
process = pm.violin_ui;
Exploring the source code of physmodels.lib
, the declaration of the violin_ui
function can be easily found: https://github.com/grame-cncm/faustlibraries/blob/master/physmodels.lib#L1513
As an exercise, try to write your own version of violin_ui
(i.e., my_violin_ui
) with custom parameter names.
Vibrato is a crucial esthetic feature when performing with some musical instruments in western music. Moreover, in the case of sound synthesis it can help make the harmonic content of a sound more “coherent” (effect of source segregation).
Vibrato can be implemented in Faust simply by modulating the pitch of the generated sound with a sine wave oscillator (e.g., pitch + osc(vibratoFreq)*vibratoAmp
).
Try to modify the previous example by adding 2 user interface elements to control the frequency and the amplitude of the vibrato. The frequency shouldn’t exceed 10Hz and a good default value is 6Hz.
Hint: don’t forget that a sine function has both positive and negative values.
compressors.lib
, misceffects.lib
, phaflangers.lib
, reverbs.lib
, and vaeffects.lib
contain a wide range of audio effects. Some of them have a demo version (with a built-in UI) that can be found in demos.lib
. This table gives of an overview of the most standard audio effects available in the Faust libraries.
We’d like to “improve” our instrument from the previous step (violin with vibrato) with a phasor. A stereo phaser is available in demos.lib
: https://github.com/grame-cncm/faustlibraries/blob/master/demos.lib#L477. Since my_violin_ui
has one output and phaser2_demo
has 2 inputs (stereo), we must use the split operator:
process = my_violin_ui <: my_phaser2_demo;
Try to make this modification to the previous program to see what happens!
demos.lib
, physmodels.lib
, and of the Faust examples page. Feel free, to write test programs in the Faust Web IDE using some of the functions and examples that you’ll discover.