Faust Tutorials

This page gathers various tutorials around the Faust programming language and some of its associated tools. This is an ongoing project and it will soon be populated with more elements.

Should you have any question, feel free to send an e-mail to the Faust user mailing list.

Adding Faust DSP Support to Your JUCE Plug-ins

JUCE is a well-known framework for creating audio plug-ins (i.e., VST, AU, AAX, etc.) and applications for a wide range of platforms. Faust can be used to generate ready-to-use JUCE applications and plug-ins implementing the standard user interface (UI) described in the Faust code using faust2juce. However, it is sooo easy to make professional looking UIs from scratch in JUCE that you might want to use Faust to implement the DSP portion of your plug-in and build your own UI.

In this tutorial, we demonstrate how to use faust2api to generate a JUCE audio engine from Faust and how to interface it with your JUCE plug-in/UI. We’ll cover basic examples as well as more advanced applications involving polyphony, etc.

“The Old Way”: Brute-Force Method

If you’re not interested by this brief page of history, you can jump directly to the next section.

Before faust2api existed, it was already possible to use Faust to implement the DSP portion of a JUCE plug-in. Indeed, the most basic way to use Faust is to generate C++ code with the Faust compiler. Running:

faust someCode.dsp -o someCode.cpp

will output a C++ file (someCode.cpp) containing a class implementing a ready-to-use audio callback (see this tutorial). Hence, it was just a matter of using this class in your JUCE project, pass it the input buffers, retrieve the output buffers, and change potential parameters, etc. This short (and potentially outdated) tutorial from 2016 demonstrates how this can be done.

Fortunately, things are simpler than ever nowadays and faust2api greatly simplifies this process.

Simple Synth Plug-In

<< Download the source of this tutorial >>

In this section, we demonstrate how to use a Faust synth to build a plug-in in JUCE with a custom UI from scratch.

This tutorial only demonstrates how to make a JUCE plug-in. Making a JUCE standalone application following the same method is perfectly possible with some adjustments.

Generating the DSP Engine

First, let’s implement a basic subtractive synthesizer in Faust based on a filtered sawtooth wave (synth.dsp):

import("stdfaust.lib");
freq = nentry("freq",50,200,1000,0.01);
gain = nentry("gain",0.5,0,1,0.01) : si.smoo;
gate = button("gate") : si.smoo;
cutoff = nentry("cutoff",10000,50,10000,0.01) : si.smoo;
process = os.sawtooth(freq)*gain*gate : fi.lowpass(3,cutoff) <: _,_;

Feel free to run it in the web editor to see how it sounds!

The output of the lowpass is split into 2 signals to create a stereo object.

Note that all the parameters are smoothed to prevent clicking (we want our plug-in to be clean!). Even gate is smoothed, which will apply a gentle exponential envelope when the trigger signal is sent, etc.

Since Faust will not build its own UI here, the type of UI element used in this code doesn’t really matter. They just serve as a point of entry to control the parameters of the audio engine we’re about to generate. So nentry could be replaced by hslider or vslider, it would not make any difference. However, we encourage you to always write “coherent” interfaces in case someone would like to use your Faust code “as such” at some point.

This Faust program can be turned into an audio engine for JUCE simply by running the following command (assuming that Faust is properly installed on your system):

faust2api -juce synth.dsp

Alternatively (i.e., if you’re an unfortunate Windows user), you can use the Faust web editor to carry out the same task by choosing source/juce in the export function (don’t forget to change the name of your Faust program to synth.dsp in the drop area).

In both cases, you’ll end up with with a zip file containing a C++ file and its companion header file as well as some automatically generated markdown documentation.

Creating an Empty JUCE Plug-In Project

In this section, we’ll assume that you’re a bit familiar with JUCE. If that’s not your case, don’t panic and just read their Getting started with the Projucer tutorial. We also recommend you to have a look a the next few following tutorials to have a sense of how things work. They’re nice and easy to read!

In the Projucer, create a new Audio Plug-In, add the targets that you want, in the settings, make sure that “Plugin is a Synth” is enabled. In our case, we named it “SawtoothSynth” (choose this name wisely as it will impact the class names of your program).

Now, place DspFaust.cpp and DspFaust.h generated in the previous step in the Source folder of your JUCE plug-in project. Then select these 2 files in Source and drag them in the Projucer so that they become visible in the Source tab:

At this point, try to compile your plug-in and see if it runs. Remember that JUCE now generates a “standalone plug-in” by default which is super convenient to test things without having to open the plug-in in a third party application.

Let’s now add our DspFaust object to the PluginProcessor. In PluginProcessor.h, include DspFaust.h:

#include "../JuceLibraryCode/JuceHeader.h"
#include "DspFaust.h"

and implement DspFaust in the private section of the AudioProcessor class:

private:
    DspFaust dspFaust;
    
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SawtoothSynthAudioProcessor)

In PluginProcessor.cpp, all we have to do is to call dspFaust.start() when we want to start the Faust audio engine and dspFaust.stop() when we want to stop it. No need to set the sampling rate or the buffer length, dspFaust takes care of that for ya.

Remember that the package generated by faust2api contains a documentation of the generated API. Feel free to have a look at it to get a sense of what methods are available.

dspFaust.start() and dspFaust.stop() can be conveniently placed in the constructor and destructor (respectively) of SawtoothSynthAudioProcessor:

SawtoothSynthAudioProcessor::SawtoothSynthAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
{
  dspFaust.start();
}

SawtoothSynthAudioProcessor::~SawtoothSynthAudioProcessor()
{
  dspFaust.stop();
}

The default JUCE plug-in copies the input buffer in the output buffer. Therefore, make sure that the implementation of the processBlock method looks like:

void SawtoothSynthAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
}

(empty).

We now want to control the parameters of our dspFaust object. For that, the setParamValue method can be used. Since we want to control the parameters of our synth from the PluginEditor, we create a series of public methods in PluginProcessor to control each parameter of our synth. In PluginProcessor.h this will look like:

public:
  void setFreq(float freq);
  void setGain(float gain);
  void setCutoff(float cutoff);
  void setGate(bool gate);

and the corresponding implementation in PluginProcessor.cpp will be:

void SawtoothSynthAudioProcessor::setFreq(float freq)
{
  dspFaust.setParamValue("/synth/freq",freq);
}

void SawtoothSynthAudioProcessor::setGain(float gain)
{
  dspFaust.setParamValue("/synth/gain",gain);
}

void SawtoothSynthAudioProcessor::setGate(bool gate)
{
  if(gate){
    dspFaust.setParamValue("/synth/gate",1);
  }
  else{
    dspFaust.setParamValue("/synth/gate",0);
  }
}

void SawtoothSynthAudioProcessor::setCutoff(float cutoff)
{
  dspFaust.setParamValue("/synth/cutoff",cutoff);
}

Hence, setParamValue has 2 arguments: the path of the parameter in the Faust code, and its value. Note that a list of all the available parameters of the Faust object of their corresponding path can be found in the markdown documentation of the package generated with faust2api.

That’s it for the PluginProcessor! Easy isn’t it ;)? Now, let’s add a basic interface to control this synth.

We add a series of sliders, button, and labels to the private section of SawtoothSynthAudioProcessorEditor in PluginEditor.h:

private:
  Slider frequencySlider;
  Slider gainSlider;
  Slider cutoffSlider;
  ToggleButton onOffButton;
    
  Label frequencyLabel;
  Label gainLabel;
  Label cutoffLabel;
  Label onOffLabel;

and their corresponding implementation in PluginEditor.cpp:

SawtoothSynthAudioProcessorEditor::SawtoothSynthAudioProcessorEditor (SawtoothSynthAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
  setSize (800, 130);
  
  addAndMakeVisible (frequencySlider);
  frequencySlider.setRange (50.0, 5000.0);
  frequencySlider.setSkewFactorFromMidPoint (500.0);
  frequencySlider.setValue(300);
  frequencySlider.onValueChange = [this] {
    processor.setFreq(frequencySlider.getValue());  
  };

  addAndMakeVisible(frequencyLabel);
  frequencyLabel.setText ("Frequency", dontSendNotification);
  frequencyLabel.attachToComponent (&frequencySlider, true);
    
  addAndMakeVisible (gainSlider);
  gainSlider.setRange (0.0, 1.0);
  gainSlider.setValue(0.5);
  gainSlider.onValueChange = [this] { 
    processor.setGain(gainSlider.getValue()); 
  };
    
  addAndMakeVisible(gainLabel);
  gainLabel.setText ("Gain", dontSendNotification);
  gainLabel.attachToComponent (&gainSlider, true);
    
  addAndMakeVisible (cutoffSlider);
  cutoffSlider.setRange (50.0, 10000.0);
  cutoffSlider.setValue(5000.0);
  cutoffSlider.onValueChange = [this] { 
    processor.setCutoff(cutoffSlider.getValue()); 
  };
    
  addAndMakeVisible(cutoffLabel);
  cutoffLabel.setText ("Cutoff", dontSendNotification);
  cutoffLabel.attachToComponent (&cutoffSlider, true);
  
  addAndMakeVisible(onOffButton);
  onOffButton.onClick = [this] { 
    processor.setGate(onOffButton.getToggleState());
  };
    
  addAndMakeVisible(onOffLabel);
  onOffLabel.setText ("On/Off", dontSendNotification);
  onOffLabel.attachToComponent (&onOffButton, true);
}

The methods that we declared in the previous step are basically called to set the value of the parameters of our DSP engine thanks to the processor object.

The resized method must be implemented so that the various UI elements that we created actually have a size:

void SawtoothSynthAudioProcessorEditor::resized()
{
  const int sliderLeft = 80;
  frequencySlider.setBounds (sliderLeft, 10, getWidth() - sliderLeft - 20, 20);
  gainSlider.setBounds (sliderLeft, 40, getWidth() - sliderLeft - 20, 20);
  cutoffSlider.setBounds (sliderLeft, 70, getWidth() - sliderLeft - 20, 20);
  onOffButton.setBounds (sliderLeft, 100, getWidth() - sliderLeft - 20, 20);
}

Finally, make sure that you clean the implementation of the paint method to get rid of the default ugly “Hello World”:

void SawtoothSynthAudioProcessorEditor::paint (Graphics& g)
{
  g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}

Note that the same approach can be used to create an audio effect: DspFaust will take care of instantiating the input buffers, etc. so it should just work right away as for this synth example.

Compile your plug-in and run it, it should look like this:

The goal of this section was just to show you how to integrate a Faust DSP engine into a JUCE plug-in project and how to control it with a simple UI. Once again, JUCE is a powerful tool to implement sophisticated UI in a very simple way. You’ll find all the documentation you need on their website to start making beautiful plug-ins!

Creating and Using a Polyphonic Faust DSP Object

<< Download the source of this tutorial >>

In the second part of this tutorial, we demonstrate how to generate a polyphonic DSP engine with faust2api and how to control it with the “standard” JUCE keyboard.

The following code is almost the same as the one used in the previous section, except that we declared a global effect using the effect standard variable (filteredSawtoothPoly.dsp):

import("stdfaust.lib");
freq = nentry("freq",50,200,1000,0.01) : si.smoo;
gain = nentry("gain",0.5,0,1,0.01) : si.smoo;
gate = button("gate") : si.smoo;
cutoff = nentry("cutoff",10000,50,10000,0.01) : si.smoo;
process = os.sawtooth(freq)*gain*gate : fi.lowpass(3,cutoff) <: _,_ ;
effect = dm.zita_light;

Note that the freq, gain, and gate parameters are declared, which means that this Faust program can be turned into a polyphonic synth (see the MIDI polyphonic documentation). Hence, in the current configuration multiple instances (voices) of process will be created and connected to a single instance of effect.

A polyphonic DSP engine for JUCE can be generated from this code by running:

faust2api -juce -nvoices 12 filteredSawtoothPoly.dsp

where 12 is the maximum number of polyphonic voices (which can be changed from the generated C++ code as well by changing the value of the NVOICES macro at the beginning of DspFaust.cpp). The same result can be achieved using the Faust web editor and by choosing source/juce-poly in the export function.

Create a new JUCE audio plug-in project with the same configuration than in the previous section. Import the DspFaust files, create an instance of the DspFaust object and call the dspFaust.start() and dspFaust.stop() methods as we did before.

Our goal is to create a simple plug-in with the following interface:

where the keyboard can be used to play several notes at the same time and the “cutoff” slider can be used to change the cutoff frequency of the lowpass filter of all active voices. This is an extremely primitive implementation where only the messages from the UI keyboard are processed: we’re just doing this for the sake of the example. If you’ve never worked with keyboards and MIDI in JUCE, we strongly recommend you to read this tutorial.

In PluginEditor.h, let’s first add the following inheritance to the SawtoothSynthAudioProcessorEditor class:

class SawtoothSynthAudioProcessorEditor  : 
  public AudioProcessorEditor, 
  private MidiInputCallback, 
  private MidiKeyboardStateListener
{

This is necessary to implement the MIDI callback and the keyboard (UI) listener. This inheritance requires us to implement the following methods in the private section of PluginEditor.h. We also add an instance of a UI keyboard and its associated state as well as a slider and its label to control the cutoff frequency of the lowpass:

private:
  void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
  void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float /*velocity*/) override;
  void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override;
  
  MidiKeyboardState keyboardState;   
  MidiKeyboardComponent keyboardComponent; 
    
  Slider cutoffSlider;
  Label cutoffLabel;

In PluginEditor.cpp, we can add the keyboard and the slider to the constructor:

SawtoothSynthAudioProcessorEditor::SawtoothSynthAudioProcessorEditor (SawtoothSynthAudioProcessor& p)
  : AudioProcessorEditor (&p), processor (p), keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard)
{
  setSize (800, 150);
    
  addAndMakeVisible (keyboardComponent);
  keyboardState.addListener (this);
    
  addAndMakeVisible (cutoffSlider);
  cutoffSlider.setRange (50.0, 10000.0);
  cutoffSlider.setValue(5000.0);
  cutoffSlider.onValueChange = [this] { 
    processor.setCutoff(cutoffSlider.getValue()); 
  };
    
  addAndMakeVisible(cutoffLabel);
  cutoffLabel.setText ("Cutoff", dontSendNotification);
  cutoffLabel.attachToComponent (&cutoffSlider, true);
}

and we must de-allocate the keyboard state listener in the destructor:

SawtoothSynthAudioProcessorEditor::~SawtoothSynthAudioProcessorEditor()
{
  keyboardState.removeListener(this);
}

The implementation of the setCutoff method is detailed later in this tutorial and is very similar to the one described in the previous section.

We also need to define the size of the various elements in the interface (as we did before):

void SawtoothSynthAudioProcessorEditor::resized()
{
  const int sliderLeft = 80;
  keyboardComponent.setBounds (10,10,getWidth()-30,100);
  cutoffSlider.setBounds (sliderLeft, 120, getWidth() - sliderLeft - 20, 20);
}

MIDI messages are retrieved from the keyboard simply by implementing the following inherited methods:

void SawtoothSynthAudioProcessorEditor::handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) {}

void SawtoothSynthAudioProcessorEditor::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
  processor.keyOn(midiNoteNumber,int(127*velocity));
}

void SawtoothSynthAudioProcessorEditor::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float /*velocity*/)
{
  processor.keyOff(midiNoteNumber);
}

The implementation of the keyOn and keyOff methods is detailed below.

On the PluginProcessor side, the following methods must be declared in PluginProcessor.h:

void keyOn(int pitch, int  velocity);
void keyOff(int pitch);
void setCutoff(float cutoff);

They are the ones that were used in the previous steps.

Their corresponding implementation in PluginProcessor.cpp is very straight forward:

void SawtoothSynthAudioProcessor::keyOn(int pitch, int velocity)
{
  dspFaust.keyOn(pitch,velocity);
}

void SawtoothSynthAudioProcessor::keyOff(int pitch)
{
  dspFaust.keyOff(pitch);
}

void SawtoothSynthAudioProcessor::setCutoff(float cutoff)
{
  dspFaust.setParamValue("/Sequencer/DSP1/Polyphonic/Voices/synth/cutoff",cutoff);
}

keyOn and keyOff are methods from DspFaust that can be used to trigger and stop a note. keyOn will allocate a new voice, convert its pitch parameter into a frequency that will be sent automatically to the Faust freq parameter, velocity is converted to a level that will be sent to the gain parameter, and the gate parameter is set to 1. Inversely, keyOff sets gate to 0 and waits for t60 to be reached to de-allocate the current voice.

keyOn returns a voice ID whose type is unsigned long. This ID can then be used to change the parameter of a specific voice. We’re not using this functionality in the example presented in this tutorial but here is how this would work:

unsigned long voiceID = dspFaust.keyOn(60,110);
dspFaust.setVoiceParamValue("/synth/cutoff",voiceID,378);

Note that voices can also be allocated directly without using keyOn and keyOff with the newVoice and the deleteVoice methods:

unsigned long voiceID = dspFaust.newVoice();
dspFaust.setVoiceParamValue("/synth/gate",voiceID,1);
// do something...
dspFaust.deleteVoice(voiceID);

Using setParamValue as we’re doing in the current example, we can set the value of a parameter for all the voices of the DSP engine. Unlike setVoiceParamValue, the parameter path must be the complete path provided in the README of the DSP package generated with faust2api. So once again, for setVoiceParamValue, the short path is enough but for setParamValue, the complete path is needed.

You might wonder why the path is much more complex with a polyphonic DSP engine than with a regular one. Sequencer is the full object (poly synth + effect), DSP1 is the synth (DSP2 is the effect), Polyphonic is the polyphonic layer of the object, and finally Voices addresses all the voices at once.

That’s it folks! Try to compile and run your plug-in, it should just work. Of course, things could be significantly improved here but at this point, you should be able to sail on your own.

2018 Programmable Audio Workshop PAW: Introduction to Faust

Building a Simple MIDI Synthesizer

We are going to build a MIDI synthesizer from scratch (without using the Faust libraries).

Phase Generator

The first step is to build a phase generator that produces a periodic sawtooth signal between 0 and 1. Here is the signal we want to generate:

Ramp

In order to produce the above signal we need first to produce a ramp signal

using the following Faust program:

process = 0.125 : + ~ _;

You can think of a Faust program as a description of an audio circuit where 0.125, + and _ are primitives of the language (predefined elementary audio components), and the other two signs: : and ~ are used to connect together these audio components.

Semantics

To understand the above diagram let’s annotate it with its mathematical semantics.

As we can see in the diagram, the formula of the output signal is: \(y(t) = y(t-1) + 0.125\)

We can compute the first values of \(y(t)\):

  • \(y(t<0)=0\)
  • \(y(0) = y(-1) + 0.125 = 0.125\)
  • \(y(1) = y(0) + 0.125 = 2*0.125 = 0.250\)
  • \(y(2) = y(1) + 0.125 = 3*0.125 = 0.375\)
  • \(y(6) = y(5) + 0.125 = 7*0.125 = 0.875\)
  • \(y(7) = y(6) + 0.125 = 8*0.125 = 1.000\)
  • \(y(8) = y(7) + 0.125 = 9*0.125 = 1.125\)

Phase Signal

How can we transform the above ramp into sawtooth signal ? By removing the integer part of the samples in order to keep only the decimal (fractional) part (3.14159 -> 0.14159).

Let’s define a function to do that:

decimalpart(x) = x - int(x);

We can now use that function to transform our ramp into a sawtooth It is then tempting to write:

process = 0.125 : + ~ _ : decimalpart;

From a mathematical point of view that would be perfectly correct, but we will accumulate rounding errors. To keep full precision it is better to place the decimal part operation inside the loop:

process = 0.125 : (+ : decimalpart) ~ _ ;

We can now listen the produced signal. Just copy and past decimalpart and process definitions into the Faust editor

Controlling the Frequency of the Phase Signal

Let’s first rearrange our code

decimalpart(x) = x-int(x);
phase = 0.125 : (+ : decimalpart) ~ _ ;
process = phase;

In our phase definition the step value, here 0.125 controls the frequency of the generated signal. We would like to compute this step value according to the desired frequency. In order to do the conversion we need to know the sampling rate. It is available from the standard library as ma.SR.

Let say we would like our phase signal to have a frequency of 1 Hz, then the step should be very small 1/ma.SR so that is will take ma.SR samples (i.e. 1 second) for the phase signal to reach 1.

If we want a frequency of 440 Hz, we need a step 440 times bigger for the phase signal to reach 1 440 times faster.

phase = 440/ma.SR : (+ : decimalpart) ~ _ ;

We can generalize this definition by replacing 440 by a parameter f:

phase(f) = f/ma.SR : (+ : decimalpart) ~ _ ;

and by indicating the desired frequency when we use phase:

process = phase(440);

Creating a Sine Wave Oscillator

The next step is to transform above phase generator into a sine wave generator. We will use the sin primitive that computes the sine of x (measured in radians). Therefore we start for the phase signal, we multiply it by \(2\pi\) to obtain radiants, and compute the sine. The full program is the following:

import("stdfaust.lib");
decimalpart(x) = x-int(x);
phase(f) = f/ma.SR : (+ : decimalpart) ~ _ ;
osc(f) = phase(f) * 2 * ma.PI : sin;
process = osc(440);

Controlling the Frequency and Gain of the Oscillator

The next step is to add some controls on the frequency and gain of the oscillator. We can replace the fixed frequency 440 by a user interface slider:

process = osc(hslider("freq", 440, 20, 10000, 1));

and add a gain to control the output level of the oscillator:

process = osc(hslider("freq", 440, 20, 10000, 1)) * hslider("gain", 0.5, 0, 1, 0.01);

Adding a Gate Button

In order to prepare our MIDI synthesizer we need to add a gate button so that the sound is only when we press it:

process = osc(hslider("freq", 440, 20, 10000, 1)) * hslider("gain", 0.5, 0, 1, 0.01) * button("gate");

Adding an Envelope Generator

It ia a good idea to add also an envelop generator. Here we will use a predefined adsr in the Standard Faust library.

import("stdfaust.lib");
decimalpart(x) = x-int(x);
phase(f) = f/ma.SR : (+ : decimalpart) ~ _ ;
osc(f) = phase(f) * 2 * ma.PI : sin;
process = osc(hslider("freq", 440, 20, 10000, 1)) 
        * hslider("gain", 0.5, 0, 1, 0.01) 
        * (button("gate") : en.adsr(0.1,0.1,0.98,0.1));

Improving the Timbre

Instead of playing pure sine waves tones, let’s improve the timbre with simple additive synthesis:

timbre(f) = osc(f)*0.5 + osc(f*2)*0.25 + osc(f*3)*0.125;
process = timbre(hslider("freq", 440, 20, 10000, 1)) 
        * hslider("gain", 0.5, 0, 1, 0.01) 
        * (button("gate") : en.adsr(0.1,0.1,0.98,0.1));

Running as a Polyphonic MIDI Synth

To control the synthesizer using MIDI you need to use Chrome. The polyphonic MIDI mode is activated using the drop down menu.

Adding a Global Effect

A global effect can be added by providing a definition for effect.

// Common effect
effect = dm.zita_light;

Making a Sine Oscillator From Scratch and Additive Synthesis

Goals

  • Implementing a sine oscillator from scratch in Faust
  • Understand the relation between the sine function and the generated sound
  • Use multiple sine oscillator to implement an additive synthesizer
  • Use SmartKeyboard to produce polyphonic mobile apps to control this synth

Sine Function in Faust

  • The sine function in Faust works like on a calculator:
process = sin(0);

will output 0.

To verify this, you could click on the truck (export function) in the Faust web editor and then choose misc/csv to get a table containing the first n samples output by the program.

import("stdfaust.lib");
process = sin(ma.PI);

will output 1.

Note that stdfaust.lib is imported here in order to use ma.pi.

import("stdfaust.lib");
process = sin(2*ma.PI);

will output 0.

Implementing a Phasor

  • What is needed to “print” a full sine wave?
    • -> We need to create a series of numbers (vector) going from 0 to 2pi, in other words, draw a line.
  • First let’s create a “counter” in Faust:
process = +(1)~_;

Don’t forget that you can always print the output of a Faust program by using the in the Faust web editor misc/csv

  • The current counter counts one by one. Instead we’d like to count slower 0.01 by 0.01.
process = +(0.01)~_;
  • Now, we want to reset the counter back to 0 when it reaches 1. This can be done easily using the ma.decimal function:
import("stdfaust.lib");
process = +(0.01) ~ ma.decimal;

Note the use of ma.decimal in the loop here to prevent numerical errors.

  • Try to run the program (play button in the editor) and it should make sound! What are we generating here?
  • How do we change the pitch of the sawtooth wave? -
    • We should increment the counter faster or slower. Try different values (e.g., 0.001, 0.1, etc.).

  • Instead of controlling the increment of the counter, we’d like to control the frequency of the sawtooth wave.
  • To do that, we need to know the number of values of the wave processed by the computer in one second. That’s what we call the sampling rate. This value changes in function of the context of the program so it can be retrieved with ma.SR.
  • A sampling rate of 44100 corresponds to a frequency of 44100Hz. If we want a frequency of 440, what increment do we need to put in our counter?
    • -> freq/ma.SR
  • In the end, we get:
import("stdfaust.lib");
freq = 440;
process = (+(freq/ma.SR) ~ ma.decimal);
  • A this point feel free to plot the output of the Faust program using misc/csv in the export function of the online editor.
  • The freq parameter can be controlled dynamically:
import("stdfaust.lib");
freq = hslider("freq",440,50,2000,0.01);
process = (+(freq/ma.SR) ~ ma.decimal);
  • The code can be cleaned up by placing our phasor in a function:
import("stdfaust.lib");
f = hslider("freq",440,50,2000,0.01);
phasor(freq) = (+(freq/ma.SR) ~ ma.decimal);
process = phasor(f);

Generating a Sine Wave

  • Almost there! Now we want our phasor to go from 0 to 2pi so that we can plug it to the sin function:
import("stdfaust.lib");
f = hslider("freq",440,50,2000,0.01);
phasor(freq) = (+(freq/ma.SR) ~ ma.decimal);
osc(freq) = sin(phasor(freq)*2*ma.PI);
process = osc(f);

Note that we created an osc function in order to have a cleaner code.

Additive Synthesis

  • A sine wave generates what we call a pure tone. More complex sounds can be produced by adding multiple sine waves together to create harmonics. The frequency and the gain of each harmonic will determine the timbre of the sound. Using this technique, it is possible to “sculpt” a sound.
  • A simple organ synthesizer can be implemented using additive synthesis:
import("stdfaust.lib");
f = hslider("freq",440,50,2000,0.01);
phasor(freq) = (+(freq/ma.SR) ~ ma.decimal);
osc(freq) = sin(phasor(freq)*2*ma.PI);
organ(freq) = (osc(freq) + osc(freq*2) + osc(freq*3))/3;
process = organ(f)/3;

Making a Synthesizer

  • In order to use this synthesizer with a keyboard, we need to be able to turn the sound on and off and also to control its volume:
import("stdfaust.lib");
f = hslider("freq",440,50,2000,0.01);
g = hslider("gain",1,0,1,0.01);
t = button("gate");
phasor(freq) = (+(freq/ma.SR) ~ ma.decimal);
osc(freq) = sin(phasor(freq)*2*ma.PI);
organ(freq) = (osc(freq) + osc(freq*2) + osc(freq*3))/3;
process = organ(f)*g*t/3;
  • An envelope could be added to make it sound more natural:
import("stdfaust.lib");
f = hslider("freq",440,50,2000,0.01);
g = hslider("gain",1,0,1,0.01);
t = si.smoo(button("gate"));
phasor(freq) = (+(freq/ma.SR) ~ ma.decimal);
osc(freq) = sin(phasor(freq)*2*ma.PI);
organ(freq) = (osc(freq) + osc(freq*2) + osc(freq*3))/3;
process = organ(f)*g*t/3;
  • This synth can be controlled with a midi keyboard.

Tun it Into an Android App

  • Use the export function of the Faust editor and choose android/smartkeyb install the app on the phone and have fun!
  • This could also be turned into an app always making sound and controllable with accelerometers:
import("stdfaust.lib");
f = hslider("freq[acc: 0 0 -10 0 10]",1000,50,2000,0.01) : si.smoo;
phasor(freq) = (+(freq/ma.SR) ~ ma.decimal);
osc(freq) = sin(phasor(freq)*2*ma.PI);
organ(freq) = (osc(freq) + osc(freq*2) + osc(freq*3))/3;
process = organ(f)/3;
  • In that case, export with android/android.