Introduction

WARNING: This new manual for the Faust language is a work in progress! It might have missing parts, typos, “TODOs”, etc.

What is Faust?

Faust (Functional Audio Stream) is a functional programming language for sound synthesis and audio processing with a strong focus on the design of synthesizers, musical instruments, audio effects, etc. Faust targets high-performance signal processing applications and audio plug-ins for a variety of platforms and standards. It is used on stage for concerts and artistic productions, in education and research, in open source projects as well as in commercial applications.

The core component of Faust is its compiler. It allows to “translate” any Faust digital signal processing (DSP) specification to a wide range of non-domain specific languages such as C++, C, JAVA, JavaScript, LLVM bit code, WebAssembly, etc. In this regard, Faust can be seen as an alternative to C++ but is much simpler and intuitive to learn.

Thanks to a wrapping system called “architectures,” codes generated by Faust can be easily compiled into a wide variety of objects ranging from audio plug-ins to standalone applications or smartphone and web apps, etc. (check the Quick Tour of the Faust Targets section for an exhaustive list.

This manual gives an overview of the Faust programming language and of its features through various interactive examples.

What is Faust Good For?

Faust’s syntax allows to express any DSP algorithm as a block diagram. For example, + is considered as a valid function (and block) taking two arguments (signals) and returning one:

process = +;

Blocks can be easily connected together using the : “connection” composition:

process = + : *(0.5);

In that case, we add two signals together and then scale the result of this operation.

Thus, Faust is perfect to implement time-domain algorithms that can be easily represented as block diagrams such as filters, waveguide physical models, virtual analog elements, etc.

Faust is very concise, for example, here’s the implementation of a one pole filter/integrator equivalent to \(y(n) = x(n) + a_{1}y(n-1)\) (where \(a_{1}\) is the pole):

a1 = 0.9;
process = +~*(a1);

Codes generated by Faust are extremely optimized and usually more efficient that handwritten codes (at least for C and C++). The Faust compiler tries to optimize each element of an algorithm. For example, you shouldn’t have to worry about using divides instead of multiplies as they get automatically replaced by multiplies by the compiler when possible, etc.

Faust is very generic and allows to write code that will run on dozens of platforms.

What is Faust Not (So) Good For?

Despite all this, Faust does have some limitations. For instance, it doesn’t allow for the efficient implementation of algorithms requiring multi-rates such as the FFT, convolution, etc. While there are tricks to go around this issue, we’re fully aware that it is a big one and we’re working as hard as possible on it.

Faust’s conciseness can sometimes become a problem too, especially for complex algorithms with lots of recursive signals. It is usually crucial in Faust to have the “mental global picture” of the algorithm to be implemented which in some cases can be hard.

While the Faust compiler is relatively bug-free, it does have some limitations and might get stuck in some extreme cases that you will probably never encounter. If you do, shoot us an e-mail!

From here, you can jump to … if you wanna get your hands dirty, etc. TODO.

Design Principles

Since the beginning of its development in 2002, Faust has been guided by various design principles:

  • Faust is a specification language. It aims at providing an adequate notation to describe signal processors from a mathematical point of view. Faust is, as much as possible, free from implementation details.
  • Faust programs are fully compiled (i.e., not interpreted). The compiler translates Faust programs into equivalent programs in other languages (e.g., JAVA, JavaScript, LLVM bit code, WebAssembly, etc.) taking care of generating the most efficient code. The result can generally compete with, and sometimes even outperform, C++ code written by seasoned programmers.
  • The generated code works at the sample level. It is therefore suited to implement low-level DSP functions like recursive filters. Moreover the code can be easily embedded. It is self-contained and doesn’t depend of any DSP library or runtime system. It has a very deterministic behavior and a constant memory footprint.
  • The semantic of Faust is simple and well defined. This is not just of academic interest. It allows the Faust compiler to be semantically driven. Instead of compiling a program literally, it compiles the mathematical function it denotes. This feature is useful for example to promote components reuse while preserving optimal performance.
  • Faust is a textual language but nevertheless block-diagram oriented. It actually combines two approaches: functional programming and algebraic block-diagrams. The key idea is to view block-diagram construction as function composition. For that purpose, Faust relies on a block-diagram algebra of five composition operations: : , ~ <: :> (see the section on Diagram Composition Operations for more details).
  • Thanks to the concept of architecture, Faust programs can be easily deployed on a large variety of audio platforms and plug-in formats without any change to the Faust code.

Signal Processor Semantic

A Faust program describes a signal processor. The role of a signal processor is to transforms a (possibly empty) group of input signals in order to produce a (possibly empty) group of output signals. Most audio equipments can be modeled as signal processors. They have audio inputs, audio outputs as well as control signals interfaced with sliders, knobs, vu-meters, etc.

More precisely :

  • A signal \(s\) is a discrete function of time \(s:\mathbb{Z}\rightarrow\mathbb{R}\). The value of a signal \(s\) at time \(t\) is written \(s(t)\). The values of signals are usually needed starting from time \(0\). But to take into account delay operations, negative times are possible and are always mapped to zeros. Therefore for any Faust signal \(s\) we have \(\forall t<0, s(t)=0\). In operational terms this corresponds to assuming that all delay lines are signals initialized with \(0\)s.
  • Faust considers two type of signals: integer signals (\(s:\mathbb{Z}\rightarrow\mathbb{Z}\)) and floating point signals (\(s:\mathbb{Z}\rightarrow\mathbb{Q}\)). Exchanges with the outside world are, by convention, made using floating point signals. The full range is represented by sample values between \(-1.0\) and \(+1.0\).
  • The set of all possible signals is \(\mathbb{S}=\mathbb{Z}\rightarrow\mathbb{R}\).
  • A group of \(n\) signals (a n-tuple of signals) is written \((s_{1},\ldots,s_{n})\in \mathbb{S}^{n}\). The empty tuple, single element of \(\mathbb{S}^{0}\) is notated \(()\).
  • A signal processors \(p\), is a function from n-tuples of signals to m-tuples of signals \(p:\mathbb{S}^{n}\rightarrow\mathbb{S}^{m}\). The set \(\mathbb{P}=\bigcup_{n,m}\mathbb{S}^{n}\rightarrow\mathbb{S}^{m}\) is the set of all possible signal processors.

As an example, let’s express the semantic of the Faust primitive +. Like any Faust expression, it is a signal processor. Its signature is \(\mathbb{S}^{2}\rightarrow\mathbb{S}\). It takes two input signals \(X_0\) and \(X_1\) and produces an output signal \(Y\) such that \(Y(t) = X_0(t)+X_1(t)\).

Numbers are signal processors too. For example the number \(3\) has signature \(\mathbb{S}^{0}\rightarrow\mathbb{S}\). It takes no input signals and produce an output signal \(Y\) such that \(Y(t) = 3\).

Quick Start

The goal of this section is to teach you how to use the basic elements of the Faust programming language in approximately two hours! While DSP algorithms can be easily written from scratch in Faust, we’ll just show you here how to use existing elements implemented in the Faust libraries, connect them to each other, and implement basic user interfaces (UI) to control them.

One of the strength of Faust lies in its libraries that implement hundreds of functions. So you should be able to go a long way after reading this section, simply by using what’s already out here.

This tutorial was written assuming that the reader is already familiar with basic concepts of computer music and programming.

More generally, at the end of this section:

  • your Faust development environment should be up and running,
  • you should know enough to write basic Faust programs,
  • you should be able to use them on different platforms.

This tutorial was designed to be carried out in the Faust online editor. If you wish to do it locally, you’ll have to install Faust on your system but this step is absolutely not required,

Making Sound

Write the following code in the Faust online editor:

import("stdfaust.lib");
process = no.noise;

and then click on the “run” button on the top left corner. Alternatively, you can also click on the “Try it Yourself” button of the window above if you’re reading the online version of this documentation. You should now hear white noise, of course… ;)

stdfaust.lib gives access to all the Faust libraries from a single point through a series of environments. For instance, we’re using here the no environment which stands for noise.lib and the noise function (which is the standard white noise generator of Faust). The Faust libraries documentation provides more details about this system.

The most fundamental element of any Faust code is the process line, which gives access to the audio inputs and outputs of the target. This system is completely dynamic and since no.noise has only one output and no input, the corresponding program will have a single output.

Let’s statically change the gain of the output of no.noise simply by multiplying it by a number between 0 and 1:

process = no.noise*0.5;

Thus, standard mathematical operations can be used in Faust just like in any other language.

We’ll now connect the noise generator to a resonant lowpass filter (fi.resonlp) by using the Faust sequential composition operator: :

import("stdfaust.lib");
ctFreq = 500;
q = 5;
gain = 1;
process = no.noise : fi.resonlp(ctFreq,q,gain);

fi.resonlp has four arguments (in order): cut-off frequency, q, gain and its input. Here, we’re setting the first three arguments with fixed variables. Variables don’t have a type in Faust and everything is considered as a signal. The Faust compiler takes care of making the right optimizations by choosing which variable is ran at audio rate, what their types are, etc. Thus, ctFreq, q and gain could well be controlled by oscillators (i.e., signals running at audio rate) here.

Since the input of the filter is not specified as an argument here (but it could, of course), it automatically becomes an “implicit” input/argument of fi.resonlp. The : sequential composition operator can be used to connect two elements that have the same number of outputs and inputs. Since no.noise has one output and fi.resonlp(ctFreq,q,gain) has one implicit input, we can connect them together. This is essentially the same as writing something like:

import("stdfaust.lib");
ctFreq = 500;
q = 5;
gain = 1;
process = fi.resonlp(ctFreq,q,gain,no.noise);

While this would work, it’s kind of ugly and not very “Faustian”, so we don’t do it… ;)

At this point, you should be able to use and plug various elements of the Faust libraries together. The Faust libraries implement hundreds of functions and some of them have a very specialized use. Fortunately, the Faust libraries documentation contains a section on Standard Faust Libraries listing all the high level “standard” Faust functions organized by types. We recommend you to have a look at it now. As you do this, be aware that implicit signals in Faust can be explicitly represented with the _ character. Thus, when you see something like this in the libraries documentation:

_ : aFunction(a,b) : _

it means that this function has one implicit input, one implicit output and two parameters (a and b). On the other hand:

anotherFunction(a,b,c) : _,_

is a function that has three parameters, no implicit input and two outputs.

Just for “fun,” try to rewrite the previous example running in the Faust online editor so that the process line looks like this:

import("stdfaust.lib");
ctFreq = 500;
q = 5;
gain = 1;
process = no.noise : _ : fi.resonlp(ctFreq,q,gain) : _;

Of course, this should not affect the result.

You probably noticed that we used the , Faust composition operator to express two signals in parallel. We can easily turn our filtered noise example into a stereo object using it:

import("stdfaust.lib");
ctFreq = 500;
q = 5;
gain = 1;
process = no.noise : _ <: fi.resonlp(ctFreq,q,gain),fi.resonlp(ctFreq,q,gain);

or we could even write this in a cleaner way:

import("stdfaust.lib");
ctFreq = 500;
q = 5;
gain = 1;
filter = fi.resonlp(ctFreq,q,gain);
process = no.noise <: filter,filter;

Note that this example allows us to have 2 separate filters for each channel. Since both filters currently have the same parameters, another way of writing this could be: process = no.noise : filter <: _,_;.

Since filter,filter is considered here as a full expression, we cannot use the : operator to connect no.noise to the two filters in parallel because filter,filter has two inputs (_,_ : filter,filter : _,_) and no.noise only has one output.

The <: split composition operator used here takes n signals and splits them into m signals. The only rule is that m has to be a multiple of n.

The merge :> composition operator can be used exactly the same way:

import("stdfaust.lib");
process = no.noise <: filter,filter :> _;

Here we split the signal of no.noise into two signals that are connected to two filters in parallel. Finally, we merge the outputs of the filters into one signal. Note, that the previous expression could have been written as such too:

import("stdfaust.lib");
process = no.noise <: filter+filter;

Keep in mind that splitting a signal doesn’t mean that its energy get spread in each copy, for example, in the expression:

process = 1 <: _,_;

the two _ both contain 1…

All right, it’s now time to add a basic user interface to our Faust program to make things a bit more interactive.

Building a Simple User Interface

In this section, we’ll add a simple user interface to the code that we wrote in the previous section:

import("stdfaust.lib");
ctFreq = 500;
q = 5;
gain = 1;
process = no.noise : fi.resonlp(ctFreq,q,gain) ;

Faust allows us to declare basic user interface (UI) elements to control the parameters of a Faust object. Since Faust can be used to make a wide range of elements ranging from standalone applications to audio plug-ins or API, the role of UI declarations differs a little in function of the target. For example, in the Faust Online Editor, a UI is a window with various kind of controllers (sliders, buttons, etc.). On the other hand, if you’re using Faust to generate an audio engine using faust2api, then UI elements declared in your Faust code will be the parameters visible to “the rest of the world” and controllable through the API.

An exhaustive list of the standard Faust UI elements is given in the corresponding section. Be aware that they not all supported by all the Faust targets. For example, you wont be able to declare vertical sliders if you’re using the Faust Playground, etc.

In the current case, we’d like to control the ctFreq, q and gain parameters of the previous program with horizontal sliders. To do this, we can write something like:

import("stdfaust.lib");
ctFreq = hslider("cutoffFrequency",500,50,10000,0.01);
q = hslider("q",5,1,30,0.1);
gain = hslider("gain",1,0,1,0.01);
process = no.noise : fi.resonlp(ctFreq,q,gain);

The first argument of hslider is the name of the parameter as it will be displayed in the interface or used in the API (it can be different from the name of the variable associated with the UI element), the next one is the default value, then the min and max values and finally the step. To summarize: hslider("paramName",default,min,max,step).

Let’s now add a “gate” button to start and stop the sound (where gate is just the name of the button):

import("stdfaust.lib");
ctFreq = hslider("[0]cutoffFrequency",500,50,10000,0.01);
q = hslider("[1]q",5,1,30,0.1);
gain = hslider("[2]gain",1,0,1,0.01);
t = button("[3]gate");
process = no.noise : fi.resonlp(ctFreq,q,gain)*t;

Note that we were able to order parameters in the interface by numbering them in the parameter name field using squared brackets.

Faust user interface elements run at control rate. Thus, you might have noticed that clicks are produced when moving sliders quickly. This problem can be easily solved by “smoothing” down the output of the sliders using the si.smoo function:

import("stdfaust.lib");
ctFreq = hslider("[0]cutoffFrequency",500,50,10000,0.01) : si.smoo;
q = hslider("[1]q",5,1,30,0.1) : si.smoo;
gain = hslider("[2]gain",1,0,1,0.01) : si.smoo;
t = button("[3]gate") : si.smoo;
process = no.noise : fi.resonlp(ctFreq,q,gain)*t;

Note that we’re also using si.smoo on the output of the gate button to apply a exponential envelope on its signal.

This is a very broad introduction to making user interface elements in Faust. You can do much more like creating groups, using knobs, different types of menus, etc. but at least you should be able to make Faust programs at this point that are controllable and sound good (or not ;) ).

Final Polishing

Some Faust functions already contain a built-in UI and are ready-to-be-used. These functions are all placed in demo.lib and are accessible through the dm. environment.

As an example, let’s add a reverb to our previous code by calling dm.zita_light (high quality feedback delay network based reverb). Since this function has two implicit inputs, we also need to split the output of the filter (otherwise you will get an error because Faust wont know how to connect things):

import("stdfaust.lib");
ctFreq = hslider("[0]cutoffFrequency",500,50,10000,0.01) : si.smoo;
q = hslider("[1]q",5,1,30,0.1) : si.smoo;
gain = hslider("[2]gain",1,0,1,0.01) : si.smoo;
t = button("[3]gate") : si.smoo;
process = no.noise : fi.resonlp(ctFreq,q,gain)*t <: dm.zita_light;

Hopefully, you should see many more UI elements in your interface.

That’s it folks! At this point you should be able to use Faust standard functions, connect them together and build a simple UI at the top of them.

Some Project Ideas

In this section, we present a couple of project ideas that you could try to implement using Faust standard functions. Also, feel free to check the /examples folder of the Faust repository.

Additive Synthesizer

Make an additive synthesizer using os.osc (sine wave oscillator):

import("stdfaust.lib");
// freqs and gains definitions go here
process = 
    os.osc(freq0)*gain0,
    os.osc(freq2)*gain2 
    :> _ // merging signals here
    <: dm.zita_light; // and then splitting them for stereo in

FM Synthesizer

Make a frequency modulation (FM) synthesizer using os.osc (sine wave oscillator):

import("stdfaust.lib");
// carrierFreq, modulatorFreq and index definitions go here
process = 
    os.osc(carrierFreq+os.osc(modulatorFreq)*index)
    <: dm.zita_light; // splitting signals for stereo in

Guitar Effect Chain

Make a guitar effect chain:

import("stdfaust.lib");
process = 
    dm.cubicnl_demo : // distortion 
    dm.wah4_demo <: // wah pedal
    dm.phaser2_demo : // stereo phaser 
    dm.compressor_demo : // stereo compressor
    dm.zita_light; // stereo reverb

Since we’re only using functions from demo.lib here, there’s no need to define any UI since it is built-in in the functions that we’re calling. Note that the mono output of dm.wah4_demo is split to fit the stereo input of dm.phaser2_demo. The last three effects have the same number of inputs and outputs (2x2) so no need to split or merge them.

String Physical Model Based On a Comb Filter

Make a string physical model based on a feedback comb filter:

import("stdfaust.lib");
// freq, res and gate definitions go here
string(frequency,resonance,trigger) = trigger : ba.impulsify : fi.fb_fcomb(1024,del,1,resonance)
with {
    del = ma.SR/frequency;
};
process = string(freq,res,gate);

Sampling rate is defined in maths.lib as SR. We’re using it here to compute the length of the delay of the comb filter. with{} is a Faust primitive to attach local variables to a function. So in the current case, del is a local variable of string.

What to Do From Here?

TODO.

Overview of the Faust Universe

While in its most primitive form, Faust is distributed as a command-line compiler, a wide range of tools have been developed around it in the course of the past few years. Their variety and their function might be hard to grab at first. This sort chapter provides an overview of their role and will hopefully help you decide which one is better suited for your personal use.

TODO: here say a few words about the philosophy behind the disto: the online editor is the way to go for most users, then various pre-compiled packages of the compiler can be found, then source, then git. Finally other external tools for development.

The Faust Distribution

The Faust distribution hosts the source of the Faust compiler (both in its command line and library version), the source of the Faust architectures (targets), the various Faust compilation scripts, a wide range of Faust-related-tools, the Faust DSP Libraries (which in practice are hosted a separate Git submodule), etc.

The latest stable release of the Faust distribution can be found here: https://github.com/grame-cncm/faust/releases. It is recommended for most Faust users willing to compile the Faust compiler and libfaust from scratch.

To have the latest stable development version, you can use the master branch of the Faust git repository which is hosted on GitHub: https://github.com/grame-cncm/faust/tree/master.

For something even more bleeding edge (to be used at your own risks), you might use the master-dev branch of the Faust git repository: https://github.com/grame-cncm/faust/tree/master-dev. master-dev is the development sub-branch of master. It is used by Faust developers to commit their changes and can be considered as “the main development branch.” The goal is to make sure that master is always functional. Merges between master-dev and master are carried out multiple times a week by the GRAME team.

Also, note that pre-compiled packages of the Faust compiler and of libfaust for various platforms can be found on the Download Page of the Faust website.

The Faust distribution is organized as follows:

architecture/          : the source of the architecture files
benchmark/             : tools to measure the efficiency of the generated code
build/                 : the various makefiles and build folders
compiler/              : sources of the Faust compiler
COPYING                : license information
debian/                : files for Debian installation
Dockerfile             : docker file
documentation/         : Faust's documentations
examples/              : Faust programs examples organized by categories
installer/             : various installers for Linux distribution
libraries/             : Faust DSP libraries
Makefile               : makefile used to build and install Faust
README.md              : instructions on how to build and install Faust
syntax-highlighting/   : support for syntax highlighting for several editors
tests/                 : various tests
tools/                 : tools to produce audio applications and plug-ins
windows/               : Windows related ressources

The following subsections present some of the main components of the Faust distribution.

Command-Line Compiler

  • Link to precompiled version versions (download page)
  • What is the Faust compiler? (Quickly)
  • Link to Using the Faust Compiler

libfaust

faust2... Scripts

Web Tools

The Online Editor

The FaustPlayground

The Faust Online Compiler

Web Services

Development Tools

FaustLive

FaustWorks

A Quick Tour of the Faust Targets

TODO: say something about the fact that faust2 scripts only works on the Mac and Linux and that web services can help Windows users.

faust2alqt

faust2alqt converts a Faust program into a standalone application with an ALSA audio engine and a Qt interface.

Platforms

  • Linux only (because of ALSA)

Dependencies

  • ASLA development libraries
  • Qt development libraries

Usage

faust2alqt [OPTIONS] faustFile.dsp

Run:

faust2alqt -h

for additional help and options.

faust2alsa

faust2alsa converts a Faust program into a standalone application with an ALSA audio engine and a GTK interface.

Platforms

  • Linux only (because of ALSA)

Dependencies

  • ASLA development libraries
  • GTK development libraries

Usage

faust2alsa [OPTIONS] faustFile.dsp

Run:

faust2alsa -h

for additional help and options.

faust2alsaconsole

faust2alsaconsole converts a Faust program into a command line application with an ALSA audio engine.

Platforms

  • Linux only (because of ALSA)

Dependencies

  • ASLA development libraries

Usage

faust2alsaconsole [OPTIONS] faustFile.dsp

Run:

faust2alsaconsole -h

for additional help and options.

faust2android

faust2android converts a Faust program into a ready-to-use Android application. Additional information about this Faust target can be found on the corresponding website.

Platforms

  • Android only

Dependencies

  • Android SDK and development tools (i.e., Android studio, etc.)
  • Android NDK

Usage

faust2android [OPTIONS] faustFile.dsp

Run:

faust2android -h

for additional help and options.

faust2androidunity

faust2android converts a Faust program into an android library (armeabi-v7a and x86) for the Faust unity plug-in.

Platforms

  • Android only (within Unity)

Dependencies

  • Android SDK and development tools (i.e., Android studio, etc.)
  • Android NDK

Usage

faust2androidunity [OPTIONS] faustFile.dsp

Run:

faust2androidunity -h

for additional help and options.

faust2api

faust2api converts a Faust program into a C++ library that can be used with a simple API. It supports most of Faust’s target platforms (i.e., Android, iOS, ALSA, CoreAudio, JUCE, Jack, etc.).

Additional information about faust2api can be found on the corresponding website.

Platforms

  • Android
  • iOS
  • Windows
  • Linux
  • MacOS

Dependencies

Dependencies varies in functions of the target platform.

Usage

faust2api [TARGET] [OPTIONS] faustFile.dsp

Run:

faust2api -h

for additional help and options.

faust2asmjs

faust2asmjs converts a Faust program into an AMS JavaScript object to be used in a web-app.

Platforms

  • Web

Usage

faust2asmjs [OPTIONS] faustFile.dsp

Run:

faust2asmjs -h

for additional help and options.

faust2au

faust2au converts a Faust program into an Audio Unit plug-in.

Note that Audio Unit plug-ins can also be generated in Faust using faust2juce. In fact, this option might lead to better results in many cases.

Platforms

  • MacOS

Usage

faust2au [OPTIONS] faustFile.dsp

Run:

faust2au -h

for additional help and options.

faust2bela

faust2bela converts a Faust program into a BELA application.

Platforms

  • BELA

Usage

faust2bela [OPTIONS] faustFile.dsp

Run:

faust2bela -h

for additional help and options.

faust2caqt

faust2caqt converts a Faust program into a standalone application with an CoreAudio audio engine and a Qt interface.

Platforms

  • MacOS only (because of CoreAudio)

Usage

faust2caqt [OPTIONS] faustFile.dsp

Run:

faust2caqt -h

for additional help and options.

faust2caqtios

faust2caqtios converts a Faust program into an iOS app with a Qt interface.

Platforms

  • iOS

Usage

faust2caqtios [OPTIONS] faustFile.dsp

Run:

faust2caqtios -h

for additional help and options.

faust2ck

faust2ck converts a Faust program into a Chugin (ChucK plug-in). This tools was developed by Spencer Salazar.

Platforms

  • MacOS
  • Linux
  • Windows

Usage

faust2ck [OPTIONS] faustFile.dsp

faust2csound

faust2csound converts a Faust program into a CSOUND opcode.

Platforms

  • MacOS
  • Linux
  • Windows

Usage

faust2csound [OPTIONS] faustFile.dsp

faust2dssi

TODO

faust2dummy

TODO

faust2dummymem

TODO

faust2eps

faust2eps converts a Faust program into a diagram in EPS format.

Usage

faust2eps faustFile.dsp

faust2faustvst

faust2faustvst converts a Faust program into a VST plug-in. This tool was developed by Albert Gräf.

Note that VST plug-ins can also be generated in Faust using faust2vst and faust2juce. The former was developed prior to faust2faustvst by another author and provides less features. Hence faust2faustvst should be preferred to it. faust2juce is also a good option.

Platforms

  • MacOS
  • Windows
  • Linux

Dependencies

  • VST SDK

Usage

faust2faustvst [OPTIONS] faustFile.dsp

Run:

faust2faustvst -h

for additional help and options.

faust2firefox

faust2faustvst converts a Faust program into a block diagram and opens it in the default web browser of the system. It can be seen as a subset of faust2svg.

Platforms

  • MacOS
  • Linux

Usage

faust2firefox faustFile.dsp

faust2gen

faust2gen compiles a Faust program to a faustgen~ patch for MaxMSP.

Platforms

  • MacOS
  • Windows

Dependencies

  • MaxMSP SDK

Usage

faust2gen [OPTIONS] faustFile.dsp

Run:

faust2gen -h

for additional help and options.

faust2graph

faust2graph simply calls faust with the -tg option which generates the corresponding graph of internal computational loops.

Platforms

  • MacOS
  • Linux

Usage

faust2graph [OPTIONS] faustFile.dsp

faust2graphviewer

faust2graphviewer simply calls faust2graph and then opens the generated file in the default viewer.

Platforms

  • MacOS
  • Linux

Usage

faust2graphviewer [OPTIONS] faustFile.dsp

faust2ios

faust2ios turns a Faust program into a ready-to-use iOS application. While the app might be compiled directly from the command line calling this script, it will be necessary in most cases to generate an Xcode project to fix the bundle identifier of the app, etc.

Platforms

  • iOS

Dependencies

  • iOS SDK / development tools

Usage

faust2ios [OPTIONS] faustFile.dsp

Run:

faust2ios -h

for additional help and options.

faust2jack

faust2jack turns a Faust program into a standalone application with a Jack audio engine and a GTK interface.

Platforms

  • Linux
  • MacOS

Dependencies

  • Jack development libraries
  • GTK development libraries

Usage

faust2jack [OPTIONS] faustFile.dsp

Run:

faust2jack -h

for additional help and options.

faust2jackconsole

faust2jackconsole turns a Faust program into a command-line application with a Jack audio engine.

Platforms

  • Linux
  • MacOS

Dependencies

  • Jack development libraries

Usage

faust2jackconsole [OPTIONS] faustFile.dsp

Run:

faust2jackconsole -h

for additional help and options.

faust2jackinternal

TODO

faust2jackrust

TODO

faust2jackserver

faust2jackserver turns a Faust program into a standalone application with a Jack audio engine and a Qt interface in server mode (similar to faust2jaqt).

Platforms

  • Linux
  • MacOS
  • Windows

Dependencies

  • Jack development libraries
  • Qt development libraries

Usage

faust2jackserver [OPTIONS] faustFile.dsp

faust2jaqt

faust2jaqt turns a Faust program into a standalone application with a Jack audio engine and a Qt.

Platforms

  • Linux
  • MacOS
  • Windows

Dependencies

  • Jack development libraries
  • Qt development libraries

Usage

faust2jaqt [OPTIONS] faustFile.dsp

Run:

faust2jaqt -h

for additional help and options.

faust2jaqtchain

faust2jaqtchain is the same as faust2jaqt but it will work with multiple files (that will be compiled as multiple independent applications).

Platforms

  • Linux
  • MacOS
  • Windows

Dependencies

  • Jack development libraries
  • Qt development libraries

Usage

faust2jaqtchain [OPTIONS] faustFile0.dsp faustFile1.dsp etc.

Run:

faust2jaqtchain -h

for additional help and options.

faust2javaswing

faust2javaswing will compile a Faust program as a JAVA standalone application with a Swing interface.

Platforms

  • Linux
  • MacOS
  • Windows

Dependencies

  • JAVA

Usage

faust2javaswing [OPTIONS] faustFile.dsp

faust2juce

faust2ladspa

faust2linuxunity

faust2lv2

faust2mathdoc

faust2mathviewer

faust2jmax6

faust2md

faust2msp

faust2netjackconsole

faust2netjackqt

faust2nodejs

faust2octave

faust2osxiosunity

faust2owl

faust2paqt

faust2pdf

faust2plot

faust2png

faust2portaudiorust

faust2pure

faust2puredata

faust2raqt

faust2ros

faust2rosgtk

faust2rpialsaconsole

faust2rpinetjackconsole

faust2sam

faust2sig

faust2sigviewer

faust2smartkeyb

faust2sndfile

faust2supercollider

faust2svg

faust2unity

faust2unitywin

faust2vst

faust2vsti

faust2w32max6

faust2w32msp

faust2w32puredata

faust2w32vst

faust2w64max6

faust2wasm

faust2webaudio

faust2webaudioasm

faust2webaudiowasm

faust2webaudiowast

faust2winunity

Compiling and Installing the Faust Compiler

This chapter describes how to get and compile the Faust compiler as well as other tools related to Faust (e.g., libfaust, libosc, libhttpd, etc.).

TODO: more info here: normalize and explain that there exist pre-compiled binaries: link to those.

For many years, Faust was nearly dependencies-free, making it easy to compile using make. However, as the compiler developed and started supporting more backends and having more features, things slowly became more complicated. In particular, LLVM support added a significant layer of complexity to the compilation process. Moreover, the use of make was making the build process almost impossible for non-Unix systems (i.e., Windows).

For all these reasons, Faust’s build system is now based on CMake. CMake offers great deal of flexibility, both in terms of defining the targets to be compiled as well as selecting the different backends to be included in each target. However, this flexibility is based on a set of states (cached by CMake), which can sometimes make the compilation process a bit obscure.

The goal of this chapter is to provide practical information about this building system. The first section explains how to do a basic Faust installation, when LLVM is not required. The second section gives details on all the available options.

Basic Faust Installation

If you don’t have special needs and if you just want to compile Faust code to your favorite target (i.e., C++, JAVA, JavaScript, WebAssembly, etc.), things are relatively straight forward. This section describes the steps to generate a basic Faust build for Unix systems and for windows.

Unix Systems

In this section, we describe the steps to install and run Faust on a fresh Ubuntu 16.04 distribution. While this process might slightly differ from one Linux distribution to another and on macOS, users used to using the command line should be able to easily adapt these steps to their needs.

Compiling the Compiler

The first step is to compile Faust itself. Let’s start with the minimal requirement in terms of packages: the building tools, git and libmicrohttpd:

sudo apt-get update
sudo apt-get install -y build-essential cmake git libmicrohttpd-dev

We can now clone the GitHub repository of Faust:

git clone https://github.com/grame-cncm/faust.git

Then compile and install Faust:

cd faust
make
sudo make install

Once the installation has been completed, it can be checked:

faust -v

This will give you the version of the Faust compiler with a list of the available backends:

FAUST : DSP to C, C++, Java, JavaScript, old C++, asm.js, WebAssembly 
(wast/wasm) compiler, Version 2.11.1
Copyright (C) 2002-2018, GRAME - Centre National de Creation Musicale. All 
rights reserved. 

As you probably noticed the LLVM backend is not installed in this basic setup. You will typically need LLVM if you want to compile the library version of Faust. A complete installation is described in the next section.

We dont have any audio development package installed, but we can nevertheless use Faust to compile some of the examples to C++, at least to make sure that the compiler works:

faust examples/generator/noise.dsp

The above command will compile noise.dsp (from the Faust distribution) and print the corresponding C++ code to the standard output.

This C++ code has to be embedded into an architecture file (that describes how to relate the audio computation to the external world) before being compiled into standalone application or an audio plug-in. For that, you will need to install some development packages depending of your targets. For example, in order to compile a Faust program as a Jack (or ALSA) application with a Qt interface, the following development packages will have to be installed:

sudo apt-get install -y libasound2-dev libjack-jackd2-dev libqt4-dev

It is then possible to use the corresponding faust2 script to generate the corresponding application:

faust2alqt foo.dsp

for an ALSA application with a Qt interface.

faust2jaqt foo.dsp

for a Jack application with a Qt interface.

An overview of all the available Faust targets and of their dependencies is available in this section.

Windows Systems

TODO

Advanced Faust Build

The Faust distribution includes the Faust compiler, but also other elements that you may want to compile, in particular libfaust, the library version of the Faust compiler.

The way these elements are compiled can be configured with the appropriate files. This section demonstrate how this system works.

Faust Backends

The Faust compiler can output DSP code for various languages. Support for these languages is provided using backends that may or may not be embedded into the compiler or into the Faust compiler library version. This is intended to simplify the compilation process: some backends (like LLVM) proved to be a bit complex to compile, some others are not supported by all compilers (like the interpreter backend). In addition, selecting only the set of backends to be used, can reduce significantly the size of the resulting binary.

Selecting Your Backends

The backends selection is described using backends files which are actually cmake files that simply populate the cmake cache. These files are located in the build/backends folder of the Faust distribution. They consist of a matrix where each line corresponds to a language support and where the columns select (or discard) the corresponding backend for each binary output i.e.:

  • the Faust compiler,
  • the libfaust static library,
  • the libfaust dynamic library,
  • the libfaust asmjs library,
  • the libfaust wasm library.

The following example selects the ASMJS backend for the asmjs library, the cpp backend for the compiler and the faust static and dynamic libraries and discards the interpreter backend.

set (ASMJS_BACKEND ASMJS CACHE STRING "Include ASMJS backend" FORCE)
set (CPP_BACKEND COMPILER STATIC DYNAMIC CACHE STRING "Include CPP backend" FORCE)
set (INTERP_BACKEND OFF CACHE STRING "Include INTERPRETER backend" FORCE)

A BACKENDS option is provided to select a backend file using make e.g.:

make BACKENDS=backends.cmake

By default the selected backends are taken from backends.cmake. Note that make always looks for the backend files in the backends folder.

You can get similar results using direct cmake invocation:

cd faustdir
cmake -C ../backends/backends.cmake ..

The -C file option instructs cmake to populate the cache using the file given as argument.

Note that once the backends have been selected, they won’t change unless you specify another backend file.

Review the Compiled Backends

During project generation, cmake prints a list of all the backends that will be compiled for each component. Below you have an example of this output:

-- In target faust: include ASMJS backend
-- In target faust: include C backend
-- In target faust: include CPP backend
-- In target faust: include OCPP backend
-- In target faust: include WASM backend
-- In target staticlib: include ASMJS backend
-- In target staticlib: include C backend
-- In target staticlib: include CPP backend
-- In target staticlib: include OCPP backend
-- In target staticlib: include WASM backend
-- In target staticlib: include LLVM backend
-- In target wasmlib: include WASM backend
-- In target asmjslib: include ASMJS backend

Note also that the command faust -v prints the list of embedded backends support by the compiler e.g.:

FAUST : DSP to C, C++, FIR, Java, JavaScript, old C++, Rust, asm.js, WebAssembly 
(wast/wasm) compiler, Version 2.11.1
Copyright (C) 2002-2018, GRAME - Centre National de Creation Musicale. All 
rights reserved. 

Building Steps

The compilation process takes place in 2 phases:

  • project generation
  • project compilation

which are detailed in the following sections.

Project Generation

This is the step where you choose what you want to include in your project (before compiling it in a second step). The Faust compiler, the OSC, and HTTP libraries are included by default, but you can add (or remove) the Faust libraries (static or dynamic versions). You can also choose the form of your project: a Makefile, an Xcode or Visual Studio project, or any of the generator provided by cmake on your platform.

You may think of this step as the definition of the targets that will be available from your project. Note that you can also choose the Faust backends that you want to include in the different components (compiler and Faust libraries).

Project Form and Location

Cmake provides support for a lot of development environments depending on you platform. To know what environments are supported, type cmake --help and you’ll get a list of the supported generators at the end of the help message.

By default, the Makefile makes use of “Unix Makefiles” (or “MSYS Makefiles” on Windows). Thus, when you type make, it generates a Makefile and then runs a make command using this Makefile. To avoid overwriting the existing Makefile, the project is generated in a subfolder named faustdir by default and is created on the fly.

You can freely change these default settings of make and the FAUSTDIR and GENERATOR options, that control the subfolder name and the generator to use. For example:

make GENERATOR=Xcode

will generate an Xcode project in the faustdir subfolder.

make FAUSTDIR=macos GENERATOR=Xcode

will generate an Xcode project in the macos subfolder, etc.

You can achieve similar results using direct cmake invocation:

mkdir macos
cd macos
cmake .. -G Xcode

Project Targets

By default, the generated project includes the Faust compiler and the OSC and HTTPD static libraries, but not the Faust static or dynamic libraries. The makefile provides specific targets to include these libraries in your project:

  • make configstatic: add the libfaust static library to your projects
  • make configdynamic: add the libfaust dynamic library to your projects
  • make configall: add the libfaust static and dynamic libraries to your projects
  • make reset: restore the default project settings.

Equivalent settings using direct cmake invocation are also available. For example, to add/remove the libfaust static library to/from your project, you can run the following command from your faustdir:

cmake -DINCLUDE_STATIC=[on/off] ..

You can have a look at the Makefile to see the correspondence between the make targets and the cmake equivalent call. Note that since cmake is a state machine, it’ll keep all the current settings (i.e. the values of the cmake variables) unless specified with new values.

Re-Generating the Project

The Makefile includes a special target to re-generate a given project. It can be used to change the backends, but it might also be a necessary step when including new source files (source files are scanned at project generation and are not described explicitly). Simply type:

make cmake [options]

All the options described in the previous sections can be specified when running the cmake target (except the GENERATOR option that can’t be changed at the cmake level).

A cmake equivalent call has the following form:

cd faustdir
cmake .. [optional cmake options]

Miscellaneous Project Configuration Targets

  • make verbose: activates the printing of the exact command that is ran at each make step
  • make silent: reverts what make verbose did
  • make universal: [MacOSX only] creates universal binaries
  • make native: [MacOSX only] reverts native only binaries (default state).

Compiling Using make or cmake

Once your project has been generated (see Building Steps), the default behavior is to compile all the targets that are included in the project. So, typing make will build the Faust compiler, the OSC static library and the HTTP static library when these 3 components are included in your project.

Standard Single Targets

Single targets are available to use with make or cmake:

  • faust: to build the Faust compiler
  • osc: to build the OSC library
  • http: to build the HTTP library

Single Targets Requiring a Project Configuration

  • staticlib: to build the libfaust library in static mode. Requires to call make configstatic first.
  • dynamiclib: to build libfaust library in dynamic mode. Requires to call make configdynamic first.
  • oscdynamic: to build OSC library in dynamic mode. Requires to call make configoscdynamic first.
  • httpdynamic: to build HTTP library in dynamic mode. Requires to call make confighttpdynamic first.

Targets Excluded From All

  • wasmlib: to build libfaust as a Web Assembly library.
  • asmjslib: to build libfaust as an ASM JS library.

These targets require the emcc compiler to be available from your path.

Platform-Specific Targets

  • ioslib: to build libfaust in static mode for iOS.

Invoking Targets From cmake

The general form to invoke a target using cmake commands is the following:

cmake --build <project dir> [--target target] [-- native project options]

The default cmake target is all. For example the following command builds all the targets included in your project:

cmake --build faustdir

Cmake takes care of the generator you used and thus provides a universal way to build your project from the command line whether it’s Makefile-based or IDE-based (e.g. Xcode or Visual Studio).

The following sequence creates and build a project using Visual Studio on Windows in release mode:

cd your_build_folder
cmake -C ../backends/backends.cmake .. -G "Visual Studio 14 2015 Win64"
cmake --build . --config Release

More information on how to build the Faust compiler on Windows can be found in the corresponding section.

For more details and options, you should refer to the cmake documentation.

The Install and Uninstall Targets

Generated projects always include an install target, which installs all the components included in the project. There is no uninstall target at the cmake level (not supported by cmake). It is provided by the Makefile only and is based on the install_manifest.txt file that is generated by the install target in build/faustdir.

Note that cmake ensures that all the targets of your project are up-to-date before installing and thus may compile some or all the targets. It can be annoying if you invoke sudo make install: the object files will then be property of the superuser and you can then have errors during later compilations due to access rights issues on object files. Hence, it is recommended to make sure that all your targets are up-to-date by running make before running sudo make install.

Faust Syntax

Faust Program

A Faust program is essentially a list of statements. These statements can be metadata declarations (either global metadata or function metadata), imports, definitions, and documentation tags, with optional C++ style (//... and /*...*/) comments.

Here is a short Faust program that implements of a simple noise generator (called from the noises.lib Faust library). It exhibits various kind of statements : two global metadata declarations, an imports, a comment, and a definition. We will study later how documentation statements work:

declare name "Noise";
declare copyright "(c)GRAME 2018";

import("stdfaust.lib");

// noise level controlled by a slider
process = no.noise * hslider("gain",0,0,1, 0.1);

The keyword process is the equivalent of main in C/C++. Any Faust program, to be valid, must at least define process.

Statements

The statements of a Faust program are of four kinds:

  • metadata declarations,
  • file imports,
  • definitions,
  • documentation.

All statements but documentation end with a semicolon ;.

Metadata

Metadata allow us to add elements which are not part of the language to Faust code. These can range to the name of a Faust program, its author, to potential compilation options or user interface element customizations.

There are three different types of metadata in Faust:

Note that some Global Metadata have standard names and can be used for specific tasks. Their role is described in the Standard Metadata section.

Global Metadata

All global metadata declaration in Faust start with declare, followed by a key and a string. For example:

declare name "Noise";

allows us to specify the name of a Faust program in its whole.

Unlike regular comments, metadata declarations will appear in the C++ code generated by the Faust compiler. A good practice is to start a Faust program with some standard declarations:

declare name "MyProgram";
declare author "MySelf";
declare copyright "MyCompany";
declare version "1.00";
declare license "BSD"; 

Function Metadata

Metadata can be associated to a specific function. In that case, declare is followed by the name of the function, a key, and a string. For example:

declare add author "John Doe"
add = +;

This is very useful when a library has several contributors and that functions potentially have different license terms, etc.

Standard Metadata

There exists a series of standard global metadata in Faust whose role role is described in the following table:

Metadata Role
declare options "[key0:value][key1:value]" This metadata can be used to specify various options associated to a Faust code such as the fact its polyphonic, if it should have OSC, MIDI support, etc. Specific keys usable with this metadata are described throughout this documentation.
declare interface "xxx" Specifies an interface replacing the standard Faust UI.

Imports

File imports allow us to import definitions from other source files.

For example import("maths.lib"); imports the definitions of the maths.lib library.

The most common file to be imported is the stdfaust.lib library which gives access to all the standard Faust libraries from a single point:

import("stdfaust.lib");
process = os.osc(440); // the "hello world" of computer music

Documentation Tags

Documentation statements are optional and typically used to control the generation of the mathematical documentation of a Faust program. This documentation system is detailed in the Mathematical Documentation chapter. In this section we essentially describe the documentation statements syntax.

A documentation statement starts with an opening <mdoc> tag and ends with a closing </mdoc> tag. Free text content, typically in Latex format, can be placed in between these two tags.

Moreover, optional sub-tags can be inserted in the text content itself to require the generation, at the insertion point, of mathematical equations, graphical block-diagrams, Faust source code listing and explanation notice.

The generation of the mathematical equations of a Faust expression can be requested by placing this expression between an opening <equation> and a closing </equation> tag. The expression is evaluated within the lexical context of the Faust program.

Similarly, the generation of the graphical block-diagram of a Faust expression can be requested by placing this expression between an opening <diagram> and a closing </diagram> tag. The expression is evaluated within the lexical context of the Faust program.

The <metadata> tags allow to reference Faust global metadatas, calling the corresponding keyword.

The <notice/> empty-element tag is used to generate the conventions used in the mathematical equations.

The <listing/> empty-element tag is used to generate the listing of the Faust program. Its three attributes mdoctags, dependencies, and distributed enable or disable respectively <mdoc> tags, other files dependencies and distribution of interleaved Faust code between <mdoc> sections.

Definitions

A definition associates an identifier with an expression. Definitions are essentially a convenient shortcut avoiding to type long expressions. During compilation, more precisely during the evaluation stage, identifiers are replaced by their definitions. It is therefore always equivalent to use an identifier or directly its definition. Please note that multiple definitions of a same identifier are not allowed, unless it is a pattern matching based definition.

Simple Definitions

The syntax of a simple definition is:

identifier = expression ;

For example here is the definition of random, a simple pseudo-random number generator:

random = +(12345) ~ *(1103515245);

Function Definitions

Definitions with formal parameters correspond to functions definitions.

For example the definition of linear2db, a function that converts linear values to decibels, is:

linear2db(x) = 20*log10(x);

Please note that this notation is only a convenient alternative to the direct use of lambda-abstractions (also called anonymous functions). The following is an equivalent definition of linear2db using a lambda-abstraction:

linear2db = \(x).(20*log10(x));

Definitions with pattern matching

Moreover, formal parameters can also be full expressions representing patterns.

This powerful mechanism allows to algorithmically create and manipulate block diagrams expressions. Let’s say that you want to describe a function to duplicate an expression several times in parallel:

duplicate(1,x) = x;
duplicate(n,x) = x, duplicate(n-1,x);

Note that this last definition is a convenient alternative to the more verbose:

duplicate = case { 
  (1,x) => x; 
  (n,x) => duplicate(n-1,x); 
};

A use case for duplicate could be to put 5 white noise generators in parallel:

import("stdfaust.lib");
duplicate(1,x) = x;
duplicate(n,x) = x, duplicate(n-1,x);
process = duplicate(5,no.noise);

Here is another example to count the number of elements of a list. Please note that we simulate lists using parallel composition: (1,2,3,5,7,11). The main limitation of this approach is that there is no empty list. Moreover lists of only one element are represented by this element:

count((x,xs)) = 1+count(xs);
count(x) = 1;

If we now write count(duplicate(10,666)), the expression will be evaluated as 10.

Note that the order of pattern matching rules matters. The more specific rules must precede the more general rules. When this order is not respected, as in:

count(x) = 1;
count((x,xs)) = 1+count(xs);

the first rule will always match and the second rule will never be called.

Expressions

Despite its textual syntax, Faust is conceptually a block-diagram language. Faust expressions represent DSP block-diagrams and are assembled from primitive ones using various composition operations. More traditional numerical expressions in infix notation are also possible. Additionally Faust provides time based expressions, like delays, expressions related to lexical environments, expressions to interface with foreign function and lambda expressions.

Diagram Expressions

Diagram expressions are assembled from primitive ones using either binary composition operations or high level iterative constructions.

Diagram Composition Operations

Five binary composition operations are available to combine block-diagrams:

One can think of each of these composition operations as a particular way to connect two block diagrams.

To describe precisely how these connections are done, we have to introduce some notation. The number of inputs and outputs of a block-diagram \(A\) are expressed as \(\mathrm{inputs}(A)\) and \(\mathrm{outputs}(A)\). The inputs and outputs themselves are respectively expressed as: \([0]A\), \([1]A\), \([2]A\), \(\ldots\) and \(A[0]\), \(A[1]\), \(A[2]\), etc.

For each composition operation between two block-diagrams \(A\) and \(B\) we will describe the connections \(A[i]\rightarrow [j]B\) that are created and the constraints on their relative numbers of inputs and outputs.

The priority and associativity of this five operations are:

Syntax Priority Association Description
expression ~ expression 4 left Recursive Composition
expression , expression 3 right Parallel Composition
expression : expression 2 right Sequential Composition
expression <: expression 1 right Split Composition
expression :> expression 1 right Merge Composition

Parallel Composition

The parallel composition (e.g., (A,B)) is probably the simplest one. It places the two block-diagrams one on top of the other, without connections. The inputs of the resulting block-diagram are the inputs of A and B. The outputs of the resulting block-diagram are the outputs of A and B.

Parallel composition is an associative operation: (A,(B,C)) and ((A,B),C) are equivalents. When no parenthesis are used (e.g., A,B,C,D), Faust uses right associativity and therefore builds internally the expression (A,(B,(C,D))). This organization is important to know when using pattern matching techniques on parallel compositions.

Example: Oscillators in Parallel

Parallel composition can be used to put 3 oscillators of different kinds and frequencies in parallel, which will result in a Faust program with 3 outputs:

import("stdfaust.lib");
process = os.osc(440),os.sawtooth(550),os.triangle(660);

Example: Stereo Effect

Parallel composition can be used to easily turn a mono effect into a stereo one which will result in a Faust program with 2 inputs and 2 outputs:

import("stdfaust.lib");
level = 1;
process = ve.autowah(level),ve.autowah(level);

Note that there’s a better to write this last example using the par iteration:

import("stdfaust.lib");
level = 1;
process = par(i,2,ve.autowah(level));

Sequential Composition

The sequential composition (e.g., A:B) expects:

\[\mathrm{outputs}(A)=\mathrm{inputs}(B)\]

It connects each output of \(A\) to the corresponding input of \(B\):

\[A[i]\rightarrow[i]B\]

Sequential composition is an associative operation: (A:(B:C)) and ((A:B):C) are equivalents. When no parenthesis are used, like in A:B:C:D, Faust uses right associativity and therefore builds internally the expression (A:(B:(C:D))).

Example: Sine Oscillator

Since everything is considered as a signal generator in Faust, sequential composition can be simply used to pass an argument to a function:

import("stdfaust.lib");
process = 440 : os.osc;

Example: Effect Chain

Sequential composition can be used to create an audio effect chain. Here we’re plugging a guitar distortion to an autowah:

import("stdfaust.lib");
drive = 0.6;
offset = 0;
autoWahLevel = 1;
process = ef.cubicnl(drive,offset) : ve.autowah(autoWahLevel);

Split Composition

The split composition (e.g., A<:B) operator is used to distribute the outputs of \(A\) to the inputs of \(B\).

For the operation to be valid, the number of inputs of \(B\) must be a multiple of the number of outputs of \(A\):

\[\mathrm{outputs}(A).k=\mathrm{inputs}(B)\]

Each input \(i\) of \(B\) is connected to the output \(i \bmod k\) of \(A\):

\[A[i \bmod k]\rightarrow[i]B\]

Example: Duplicating the Output of an Oscillator

Split composition can be used to duplicate signals. For example, the output of the following sawtooth oscillator is duplicated 3 times in parallel.

import("stdfaust.lib");
process = os.sawtooth(440) <: _,_,_;

Note that this can be written in a more effective way by replacing _,_,_ with par(i,3,_) using the par iteration.

Example: Connecting a Mono Effect to a Stereo One

More generally, the split composition can be used to connect a block with a certain number of output to a block with a greater number of inputs:

import("stdfaust.lib");
drive = 0.6;
offset = 0;
process = ef.cubicnl(drive,offset) <: dm.zita_light;

Note that an arbitrary number of signals can be split, for example:

import("stdfaust.lib");
drive = 0.6;
offset = 0;
process = par(i,2,ef.cubicnl(drive,offset)) <: par(i,2,dm.zita_light);

Once again, the only rule with this is that in the expression A<:B the number of inputs of B has to be a multiple of the number of outputs of A.

Merge Composition

The merge composition (e.g., A:>B) is the dual of the split composition. The number of outputs of \(A\) must be a multiple of the number of inputs of \(B\):

\[\mathrm{outputs}(A)=k.\mathrm{inputs}(B)\]

Each output \(i\) of \(A\) is connected to the input \(i \bmod k\) of \(B\) :

\[A[i]\rightarrow\ [i \bmod k]B\]

The \(k\) incoming signals of an input of \(B\) are summed together.

Example: Summing Signals Together - Additive Synthesis

Merge composition can be used to sum an arbitrary number of signals together. Here’s an example of a simple additive synthesizer (note that the result of the sum of the signals is divided by 3 to prevent clicking):

import("stdfaust.lib");
freq = hslider("freq",440,50,3000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = gain*gate : si.smoo;
process = os.osc(freq),os.osc(freq*2),os.osc(freq*3) :> /(3)*envelope;

While the resulting block diagram will look slightly different, this is mathematically equivalent to:

import("stdfaust.lib");
freq = hslider("freq",440,50,3000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = gain*gate : si.smoo;
process = (os.osc(freq) + os.osc(freq*2) + os.osc(freq*3))/(3)*envelope;

Example: Connecting a Stereo Effect to a Mono One

More generally, the merge composition can be used to connect a block with a certain number of output to a block with a smaller number of inputs:

import("stdfaust.lib");
drive = 0.6;
offset = 0;
process = dm.zita_light :> ef.cubicnl(drive,offset);

Note that an arbitrary number of signals can be split, for example:

import("stdfaust.lib");
drive = 0.6;
offset = 0;
process = par(i,2,dm.zita_light) :> par(i,2,ef.cubicnl(drive,offset));

Once again, the only rule with this is that in the expression A:>B the number of outputs of A has to be a multiple of the number of inputs of B.

Recursive Composition

The recursive composition (e.g., A~B) is used to create cycles in the block-diagram in order to express recursive computations. It is the most complex operation in terms of connections.

To be applicable, it requires that:

\[\mathrm{outputs}(A) \geq \mathrm{inputs}(B) and \mathrm{inputs}(A) \geq \mathrm{outputs}(B)\]

Each input of \(B\) is connected to the corresponding output of \(A\) via an implicit 1-sample delay :

\[A[i]\stackrel{Z^{-1}}{\rightarrow}[i]B\]

and each output of \(B\) is connected to the corresponding input of \(A\):

\[B[i]\rightarrow [i]A\]

The inputs of the resulting block diagram are the remaining unconnected inputs of \(A\). The outputs are all the outputs of \(A\).

Example: Timer

Recursive composition can be used to implement a “timer” that will count each sample starting at time \(n=0\):

process = _~+(1);

The difference equation corresponding to this program is:

\[y(n) = y(n-1) + 1\]

an its output signal will look like: \((1,2,3,4,5,6,\dots)\).

Example: One Pole Filter

Recursive composition can be used to implement a one pole filter with one line of code and just a few characters:

a1 = 0.999; // the pole
process = +~*(a1);

The difference equation corresponding to this program is:

\[y(n) = x(n) + a_{1}y(n-1)\]

Note that the one sample delay of the filter is implicit here so it doesn’t have to be declared.

Inputs and Outputs of an Expression

The number of inputs and outputs of a Faust expression can be known at compile time simply by using inputs(expression) and outputs(expression).

For example, the number of outputs of a sine wave oscillator can be known simply by writing the following program:

import("stdfaust.lib");
process = outputs(os.osc(440));

Note that Faust automatically simplified the expression by generating a program that just outputs 1.

This type of construction is useful to define high order functions and build algorithmically complex block-diagrams. Here is an example to automatically reverse the order of the outputs of an expression.

Xo(expr) = expr <: par(i,n,ba.selector(n-i-1,n)) 
with { 
  n = outputs(expr);
};

And the inputs of an expression :

Xi(expr) = si.bus(n) <: par(i,n,ba.selector(n-i-1,n)) : expr 
with { 
  n = inputs(expr); 
};

For example Xi(-) will reverse the order of the two inputs of the substraction:

import("stdfaust.lib");
Xi(expr) = si.bus(n) <: par(i,n,ba.selector(n-i-1,n)) : expr 
with { 
  n = inputs(expr); 
};
toto = os.osc(440),os.sawtooth(440), os.triangle(440);
process = Xi(-);

Iterations

Iterations are analogous to for(...) loops in other languages and provide a convenient way to automate some complex block-diagram constructions.

The use and role of par, seq, sum, and prod are detailed in the following sections.

par Iteration

The par iteration can be used to duplicate an expression in parallel. Just like other types of iterations in Faust:

  • its first argument is a variable name containing the number of the current iteration (a bit like the variable that is usually named i in a for loop) starting at 0,
  • its second argument is the number of iterations,
  • its third argument is the expression to be duplicated.

Example: Simple Additive Synthesizer

import("stdfaust.lib");
freq = hslider("freq",440,50,3000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = gain*gate : si.smoo;
nHarmonics = 4;
process = par(i,nHarmonics,os.osc(freq*(i+1))) :> /(nHarmonics)*envelope;

i is used here at each iteration to compute the value of the frequency of the current oscillator. Also, note that this example could be re-wrtitten using sum iteration (see example in the corresponding section).

seq Iteration

The seq iteration can be used to duplicate an expression in series. Just like other types of iterations in Faust:

  • its first argument is a variable name containing the number of the current iteration (a bit like the variable that is usually named i in a for loop) starting at 0,
  • its second argument is the number of iterations,
  • its third argument is the expression to be duplicated.

Example: Peak Equalizer

The fi.peak_eq function of the Faust libraries implements a second order “peak equalizer” section (gain boost or cut near some frequency). When placed in series, it can be used to implement a full peak equalizer:

import("stdfaust.lib");
nBands = 8;
filterBank(N) = hgroup("Filter Bank",seq(i,N,oneBand(i)))
with {
    oneBand(j) = vgroup("[%j]Band %a",fi.peak_eq(l,f,b))
    with {
        a = j+1; // just so that band numbers don't start at 0
        l = vslider("[2]Level[unit:db]",0,-70,12,0.01) : si.smoo;
        f = nentry("[1]Freq",(80+(1000*8/N*(j+1)-80)),20,20000,0.01) : si.smoo;
        b = f/hslider("[0]Q[style:knob]",1,1,50,0.01) : si.smoo;
    };
};
process = filterBank(nBands);

Note that i is used here at each iteration to compute various elements and to format some labels. Having user interface elements with different names is a way to force their differentiation in the generated interface.

sum Iteration

The sum iteration can be used to duplicate an expression as a sum. Just like other types of iterations in Faust:

  • its first argument is a variable name containing the number of the current iteration (a bit like the variable that is usually named i in a for loop) starting at 0,
  • its second argument is the number of iterations,
  • its third argument is the expression to be duplicated.

Example: Simple Additive Synthesizer

The following example is just a slightly different version from the one presented in the par iteration section. While their block diagrams look slightly different, the generated code is exactly the same.

import("stdfaust.lib");
freq = hslider("freq",440,50,3000,0.01);
gain = hslider("gain",1,0,1,0.01);
gate = button("gate");
envelope = gain*gate : si.smoo;
nHarmonics = 4;
process = sum(i,nHarmonics,os.osc(freq*(i+1)))/(nHarmonics)*envelope;

i is used here at each iteration to compute the value of the frequency of the current oscillator.

prod Iteration

The prod iteration can be used to duplicate an expression as a product. Just like other types of iterations in Faust:

  • its first argument is a variable name containing the number of the current iteration (a bit like the variable that is usually named i in a for loop) starting at 0,
  • its second argument is the number of iterations,
  • its third argument is the expression to be duplicated.

Example: Amplitude Modulation Synthesizer

The following example implements an amplitude modulation synthesizer using an arbitrary number of oscillators thanks to the prod iteration:

import("stdfaust.lib");
freq = hslider("[0]freq",440,50,3000,0.01);
gain = hslider("[1]gain",1,0,1,0.01);
shift = hslider("[2]shift",0,0,1,0.01);
gate = button("[3]gate");
envelope = gain*gate : si.smoo;
nOscs = 4;
process = prod(i,nOscs,os.osc(freq*(i+1+shift)))*envelope;

i is used here at each iteration to compute the value of the frequency of the current oscillator. Note that the shift parameter can be used to tune the frequency drift between each oscillator.

Infix Notation and Other Syntax Extensions

Infix notation is commonly used in mathematics. It consists in placing the operand between the arguments as in \(2+3\)

Besides its algebra-based core syntax, Faust provides some syntax extensions, in particular the familiar infix notation. For example if you want to multiply two numbers, say 2 and 3, you can write directly 2*3 instead of the equivalent core-syntax expression 2,3 : *.

The infix notation is not limited to numbers or numerical expressions. Arbitrary expressions A and B can be used, provided that A,B has exactly two outputs. For example _/2 is equivalent to _,2:/ which divides the incoming signal by 2.

Here are a few examples of equivalences:

Infix Syntax Core Syntax
2-3 \(\equiv\) 2,3 : -
2*3 \(\equiv\) 2,3 : *
_@7 \(\equiv\) _,7 : @
_/2 \(\equiv\) _,2 : /
A<B \(\equiv\) A,B : <

In case of doubts on the meaning of an infix expression, for example _*_, it is useful to translate it to its core syntax equivalent, here _,_:*, which is equivalent to *.

Infix Operators

Built-in primitives that can be used in infix notation are called infix operators and are listed below. Please note that a more detailed description of these operators is available section on primitives.

Prefix Notation

Beside infix notation, it is also possible to use prefix notation. The prefix notation is the usual mathematical notation for functions \(f(x,y,z,\ldots)\), but extended to infix operators.

It consists in first having the operator, for example /, followed by its arguments between parentheses: /(2,3):

Prefix Syntax Core Syntax
*(2,3) \(\equiv\) 2,3 : *
@(_,7) \(\equiv\) _,7 : @
/(_,2) \(\equiv\) _,2 : /
<(A,B) \(\equiv\) A,B : <

Partial Application

The partial application notation is a variant of the prefix notation in which not all arguments are given. For instance /(2) (divide by 2), ^(3) (rise to the cube), and @(512) (delay by 512 samples) are examples of partial applications where only one argument is given. The result of a partial application is a function that “waits” for the remaining arguments.

When doing partial application with an infix operator, it is important to note that the supplied argument is not the first argument, but always the second one:

Prefix Partial Application Syntax Core Syntax
+(C) \(\equiv\) _,C : *
-(C) \(\equiv\) _,C : -
<(C) \(\equiv\) _,C : <
/(C) \(\equiv\) _,C : /

For commutative operations that doesn’t matter. But for non-commutative ones, it is more “natural” to fix the second argument. We use divide by 2 (/(2)) or rise to the cube (^(3)) more often than the other way around.

Please note that this rule only applies to infix operators, not to other primitives or functions. If you partially apply a regular function to a single argument, it will correspond to the first parameter.

Example: Gain Controller

The following example demonstrates the use of partial application in the context of a gain controller:

gain = hslider("gain",0.5,0,1,0.01);
process = *(gain);

' Time Expression

' is used to express a one sample delay. For example:

process = _';

will delay the incoming signal by one sample.

' time expressions can be chained, so the output signal of this program:

process = 1'';

will look like: \((0,0,1,1,1,1,\dots)\).

The ' time expression is useful when designing filters, etc. and is equivalent to @(1) (see the @ Time Expression).

@ Time Expression

@ is used to express a delay with an arbitrary number of samples. For example:

process = @(10);

will delay the incoming signal by 10 samples.

A delay expressed with @ doesn’t have to be fixed but it must be positive and bounded. Therefore, the values of a slider are perfectly acceptable:

process = @(hslider("delay",0,0,100,1));

@ only allows for the implementation of integer delay. Thus, various fractional delay algorithms are implemented in the Faust libraries.

Environment Expressions

Faust is a lexically scoped language. The meaning of a Faust expression is determined by its context of definition (its lexical environment) and not by its context of use.

To keep their original meaning, Faust expressions are bounded to their lexical environment in structures called closures. The following constructions allow to explicitly create and access such environments. Moreover they provide powerful means to reuse existing code and promote modular design.

with Expression

The with construction allows to specify a local environment: a private list of definition that will be used to evaluate the left hand expression.

In the following example :

pink = f : + ~ g 
with {
  f(x) = 0.04957526213389*x - 0.06305581334498*x' + 0.01483220320740*x'';
    g(x) = 1.80116083982126*x - 0.80257737639225*x';
};
process = pink;

the definitions of f(x) and g(x) are local to f : + ~ g.

Please note that with is left associative and has the lowest priority:

  • f : + ~ g with {...} is equivalent to (f : + ~ g) with {...}.
  • f : + ~ g with {...} with {...} is equivalent to ((f : + ~ g) with {...}) with {...}.

letrec Expression

The letrec construction is somehow similar to with, but for difference equations instead of regular definitions. It allows us to easily express groups of mutually recursive signals, for example:

\[ x(t) = y(t-1) + 10\\ y(t) = x(t-1) - 1 \]

as E letrec { 'x = y+10; 'y = x-1; }

The syntax is defined by the following rules:

Note the special notation 'x = y + 10 instead of x = y' + 10. It makes syntactically impossible to write non-sensical equations like x=x+1.

Here is a more involved example. Let say we want to define an envelope generator with an attack and a release time (as a number of samples), and a gate signal. A possible definition could be:

import("stdfaust.lib");
ar(a,r,g) = v
letrec {
  'n = (n+1) * (g<=g');
  'v = max(0, v + (n<a)/a - (n>=a)/r) * (g<=g');
};
gate = button("gate");
process = os.osc(440)*ar(1000,1000,gate);

With the following semantics for \(n(t)\) and \(v(t)\):

\[ n(t) = (n(t-1)+1) * (g(t) <= g(t-1))\\ v(t) = max(0, v(t-1) + (n(t-1)<a(t))/a(t) - (n(t-1)>=a(t))/r(t)) * (g(t)<=g(t-1)) \]

environment Expression

The environment construction allows to create an explicit environment. It is like a `with’, but without the left hand expression. It is a convenient way to group together related definitions, to isolate groups of definitions and to create a name space hierarchy.

In the following example an environment construction is used to group together some constant definitions :

constant = environment {
  pi = 3.14159;
  e = 2,718;
    ...
};

The . construction allows to access the definitions of an environment (see next section).

Access Expression

Definitions inside an environment can be accessed using the . construction.

For example constant.pi refers to the definition of pi in the constant environment defined above.

Note that environments don’t have to be named. We could have written directly:

environment{pi = 3.14159; e = 2,718;....}.pi

library Expression

The library construct allows to create an environment by reading the definitions from a file.

For example library("filters.lib") represents the environment obtained by reading the file filters.lib. It works like import("miscfilter.lib") but all the read definitions are stored in a new separate lexical environment. Individual definitions can be accessed as described in the previous paragraph. For example library("filters.lib").lowpass denotes the function lowpass as defined in the file miscfilter.lib.

To avoid name conflicts when importing libraries it is recommended to prefer library to import. So instead of :

import("filters.lib");
  ...
...lowpass....
    ...
};

the following will ensure an absence of conflicts :

fl = library("filters.lib");
  ...
...fl.lowpass....
    ...
};

In practice, that’s how the stdfaust.lib library works.

component Expression

The component construction allows us to reuse a full Faust program (e.g., a .dsp file) as a simple expression.

For example component("freeverb.dsp") denotes the signal processor defined in file freeverb.dsp.

Components can be used within expressions like in:

...component("karplus32.dsp") : component("freeverb.dsp")... 

Please note that component("freeverb.dsp") is equivalent to library("freeverb.dsp").process.

component works well in tandem with explicit substitution (see next section).

Explicit Substitution

Explicit substitution can be used to customize a component or any expression with a lexical environment by replacing some of its internal definitions, without having to modify it.

For example we can create a customized version of component("freeverb.dsp"), with a different definition of foo(x), by writing:

...component("freeverb.dsp")[foo(x) = ...;]...
};

Foreign Expressions

Reference to external C functions, variables and constants can be introduced using the foreign function mechanism.

ffunction

An external C function is declared by indicating its name and signature as well as the required include file. The file maths.lib of the Faust distribution contains several foreign function definitions, for example the inverse hyperbolic sine function asinh:

asinh = ffunction(float asinh (float), <math.h>, "");

Foreign functions with input parameters are considered pure math functions. They are therefore considered free of side effects and called only when their parameters change (that is at the rate of the fastest parameter).

Exceptions are functions with no input parameters. A typical example is the C rand() function. In this case, the compiler generates code to call the function at sample rate.

Signature

The signature part (float asinh (float) in the example presented in the previous section) describes the prototype of the C function: return type, function name, and list of parameter types. Because the name of the foreign function can possibly depend on the floating point precision in use (float, double and quad), it is possible to give a different function name for each floating point precision using a signature with up to three function names.

For example in the declaration:

asinh = ffunction(float asinhf|asinh|asinhl (float), <math.h>, "");

the signature float asinhf|asinh|asinhl (float) indicates to use the function name asinhf in single precision, asinh in double precision and asinhl in long double (quad) precision.

Types

Only numerical functions involving simple int and float parameters are allowed currently in Faust. No vectors, tables or data structures can be passed as parameters or returned.

Variables and Constants

External variables and constants can also be declared with a similar syntax. In the same maths.lib file, the definition of the sampling rate constant SR and the definition of the block-size variable BS can be found:

SR = min(192000.0,max(1.0,fconstant(int fSamplingFreq, <math.h>)));
BS = fvariable(int count, <math.h>);

Foreign constants are not supposed to vary. Therefore expressions involving only foreign constants are only computed once, during the initialization period.

Variable are considered to vary at block speed. This means that expressions depending of external variables are computed every block.

File Include

In declaring foreign functions one has also to specify the include file. It allows the Faust compiler to add the corresponding #include in the generated code.

Library File

In declaring foreign functions one can possibly specify the library where the actual code is located. It allows the Faust compiler to (possibly) automatically link the library. Note that this feature is only used with the LLVM backend in ‘libfaust’ dynamic library model.

Applications and Abstractions

Abstractions and applications are fundamental programming constructions directly inspired by Lambda-Calculus. These constructions provide powerful ways to describe and transform block-diagrams algorithmically.

Abstractions

Abstractions correspond to functions definitions and allow to generalize a block-diagram by making variable some of its parts.

Let’s say we want to transform a stereo reverb, dm.zita_light for instance, into a mono effect. The following expression can be written (see the sections on Split Composition and Merge Composition):

_ <: dm.zita_light :> _ 

The incoming mono signal is split to feed the two input channels of the reverb, while the two output channels of the reverb are mixed together to produce the resulting mono output.

Imagine now that we are interested in transforming other stereo effects. We could generalize this principle by making zita_light a variable:

\(zita_light).(_ <: zita_light :> _)

The resulting abstraction can then be applied to transform other effects. Note that if zita_light is a perfectly valid variable name, a more neutral name would probably be easier to read like:

\(fx).(_ <: fx :> _)

A name can be given to the abstraction and in turn use it on dm.zita_light:

import("stdfaust.lib");
mono = \(fx).(_ <: fx :> _);
process = mono(dm.zita_light);

Or even use a more traditional, but equivalent, notation:

mono(fx) = _ <: fx :> _;

Applications

Applications correspond to function calls and allow to replace the variable parts of an abstraction with the specified arguments.

For example, the abstraction described in the previous section can be used to transform a stereo reverb:

mono(dm.zita_light)

The compiler will start by replacing mono by its definition:

\(fx).(_ <: fx :> _)(dm.zita_light)

Replacing the variable part with the argument is called beta-reduction in Lambda-Calculus

Whenever the Faust compiler find an application of an abstraction it replaces the variable part with the argument. The resulting expression is as expected:

(_ <: dm.zita_light :> _)

Pattern Matching

Pattern matching rules provide an effective way to analyze and transform block-diagrams algorithmically.

For example case{ (x:y) => y:x; (x) => x; } contains two rules. The first one will match a sequential expression and invert the two part. The second one will match all remaining expressions and leave it untouched. Therefore the application:

case{(x:y) => y:x; (x) => x;}(reverb : harmonizer)

will produce:

harmonizer : freeverb

Please note that patterns are evaluated before the pattern matching operation. Therefore only variables that appear free in the pattern are binding variables during pattern matching.

Primitives

The primitive signal processing operations represent the built-in functionalities of Faust, that is the atomic operations on signals provided by the language. All these primitives denote signal processors, in other words, functions transforming input signals into output signals.

Numbers

Faust considers two types of numbers: integers and floats. Integers are implemented as 32-bits integers, and floats are implemented either with a simple, double, or extended precision depending of the compiler options. Floats are available in decimal or scientific notation.

Like any other Faust expression, numbers are signal processors. For example the number 0.95 is a signal processor of type \(\mathbb{S}^{0}\rightarrow\mathbb{S}^{1}\) that transforms an empty tuple of signals \(()\) into a 1-tuple of signals \((y)\) such that \(\forall t\in\mathbb{N}, y(t)=0.95\).

waveform Primitive

The waveform primitive was designed to facilitate the use of rdtable (read table). It allows us to specify a fixed periodic signal as a list of samples.

waveform has two outputs:

  • a constant and indicating the size (as a number of samples) of the period,
  • the periodic signal itself.

For example waveform{0,1,2,3} produces two outputs: the constant signal 4 and the periodic signal \((0,1,2,3,0,1,2,3,0,1,\dots)\).

In the following example:

import("stdfaust.lib");
triangleWave = waveform{0,0.5,1,0.5,0,-0.5,-1,-.5};
triangleOsc(f) = triangleWave,int(os.phasor(8,f)) : rdtable;
f = hslider("freq",440,50,2000,0.01);
process = triangleOsc(f);

waveform is used to define a triangle waveform (in its most primitive form), which is then used with a rdtable controlled by a phasor to implement a triangle wave oscillator. Note that the quality of this oscillator is very low because of the low resolution of the triangle waveform.

soundfile Primitive

The soundfile("label[url:{'path1';'path2';'path3'}]", n) primitive allows for the access a list of externally defined sound resources, described as the list of their filename, or complete paths. The soundfile("label[url:path]", n) simplified syntax allows to use a single file. A soundfile has:

  • two inputs: the sound number (as a integer between 0 and 255 checked at compilation time), and the read index in the sound (which will access the last sample of the sound if the read index is greater than the sound length)
  • two fixed outputs: the first one is the currently accessed sound length in frames, the second one is the currently accessed sound nominal sample rate in frames
  • several more outputs for the sound channels themselves

If more outputs than the actual number of channels in the sound file are used, the audio channels will be automatically duplicated up to the wanted number of outputs (so for instance, if a stereo file is used with four output channels, the same group of two channels will be duplicated).

If the soundfile cannot be loaded for whatever reason, a default sound with one channel, a length of 1024 frames and null outputs (with samples of value 0) will be used. Note also that soundfiles are entirely loaded in memory by the architecture file, so that the read index signal can access any sample.

Architecture files are responsible to load the actual soundfile. The SoundUI C++ class located in the faust/gui/SoundUI.h file in the Faust repository implements the void addSoundfile(label, path, sf_zone) method, which loads the actual soundfiles using the libsndfile library, or possibly specific audio file loading code (in the case of the JUCE framework for instance), and set up the sf_zone sound memory pointers. If label is used without any url metadata, it will be considered as the soundfile pathname.

Note that a special architecture file can well decide to access and use sound resources created by another means (that is, not directly loaded from a sound file). For instance a mapping between labels and sound resources defined in memory could be used, with some additional code in charge of actually setting up all sound memory pointers when void addSoundfile(label, path, sf_zone) is called by the buidUserInterface mechanism.

C-Equivalent Primitives

Most Faust primitives are analogous to their C counterpart but adapted to signal processing. For example + is a function of type \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\) that transforms a pair of signals \((x_1,x_2)\) into a 1-tuple of signals \((y)\) such that \(\forall t\in\mathbb{N}, y(t)=x_{1}(t)+x_{2}(t)\). + can be used to very simply implement a mixer:

process = +;

Note that this is equivalent to (see Identity Function):

process = _+_;

The function - has type \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\) and transforms a pair of signals \((x_1,x_2)\) into a 1-tuple of signals \((y)\) such that \(\forall t\in\mathbb{N}, y(t)=x_{1}(t)-x_{2}(t)\).

Be aware that the unary - only exists in a limited form. It can be used with numbers: -0.5 and variables: -myvar, but not with expressions surrounded by parenthesis, because in this case it represents a partial application. For instance, -(a*b) is a partial application. It is syntactic sugar for _,(a*b) : -. If you want to negate a complex term in parenthesis, you’ll have to use 0 - (a*b) instead.

Integer Number

Integer numbers are of type \(\mathbb{S}^{0}\rightarrow\mathbb{S}^{1}\) in Faust and can be described mathematically as \(y(t)=n\).

Example: DC Offset of 1

process = 1;

Floating Point Number

Floating point numbers are of type \(\mathbb{S}^{0}\rightarrow\mathbb{S}^{1}\) in Faust and can be described as \(y(t)=n.m\).

Example: DC Offset of 0.5

process = 0.5;

Identity Function

The identity function is expressed in Faust with the _ primitive.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x(t)\)

Example: a Signal Passing Through

In the following example, the _ primitive is used to connect the single audio input of a Faust program to its output:

process = _;

Cut Primitive

The cut primitive is expressed in Faust with !. It can be used to “stop”/terminate a signal.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{0}\)
  • Mathematical Description: \(\forall x\in\mathbb{S},(x)\rightarrow ()\)

Example: Stopping a Signal

In the following example, the ! primitive is used to stop one of two parallel signals:

process = 1,2 : !,_;

int Primitive

The int primitive can be used to force the cast of a signal to int. It is of type \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\) and can be described mathematically as \(y(t)=(int)x(t)\). This primitive is useful when declaring indices to read in a table, etc.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=(int)x(t)\)

Example: Simple Cast

process = 1.5 : int;

float Primitive

The float primitive can be used to force the cast of a signal to float.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=(float)x(t)\)

Example: Simple Cast

process = 1.5 : float;

Add Primitive

The + primitive can be used to add two signals together.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)+x_{2}(t)\)

Example: Simple Mixer

process = +;

Subtract Primitive

The - primitive can be used to subtract two signals.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)-x_{2}(t)\)

Example: Subtracting Two Input Signals

process = -;

Multiply Primitive

The * primitive can be used to multiply two signals.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)*x_{2}(t)\)

Example: Multiplying a Signal by 0.5

process = *(0.5);

Divide Primitive

The / primitive can be used to divide two signals.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)/{x_{2}(t)}\)

Example: Dividing a Signal by 2

process = ^(2);

Power Primitive

The ^ primitive can be used to raise to the power of N a signal.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)^{x_{2}(t)}\)

Example: Power of Two of a Signal

process = ^(2);

Modulo Primitive

The % primitive can be used to take the modulo of a signal.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)\%{x_{2}(t)}\)

Example: Phaser

The following example uses a counter and the % primitive to implement a basic phaser:

process = _~+(1) : -(1) : %(10);

will output a signal: (0,1,2,3,4,5,6,7,8,9,0,1,2,3,4).

AND Primitive

Logical AND can be expressed in Faust with the & primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)\&{x_{2}(t)}\)

Example

TODO

OR Primitive

Logical OR can be expressed in Faust with the | primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)|{x_{2}(t)}\)

Example

The following example will output 1 if the incoming signal is smaller than 0.5 or greater than 0.7 and 0 otherwise. Note that the result of this operation could be multiplied to another signal to create a condition.

process = _ <: <(0.5) | >(0.7);

XOR Primitive

Logical XOR can be expressed in Faust with the xor primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t)\land {x_{2}(t)}\)

Example

process = _ <: <(0.5) xor >(0.7);

Left Shift Primitive

Left shift can be expressed in Faust with the << primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) << {x_{2}(t)}\)

Example

process = 1 << 2;

Right Shift Primitive

Right shift can be expressed in Faust with the >> primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) >> {x_{2}(t)}\)

Example

process = 1 >> 2;

Smaller Than Primitive

The smaller than comparison can be expressed in Faust with the < primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) < {x_{2}(t)}\)

Example

The following code will output 1 if the input signal is smaller than 0.5 and 0 otherwise.

process = <(0.5);

Smaller or Equal Than Primitive

The smaller or equal than comparison can be expressed in Faust with the <= primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) <= {x_{2}(t)}\)

Example

The following code will output 1 if the input signal is smaller or equal than 0.5 and 0 otherwise.

process = <=(0.5);

Greater Than Primitive

The greater than comparison can be expressed in Faust with the > primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) > {x_{2}(t)}\)

Example

The following code will output 1 if the input signal is greater than 0.5 and 0 otherwise.

process = >(0.5);

Greater or Equal Than Primitive

The greater or equal than comparison can be expressed in Faust with the >= primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) >= {x_{2}(t)}\)

Example

The following code will output 1 if the input signal is greater or equal than 0.5 and 0 otherwise.

process = >=(0.5);

Equal to Primitive

The equal to comparison can be expressed in Faust with the == primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) == {x_{2}(t)}\)

Example

process = 0 == 1;

Different Than Primitive

The different than comparison can be expressed in Faust with the != primitive.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=x_{1}(t) != {x_{2}(t)}\)

Example

process = 0 != 1;

math.h-Equivalent Primitives

Most of the C math.h functions are also built-in as primitives (the others are defined as external functions in file math.lib).

acos Primitive

Arc cosine can be expressed as acos in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{acosf}(x(t))\)

Example

process = 0.1 : acos;

asin Primitive

Arc sine can be expressed as asin in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{asinf}(x(t))\)

Example

process = 0.1 : asin;

atan Primitive

Arc tangent can be expressed as atan in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{atanf}(x(t))\)

Example

process = 0.1 : atan;

atan2 Primitive

The arc tangent of 2 signals can be expressed as atan2 in Faust.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{atan2f}(x_{1}(t), x_{2}(t))\)

Example

process = 0.1,-0.1 : atan2;

cos Primitive

Cosine can be expressed as cos in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{cosf}(x(t))\)

Example

process = 0.1 : cos;

sin Primitive

Sine can be expressed as sin in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{sinf}(x(t))\)

Example

process = 0.1 : sin;

tan Primitive

Tangent can be expressed as tan in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{tanf}(x(t))\)

Example

process = 0.1 : tan;

exp Primitive

Base-e exponential can be expressed as exp in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{expf}(x(t))\)

Example

process = 0.1 : exp;

log Primitive

Base-e logarithm can be expressed as log in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{logf}(x(t))\)

Example

process = 0.1 : log;

log10 Primitive

Base-10 logarithm can be expressed as log10 in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{log10}(x(t))\)

Example

process = 0.1 : log10;

pow Primitive

Power can be expressed as pow in Faust.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{powf}(x_{1}(t),x_{2}(t))\)

Example

process = 2,4 : pow;

sqrt Primitive

Square root can be expressed as sqrt in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{sqrtf}(x(t))\)

Example

process = 4 : sqrt;

abs Primitive

Absolute value can be expressed as abs in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{abs}(x(t))\) (int) or
    \(y(t)=\mathrm{fabsf}(x(t))\) (float)

Example

process = -0.5 : abs;

min Primitive

Minimum can be expressed as min in Faust.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{min}(x_{1}(t),x_{2}(t))\)

Example

process = -0.5,0.2 : min;

max Primitive

Maximum can be expressed as max in Faust.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{max}(x_{1}(t),x_{2}(t))\)

Example

process = -0.5,0.2 : max;

fmod Primitive

Float modulo can be expressed as fmod in Faust.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{fmodf}(x_{1}(t),x_{2}(t))\)

Example

process = 5.3,2 : fmod;

remainder Primitive

Float remainder can be expressed as remainder in Faust.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{remainderf}(x_{1}(t),x_{2}(t))\)

Example

process = 5.3,2 : remainder;

floor Primitive

Largest int can be expressed as floor in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(\leq\): \(y(t)=\mathrm{floorf}(x(t))\)

Example

process = 3.6 : floor;

ceil Primitive

Smallest int can be expressed as ceil in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(\geq\): \(y(t)=\mathrm{ceilf}(x(t))\)

Example

process = 3.6 : ceil;

rint Primitive

Closest int can be expressed as rint in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=\mathrm{rintf}(x(t))\)

Example

process = 3.6 : rint;

Delay Primitives and Modifiers

Faust hosts various modifiers and primitives to define one sample or integer delay of arbitrary length. They are presented in this section.

mem Primitive

A 1 sample delay can be expressed as mem in Faust.

  • Type: \(\mathbb{S}^{1}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t+1)=x(t),y(0)=0\)

Example

process = mem;

Note that this is equivalent to process = _' (see ' Modifier) and process = @(1) (see @ Primitive)

' Modifier

' can be used to apply a 1 sample delay to a signal in Faust. It can be seen as syntactic sugar to the mem primitive. ' is very convenient when implementing filters and can help significantly decrease the size of the Faust code.

Example

process = _';

@ Primitive

An integer delay of N samples can be expressed as @(N) in Faust. Note that N can be dynamic but that its range must be bounded. This can be done by using a UI primitive (see example below) allowing for the definition of a range such as hslider, vslider, or nentry.

Note that floating point delay is also available in Faust by the mean of various fractional delay implementations available in the Faust standard libraries.

  • Type: \(\mathbb{S}^{2}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t+x_{2}(t))=x_{1}(t), y(t<x_{2}(t))=0\)

Usage

_ : @(N) : _

Where:

  • N: the length of the delay as a number of samples

Example: Static N Samples Delay

N = 10;
process = @(N);

Example: Dynamic N Samples Delay

N = hslider("N",10,1,10,1);
process = @(N);

Table Primitives

TODO

rdtable Primitive

The rdtable primitive can be used to read through a read-only (pre-defined before compilation) table. The table can either be implemented using a function controlled by a timer (such as ba.time) as demonstrated in the first example, or by using the waveform primitive (as shown in the second example). The idea is that the table is parsed during the initialization step and before audio computation begins.

  • Type: \(\mathbb{S}^{3}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(y(t)=T[r(t)]\)

Usage

rdtable(n,s,r) : _

Where:

  • n: the table size
  • s: the table
  • r: the read index (an int between 0 and n)

Example: Basic Triangle Wave Oscillator Using the waveform Primitive

In this example, a basic (and dirty) triangle wave-table is defined using the waveform. It is then used with the rdtable primitive and a phasor to implement a triangle wave oscillator. Note that the output of

import("stdfaust.lib");
triangleWave = waveform{0,0.5,1,0.5,0,-0.5,-1,-.5};
triangleOsc(f) = triangleWave,int(os.phasor(8,f)) : rdtable;
f = hslider("freq",440,50,2000,0.01);
process = triangleOsc(f);

Example: Basic Triangle Wave Oscillator Using the waveform Primitive

In this example, a sine table is implemented using the sin primitive and a timer (ba.time). The timer parses the sin function during the initialization step of the Faust program. It is then used with rdtable to implement a sine wave oscillator.

import("stdfaust.lib");
sineWave(tablesize) = float(ba.time)*(2.0*ma.PI)/float(tablesize) : sin;
tableSize = 1 << 16;
triangleOsc(f) = tableSize,sineWave(tableSize),int(os.phasor(tableSize,f)) : rdtable;
f = hslider("freq",440,50,2000,0.01);
process = triangleOsc(f);

rwtable Primitive

The rwtable primitive can be used to implement a read/write table. It takes an audio input that can be written in the table using a record index (i.e., w below) and read using a read index (i.e., r below).

  • Type: \(\mathbb{S}^{5}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(T[w(t)]=c(t); y(t)=T[r(t)]\)

Usage

_ : rwtable(n,s,w,_,r) : _

Where:

  • n: the table size
  • s: the table
  • w: the write index (an int between 0 and n)
  • r: the read index (an int between 0 and n)

Note that the fourth argument of rwtable corresponds to the input of the table.

Example: Simple Looper

In this example, an input signal is written in the table when record is true (equal to 1). The read index is constantly updated to loop through the table. The table size is set to 48000, which corresponds to one second if the sampling rate is 48000 KHz.

import("stdfaust.lib");
tableSize = 48000;
recIndex = (+(1) : %(tableSize)) ~ *(record);
readIndex = readSpeed/float(ma.SR) : (+ : ma.decimal) ~ _ : *(float(tableSize)) : int;
readSpeed = hslider("[0]Read Speed",1,0.001,10,0.01);
record = button("[1]Record") : int;
looper = rwtable(tableSize,0.0,recIndex,_,readIndex);
process = looper;

Selector Primitives

Selector primitives can be used to create conditions in Faust and to implement switches to choose between several signals. Note that selector primitives optimize the code generated by the Faust compiler by only computing the selected signal.

select2 Primitives

The select2 primitive is a “two-ways selector” that can be used to select between 2 signals.

  • Type: \(\mathbb{S}^{3}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(T[]=\{x_{0}(t),x_{1}(t)\}; y(t)=T[s(t)]\)

Usage

_,_ : select2(s) : _,_

Where:

  • s: the selector (0 for the first signal, 1 for the second one)

Example: Signal Selector

The following example allows the user to choose between a sine and a sawtooth wave oscillator.

import("stdfaust.lib");
s = nentry("Selector",0,0,1,1);
sig = os.osc(440),os.sawtooth(440) : select2(s);
process = sig;

Note that select2 could be easily implemented from scratch in Faust using Boolean primitives:

import("stdfaust.lib");
s = nentry("Selector",0,0,1,1);
mySelect2(s) = *(s==0),*(s==1) :> _;
sig = os.osc(440),os.sawtooth(440) : mySelect2(s);
process = sig;

While the behavior of this last solution is identical to the first one, the generated code will be less optimized as the sine and the sawtooth waves will both be computed all the time.

select3 Primitives

The select3 primitive is a “three-ways selector” that can be used to select between 3 signals.

  • Type: \(\mathbb{S}^{4}\rightarrow\mathbb{S}^{1}\)
  • Mathematical Description: \(T[]=\{x_{0}(t),x_{1}(t),x_{2}(t)\}; y(t)=T[s(t)]\)

Usage

_,_,_ : select3(s) : _,_,_

Where:

  • s: the selector (0 for the first signal, 1 for the second one, 2 for the third one)

Example: Signal Selector

The following example allows the user to choose between a sine, a sawtooth and a triangle wave oscillator.

import("stdfaust.lib");
s = nentry("Selector",0,0,1,1);
sig = os.osc(440),os.sawtooth(440),os.triangle(440) : select3(s);
process = sig;

Note that select3 could be easily implemented from scratch in Faust using Boolean primitives:

import("stdfaust.lib");
s = nentry("Selector",0,0,2,1);
mySelect3(s) = *(s==0),*(s==1),*(s==2) :> _;
sig = os.osc(440),os.sawtooth(440),os.triangle(440) : mySelect3(s);
process = sig;

While the behavior of this last solution is identical to the first one, the generated code will be less optimized as the sine, the sawtooth and the triangle waves will all be computed all the time.

User Interface Primitives and Configuration

Faust user interface widgets/primitives allow for an abstract description of a user interface from within the Faust code. This description is independent from any GUI toolkits/frameworks and is purely abstract. Widgets can be discrete (e.g., button, checkbox, etc.), continuous (e.g., hslider, vslider, nentry), and organizational (e.g., vgroup, hgroup).

Discrete and continuous elements are signal generators. For example, a button produces a signal which is 1 when the button is pressed and 0 otherwise:

These signals can be freely combined with other audio signals. In fact, the following code is perfectly valid and will generate sound:

process = button("DC");

Each primitive implements a specific UI element, but their appearance can also be completely modified using metadata (a little bit like HTML and CSS in the web). Therefore, hslider, vslider, and nentry) can for example be turned into a knob, a dropdown menu, etc. This concept is further developed in the section on UI metadata.

Continuous UI elements (i.e., hslider, vslider, and nentry) must all declare a range for the parameter they’re controlling. In some cases, this range is used during compilation to allocate memory and will impact the generated code. For example, in the case of:

process = @(hslider("N",1,1,10,1));

a buffer of 10 samples will be allocated for the delay implemented with the @ primitive while 20 samples will be allocated in the following example:

process = @(hslider("N",1,1,20,1));

button Primitive

The button primitive implements a button.

Usage

button("label") : _

Where:

  • label: the label (expressed as a string) of the element in the interface

Example: Trigger

import("stdfaust.lib");
process = no.noise*button("gate");

checkbox Primitive

The checkbox primitive implements a checkbox/toggle.

Usage

checkbox("label") : _

Where:

  • label: the label (expressed as a string) of the element in the interface

Example: Trigger

import("stdfaust.lib");
process = no.noise*checkbox("gate");

hslider Primitive

The hslider primitive implements a horizontal slider.

Usage

hslider("label",init,min,max,step) : _

Where:

  • label: the label (expressed as a string) of the element in the interface
  • init: the initial value of the slider
  • min: the minimum value of the slider
  • max: the maximum value of the slider
  • step: the precision (step) of the slider (1 to count 1 by 1, 0.1 to count 0.1 by 0.1, etc.)

Example: Gain Control

gain = hslider("gain",0,0,1,0.01);
process = *(gain);

vslider Primitive

The vslider primitive implements a vertical slider.

Usage

vslider("label",init,min,max,step) : _

Where:

  • label: the label (expressed as a string) of the element in the interface
  • init: the initial value of the slider
  • min: the minimum value of the slider
  • max: the maximum value of the slider
  • step: the precision (step) of the slider (1 to count 1 by 1, 0.1 to count 0.1 by 0.1, etc.)

Example

gain = vslider("gain",0,0,1,0.01);
process = *(gain);

nentry Primitive

The nentry primitive implements a “numerical entry”.

Usage

nentry("label",init,min,max,step) : _

Where:

  • label: the label (expressed as a string) of the element in the interface
  • init: the initial value of the numerical entry
  • min: the minimum value of the numerical entry
  • max: the maximum value of the numerical entry
  • step: the precision (step) of the numerical entry (1 to count 1 by 1, 0.1 to count 0.1 by 0.1, etc.)

Example

gain = nentry("gain",0,0,1,0.01);
process = *(gain);

hgroup Primitive

The hgroup primitive implements a horizontal group. A group contains other UI elements that can also be groups. hgroup is not a signal processor per se and is just a way to label/delimitate part of a Faust code.

Usage

hgroup("label",x)

Where:

  • label: the label (expressed as a string) of the element in the interface
  • x: the encapsulated/labeled Faust code

Example

In the following example, the 2 UI elements controlling an oscillator are encapsulated in a group.

import("stdfaust.lib");
freq = vslider("freq",440,50,1000,0.1);
gain = vslider("gain",0,0,1,0.01);
process = hgroup("Oscillator",os.sawtooth(freq)*gain);

Note that the Oscillator group can be placed in a function in case we’d like to add elements to it multiple times.

import("stdfaust.lib");
oscGroup(x) = hgroup("Oscillator",x);
freq = oscGroup(vslider("freq",440,50,1000,0.1));
gain = oscGroup(vslider("gain",0,0,1,0.01));
process = os.sawtooth(freq)*gain;

vgroup Primitive

The vgroup primitive implements a vertical group. A group contains other UI elements that can also be groups. vgroup is not a signal processor per se and is just a way to label/delimitate part of a Faust code.

Usage

vgroup("label",x)

Where:

  • label: the label (expressed as a string) of the element in the interface
  • x: the encapsulated/labeled Faust code

Example

In the following example, the 2 UI elements controlling an oscillator are encapsulated in a group.

import("stdfaust.lib");
freq = hslider("freq",440,50,1000,0.1);
gain = hslider("gain",0,0,1,0.01);
process = vgroup("Oscillator",os.sawtooth(freq)*gain);

Note that the Oscillator group can be placed in a function in case we’d like to add elements to it multiple times.

import("stdfaust.lib");
oscGroup(x) = vgroup("Oscillator",x);
freq = oscGroup(hslider("freq",440,50,1000,0.1));
gain = oscGroup(hslider("gain",0,0,1,0.01));
process = os.sawtooth(freq)*gain;

tgroup Primitive

The tgroup primitive implements a “tab group.” Tab groups can be used to group UI elements in tabs in the interface. A group contains other UI elements that can also be groups. tgroup is not a signal processor per se and is just a way to label/delimitate part of a Faust code.

Usage

tgroup("label",x)

Where:

  • label: the label (expressed as a string) of the element in the interface
  • x: the encapsulated/labeled Faust code

Example

In the following example, the 2 UI elements controlling an oscillator are encapsulated in a group.

import("stdfaust.lib");
freq = hslider("freq",440,50,1000,0.1);
gain = hslider("gain",0,0,1,0.01);
process = tgroup("Oscillator",os.sawtooth(freq)*gain);

Note that the Oscillator group can be placed in a function in case we’d like to add elements to it multiple times.

import("stdfaust.lib");
oscGroup(x) = tgroup("Oscillator",x);
freq = oscGroup(hslider("freq",440,50,1000,0.1));
gain = oscGroup(hslider("gain",0,0,1,0.01));
process = os.sawtooth(freq)*gain;

vbargraph Primitive

The vbargraph primitive implements a vertical bar-graph (typically a meter displaying the level of a signal).

Usage

vbargraph takes an input signal and outputs it while making it available to the UI.

_ : vbargraph("label",min,max) : _

Where:

  • min: the minimum value of the signal in the interface
  • max: the maximum value of the signal in the interface

Example: Simple VU Meter

A simple VU meter can be implemented using the vbargraph primitive:

import("stdfaust.lib");
process = _ <: attach(_,abs : ba.linear2db : vbargraph("Level",-60,0));

Note the use of the attach primitive here that forces the compilation of the vbargraph without using its output signal (see section on the attach primitive).

hbargraph Primitive

The hbargraph primitive implements a horizontal bar-graph (typically a meter displaying the level of a signal).

Usage

hbargraph takes an input signal and outputs it while making it available to the UI.

_ : hbargraph("label",min,max) : _

Where:

  • min: the minimum value of the signal in the interface
  • max: the maximum value of the signal in the interface

Example: Simple VU Meter

A simple VU meter can be implemented using the hbargraph primitive:

import("stdfaust.lib");
process = _ <: attach(_,abs : ba.linear2db : hbargraph("Level",-60,0));

Note the use of the attach primitive here that forces the compilation of the hbargraph without using its output signal (see section on the attach primitive).

attach Primitive

The attach primitive takes two input signals and produces one output signal which is a copy of the first input. The role of attach is to force its second input signal to be compiled with the first one. From a mathematical standpoint attach(x,y) is equivalent to 1*x+0*y, which is in turn equivalent to x, but it tells the compiler not to optimize-out y.

To illustrate this role, let’s say that we want to develop a mixer application with a vumeter for each input signals. Such vumeters can be easily coded in Faust using an envelope detector connected to a bargraph. The problem is that the signal of the envelope generators has no role in the output signals. Using attach(x,vumeter(x)) one can tell the compiler that when x is compiled vumeter(x) should also be compiled.

The examples in the hbargraph Primitive and the vbargraph Primitive illustrate well the use of attach.

Variable Parts of a Label

Labels can contain variable parts. These are indicated with the sign % followed by the name of a variable. During compilation each label is processed in order to replace the variable parts by the value of the variable. For example:

process = par(i,8,hslider("Voice %i", 0.9, 0, 1, 0.01));

creates 8 sliders in parallel with different names while par(i,8,hslider("Voice", 0.9, 0, 1, 0.01)) would have created only one slider and duplicated its output 8 times.

The variable part can have an optional format digit. For example "Voice %2i" would indicate to use two digit when inserting the value of i in the string.

An escape mechanism is provided. If the sign % is followed by itself, it will be included in the resulting string. For example "feedback (%%)" will result in "feedback (%)".

Labels as Pathnames

Thanks to horizontal, vertical, and tabs groups, user interfaces have a hierarchical structure analog to a hierarchical file system. Each widget has an associated path name obtained by concatenating the labels of all its surrounding groups with its own label.

In the following example :

hgroup("Foo",
    ...
    vgroup("Faa", 
        ...
        hslider("volume",...)
        ...
    )
    ...
)

the volume slider has pathname /h:Foo/v:Faa/volume.

In order to give more flexibility to the design of user interfaces, it is possible to explicitly specify the absolute or relative pathname of a widget directly in its label.

Elements of a path are separated using /. Group types are defined with the following identifiers:

Group Type Group Identifier
hgroup h:
vgroup v:
tgroup t:

Hence, the example presented in the section on the hgroup primitive can be rewritten as:

import("stdfaust.lib");
freq = vslider("h:Oscillator/freq",440,50,1000,0.1);
gain = vslider("h:Oscillator/gain",0,0,1,0.01);
process = os.sawtooth(freq)*gain;

which will be reflected in C++ as:

virtual void buildUserInterface(UI* ui_interface) {
  ui_interface->openHorizontalBox("Oscillator");
  ui_interface->addVerticalSlider("freq", &fVslider1, 440.0f, 50.0f, 1000.0f, 0.100000001f);
  ui_interface->addVerticalSlider("gain", &fVslider0, 0.0f, 0.0f, 1.0f, 0.00999999978f);
  ui_interface->closeBox();
}

Note that path names are inherent to the use of tools gravitating around Faust such as OSC control or faust2api. In the case of faust2api, since no user interface is actually generated, UI elements just become a way to declare parameters of a Faust object. Therefore, there’s no distinction between nentry, hslider, vslider, etc.

Smoothing

Despite the fact that the signal generated by user interface elements can be used in Faust with any other signals, UI elements run at a slower rate than the audio rate. This might be a source of clicking if the value of the corresponding parameter is modified while the program is running. This behavior is also amplified by the low resolution of signals generated by UI elements (as opposed to actual audio signals). For example, changing the value of the freq or gain parameters of the following code will likely create clicks (in the case of gain) or abrupt jumps (in the case of freq) in the signal:

import("stdfaust.lib");
freq = hslider("freq",440,50,1000,0.1);
gain = hslider("gain",0,0,1,0.01);
process = os.osc(freq)*gain;

This problem can be easily solved in Faust by using the si.smoo function which implements an exponential smoothing by a unit-dc-gain one-pole lowpass with a pole at 0.999 (si.smoo is just sugar for si.smooth(0.999)). Therefore, the previous example can be rewritten as:

import("stdfaust.lib");
freq = hslider("freq",440,50,1000,0.1) : si.smoo;
gain = hslider("gain",0,0,1,0.01) : si.smoo;
process = os.osc(freq)*gain;

Beware that each si.smoo that you place in your code will add some extra computation so they should be used precociously.

UI elements provide a convenient entry point to the DSP process in the code generated by the Faust compiler (e.g., C++, etc.). For example, the Faust program:

import("stdfaust.lib");
freq = hslider("freq",440,50,1000,0.1);
process = os.osc(freq);

will have the corresponding buildUserInterface method in C++:

virtual void buildUserInterface(UI* ui_interface) {
  ui_interface->openVerticalBox("osc");
  ui_interface->addHorizontalSlider("freq", &fHslider0, 440.0f, 50.0f, 1000.0f, 0.100000001f);
  ui_interface->closeBox();
}

The second argument of the addHorizontalSlider method is a pointer to the variable containing the current value of the freq parameter. The value of this pointer can be updated at any point to change the frequency of the corresponding oscillator.

UI Label Metadata

Widget labels can contain metadata enclosed in square brackets. These metadata associate a key with a value and are used to provide additional information to the architecture file. They are typically used to improve the look and feel of the user interface, configure OSC and accelerometer control/mapping, etc. Since the format of the value associated to a key is relatively open, metadata constitute a flexible way for programmers to add features to the language.

The Faust code:

process = *(hslider("foo[key1: val 1][key2: val 2]",0,0,1,0.1));

will produce the corresponding C++ code:

class mydsp : public dsp {
  ...
  virtual void buildUserInterface(UI* ui_interface) {
    ui_interface->openVerticalBox("tst");
    ui_interface->declare(&fHslider0, "key1", "val 1");
    ui_interface->declare(&fHslider0, "key2", "val 2");
    ui_interface->addHorizontalSlider("foo", &fHslider0, 0.0f, 0.0f, 1.0f, 0.100000001f);
    ui_interface->closeBox();
  }
  ...
};

All metadata are removed from the label by the compiler and transformed in calls to the UI::declare() method. All these UI::declare() calls will always take place before the UI::AddSomething() call that creates the User Interface element. This allows the UI::AddSomething() method to make full use of the available metadata.

Metadata are architecture-specific: it is up to the architecture file to decide what to do with it. While some metadata will work with most architectures (e.g., accelerometer and OSC configuration, etc.), others might be more specific. Some of them are presented in the following sections.

Ordering UI Elements

The order of UI declarations in a Faust code doesn’t necessarily reflect the actual order of the UI elements in the corresponding interface. Therefore, UI elements can be ordered by placing a metadata before the declaration of the name of the UI element in the label. For example, in the following declaration:

gain = vslider("h:Oscillator/[1]gain",0,0,1,0.01);
freq = vslider("h:Oscillator/[0]freq",440,50,1000,0.1);

the freq parameter will be placed before gain despite the fact that gain is declared first.

This system can be used to order groups as well. Ordering will be carried out on elements at the same level. For example:

import("stdfaust.lib");
freqS = vslider("h:Oscillators/h:[0]Sawtooth/[0]freq",440,50,1000,0.1);
gainS = vslider("h:Oscillators/h:[0]Sawtooth/[1]gain",0,0,1,0.01);
freqT = vslider("h:Oscillators/h:[1]Triangle/[0]freq",440,50,1000,0.1);
gainT = vslider("h:Oscillators/h:[1]Triangle/[1]gain",0,0,1,0.01);
process = os.sawtooth(freqS)*gainS + os.triangle(freqT)*gainT;

Note that this could also be written as:

import("stdfaust.lib");
freqS = vslider("[0]freq",440,50,1000,0.1);
gainS = vslider("[1]gain",0,0,1,0.01);
freqT = vslider("[0]freq",440,50,1000,0.1);
gainT = vslider("[1]gain",0,0,1,0.01);
process = hgroup("Oscillators",
  hgroup("[0]Sawtooth",os.sawtooth(freqS)*gainS) + 
  hgroup("[1]Triangle",os.triangle(freqT)*gainT)
);

Global UI Metadata

Note that global user interfaces completely replacing the one defined using the standard Faust UI primitives may be declared using global metadata. This is the case of the SmartKeyboard interface for example.

In the following subsections, the standard Faust UI metadata are documented. Other types of metadata (e.g., accelerometers, OSC, etc.) are documented in the sections related to these topics.

[style:knob] Metadata

The [style:knob] metadata turns any continuous UI element (i.e., hslider, vslider, nentry) into a knob.

Example

import("stdfaust.lib");
freq = vslider("freq[style:knob]",440,50,1000,0.1);
process = os.sawtooth(freq);

[style:menu] Metadata

The [style:menu] metadata turns any continuous UI element (i.e., hslider, vslider, nentry) into a drop-down menu.

Usage

[style:menu{'Name0':value0;'Name1':value1}]

Where:

  • NameN: the name associated to valueN
  • valueN: the value associated to NameN

Example: Selector

import("stdfaust.lib");
s = vslider("Signal[style:menu{'Noise':0;'Sawtooth':1}]",0,0,1,1);
process = select2(s,no.noise,os.sawtooth(440));

[style:radio] Metadata

The [style:radio] metadata turns a hslider or a vslider into a radio-button-menu. The orientation of the menu is determined by the type of UI element (i.e., hslider for horizontal and vslider for vertical).

Usage

[style:radio{'Name0':value0;'Name1':value1}]

Where:

  • NameN: the name associated to valueN
  • valueN: the value associated to NameN

Example: Selector

import("stdfaust.lib");
s = vslider("Signal[style:radio{'Noise':0;'Sawtooth':1}]",0,0,1,1);
process = select2(s,no.noise,os.sawtooth(440));

[style:led] Metadata

The [style:led] metadata turns a vbargraph or a hbargraph into a blinking LED (with varying intensity).

Example: Level Display

import("stdfaust.lib");
process = _ <: attach(_,abs : ba.linear2db : vbargraph("Level[style:led]",-60,0));

[style:numerical] Metadata

The [style:numerical] metadata turns a vbargraph or a hbargraph into a numerical zone (thus the bargraph itself is no more displayed).

Example: Level Display

import("stdfaust.lib");
process = _ <: attach(_,abs : ba.linear2db : vbargraph("Level[style:numerical]",-60,0));

[unit:dB] Metadata

The [style:dB] metadata changes the unit of a vbargraph or a hbargraph to dB. This impacts its overall appearance by applying a rainbow color scheme, etc.

Example: Level Display

import("stdfaust.lib");
process = _ <: attach(_,abs : ba.linear2db : vbargraph("Level[style:dB]",-60,0));

[unit:xx] Metadata

The [unit:xx] metadata allows us to specify the unit of a UI element. The unit will be displayed right next to the current value of the parameter in the interface.

Usage

[unit:xx]

Where:

  • xx: the unit of the current parameter

Example

import("stdfaust.lib");
freq = vslider("freq[unit:Hz]",440,50,1000,0.1);
process = os.sawtooth(freq);

[scale:xx] Metadata

The [scale:xx] metadata allows for the specification of a scale (different than the default linear one) to the parameter in the UI. [scale:log] can be used to change to scale to logarithmic and [scale:exp] to exponential.

[tooltip:xx] Metadata

The [tooltip:xx] metadata allows for the specification of a “tooltip” when
the mouse hover a parameter in the interface. This is very convenient when implementing complex interfaces.

Usage

[tooltip:xx]

Where:

  • xx: a string to be used as a tooltip in the interface

Example

import("stdfaust.lib");
freq = vslider("freq[tooltip:The frequency of the oscillator]",440,50,1000,0.1);
process = os.sawtooth(freq);

[hidden:xx] Metadata

The [hidden:xx] metadata can be used to hide a parameter in the interface. This is convenient when controlling a parameter with a motion sensor or OSC messages and we don’t want it to be visible in the interface. This feature is commonly used when making apps for Android and iOS using faust2android or faust2ios.

Compatibility

  • iOS
  • Android

Sensors Control Metadatas

Sensors control metadata can be used to map the built-in sensors of mobile devices to some of the parameters of a Faust program.

Compatibility

These metadatas are compatible with the following Faust targets and no additional step is required for them to be taken into account when the corresponding app is generated:

Sensors control metadatas have five parameters and follow the following syntax:

[acc: a b c d e] // for accelerometer
[gyr: a b c d e] // for gyroscope

They can be used in a Faust UI parameter declaration:

parameter = nentry("UIparamName[acc: a b c d e]",def,min,max,step);

with:

  • a: the accelerometer axis (0: x, 1: y, 2: z)
  • b: the accelerometer curve (see figure below)
  • c: the minimum acceleration (m/s^2)
  • d: the center acceleration (m/s^2)
  • e: the maximum acceleration (m/s^2)
  • def: the default/init value of the parameter
  • min: the minimum value of the parameter
  • max: the maximum value of the parameter
  • step: the step of the parameter (precision)

This allows for the implementation of complex linear and non-linear mappings that are summarized in this figure:

For example, controlling the gain of a synthesizer using the X axis of the accelerometer can be easily done simply by writing something like:

g = nentry("gain[acc: 0 0 -10 0 10]",0.5,0,1,0.01);

With this configuration, g = 0 when the device is standing vertically on its right side, g = 0.5 when the device is standing horizontally with screen facing up, and g = 1 when the device is standing vertically on its left side.

Finally, in this slightly more complex mapping, g = 0 when the device is tilted on its right side and the value of g increases towards 1 when the device is tilted on its left side:

g = nentry("gain[acc: 0 0 0 0 10]",0,0,1,0.01);

Complex nonlinear mappings can be implemented using this system.

Using the Faust Compiler

While the Faust compiler is available in different forms (e.g., Embedded Compiler, etc.), its most “common” one is the command line version, which can be invoked using the faust command. It translates a Faust program into code in a wide range of languages (C, O-C++, C++, Rust, JAVA, JavaScript, ASM JavaScript, LLVM, C-LLVM, FIR, and WebAssembly). The generated code can be wrapped into an optional architecture file allowing to directly produce a fully operational program.

A typical call of the Faust command line compiler is:

faust [OPTIONS] faustFile.dsp

The Faust compiler outputs C++ code by default therefore running:

faust noise.dsp 

will compile noise.dsp and output the corresponding C++ code on the standard output. The option -o allows to reroute the standard output to a file:

faust noise.dsp -o noise.cpp

The -a option allows us to wrap the generated code into an architecture file:

faust -a alsa-gtk.cpp noise.dsp 

which can either be placed in the same folder as the current Faust file (noise.dsp here) or be one of the standard Faust architectures.

To compile a Faust program into an ALSA application on Linux, the following commands can be used:

faust -a alsa-gtk.cpp noise.dsp -o noise.cpp
g++ -lpthread -lasound `pkg-config --cflags --libs gtk+-2.0` noise.cpp -o noise

Note that a wide range of faust2... compilation scripts can be used to facilitate this operation by taking a Faust file and returning the corresponding binary for your platform.

Structure of the Generated Code

A Faust DSP C++ class derives from the base dsp class defined as below (a similar structure is used for languages than C++):

class dsp {
  public:
  dsp() {}
  virtual ~dsp() {}
  
  // Returns the number of inputs of the Faust program
  virtual int getNumInputs() = 0;
  
  // Returns the number of outputs of the Faust program
  virtual int getNumOutputs() = 0;
  
  // This method can be called to retrieve the UI description of
  // the Faust program and its associated fields
  virtual void buildUserInterface(UI* ui_interface) = 0;
  
  // Returns the current sampling rate
  virtual int getSampleRate() = 0;
  
  // Init methods
  virtual void init(int samplingRate) = 0;
  virtual void instanceInit(int samplingRate) = 0;
  virtual void instanceConstants(int samplingRate) = 0;
  virtual void instanceResetUserInterface() = 0;
  virtual void instanceClear() = 0;
  
  // Returns a clone of the instance
  virtual dsp* clone() = 0;
  
  // Retrieve the global metadata of the Faust program
  virtual void metadata(Meta* m) = 0;
  
  // Compute one audio frame
  virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) = 0;
  
  // Compute a time-stamped audio frame
  virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs,
     FAUSTFLOAT** outputs) { compute(count, inputs, outputs); }
};

Methods are filled by the compiler with the actual code. In the case of noise.dsp:

class mydsp : public dsp {
  private:
  int iRec0[2];
  int fSamplingFreq;
  
  public:
  void metadata(Meta* m) { 
    m->declare("author", "GRAME");
    m->declare("filename", "noise");
    m->declare("name", "Noise");
    m->declare("noises.lib/name", "Faust Noise Generator Library");
    m->declare("noises.lib/version", "0.0");
  }
  virtual int getNumInputs() {
    return 0;
  }
  virtual int getNumOutputs() {
    return 1;
  }
  virtual int getInputRate(int channel) {
    int rate;
    switch (channel) {
      default: {
        rate = -1;
        break;
      }
    }
    return rate;
  }
  virtual int getOutputRate(int channel) {
    int rate;
    switch (channel) {
      case 0: {
        rate = 1;
        break;
      }
      default: {
        rate = -1;
        break;
      }
    }
    return rate;
  }
  static void classInit(int samplingFreq) {}
  virtual void instanceConstants(int samplingFreq) {
    fSamplingFreq = samplingFreq;
  }
  virtual void instanceResetUserInterface() {}
  virtual void instanceClear() {
    for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) {
      iRec0[l0] = 0;
    }
  }
  virtual void init(int samplingFreq) {
    classInit(samplingFreq);
    instanceInit(samplingFreq);
  }
  virtual void instanceInit(int samplingFreq) {
    instanceConstants(samplingFreq);
    instanceResetUserInterface();
    instanceClear();
  }
  virtual mydsp* clone() {
    return new mydsp();
  }
  virtual int getSampleRate() {
    return fSamplingFreq;
  }
  virtual void buildUserInterface(UI* ui_interface) {
    ui_interface->openVerticalBox("Noise");
    ui_interface->closeBox();
  }
  virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
    FAUSTFLOAT* output0 = outputs[0];
    for (int i = 0; (i < count); i = (i + 1)) {
      iRec0[0] = ((1103515245 * iRec0[1]) + 12345);
      output0[i] = FAUSTFLOAT((4.65661287e-10f * float(iRec0[0])));
      iRec0[1] = iRec0[0];
    }
  }
};

Several fine-grained initialization methods are available. The instanceInit method calls several additional initialization methods. The instanceConstants method sets the instance constant state. The instanceClear method resets the instance dynamic state (delay lines…). The instanceResetUserInterface method resets all control value to their default state. All of those methods can be used individually on an allocated instance to reset part of its state.

The init method combines class static state and instance initialization.

When using a single instance, calling init is the simplest way to do “what is needed.” When using several instances, all of them can be initialized using instanceInit, with a single call to classInit to initialize the static shared state.

Compilation Options

Short Long Description
-h --help print the help message
-v --version print the compiler version information
-d --details print the compilation details
-tg --task-graph Draw a graph of all internal computational loops as .dot (graphviz) file
-sg --signal-graph Draw a graph of all internal computational loops as .dot (graphviz) file
-ps --postscript Generate the block-diagram postscript file
-svg --svg Generate the block-diagram svg file
-mdoc --mathdoc Generate the full mathematical documentation of a Faust program
-mdlang <l> --mathdoc-lang Choose the language of the mathematical description (l = en, fr, etc.)
-stripmdoc --strip-mdoc-tags Remove documentation tags when printing Faust listings
-sd --simplify-diagrams Try to further simplify block diagrams before drawing them
-f <n> --fold <n> Max complexity of svg diagrams before splitting into several files (default 25 elements)
-mns <n> --max-name-size <n> Max character size used in svg diagrams labels
-sn --simple-names Use simple names (without arguments) for block-diagram generation (default max size: 40 chars)
-xml -xml Generate an XML description file
-exp10 --generate-exp10 Function call instead of pow(10) function
-json -json Generate a JSON description file
-blur --shadow-blur Add a blur to boxes shadows in block diagrams
-lb --left-balanced Generate left-balanced expressions in block diagrams
-mb --mid-balanced Generate mid-balanced expressions in block diagrams
-rb --right-balanced Generate right-balanced expressions in block diagrams
-lt --less-temporaries Generate less temporaries in compiling delays
-mcd <n> --max-copy-delay <n> Threshold between copy and ring buffer delays (default 16 samples)
-mem --memory Allocate static in global state using a custom memory manager
-a <file> -a Specifies a wrapper architecture file
-i --inline-architecture-files Inline architecture files in the generated code
-cn <name> --class-name <name> Specify the name of the DSP class to be used instead of mydsp
-scn <name> --super-class-name <name> Specify the name of the super class to be used instead of dsp
-pn <name> --process-name <name> Specify the name of the dsp entry-point instead of process
-t <sec> --timeout <sec> Abort compilation after <sec> seconds (default 120)
-time --compilation-time Flag to display compilation phases timing information
-o <file> -o <file> C, C++, JAVA, JavaScript, ASM JavaScript, WebAssembly, LLVM IR or FVM (interpreter) output file
-scal --scalar Generate non-vectorized code
-vec --vectorize Generate easier to vectorize code
-vs <n> --vec-size <n> Size of the vector (default 32 samples)
-lv <n> --loop-variant Loop variant when -vec [0:fastest (default), 1:simple]
-omp --openMP Generate OpenMP pragmas, activates the --vectorize option
-pl --par-loop Generate parallel loops in --openMP mode
-sch --scheduler Generate tasks and use a Work Stealing scheduler, activates the --vectorize option
-ocl --openCL Generate tasks with OpenCL (experimental)
-cuda --cuda Generate tasks with CUDA (experimental)
-dfs --deepFirstScheduling Schedule vector loops in deep first order
-g --groupTasks Group single-threaded sequential tasks together when -omp or -sch is used
-fun --funTasks Separate tasks code as separated functions (in -vec, -sch, or -omp mode)
-lang <lang> --language Generate various output formats: c, ocpp, cpp, rust, java, js, ajs, llvm, cllvm, fir, wast/wasm, interp (default cpp)
-uim --user-interface-macros Add user interface macro definitions in the output code
-single --single-precision-floats Use single precision floats for internal computations (default)
-double --double-precision-floats Use --double-precision-floats for internal computations
-quad --quad-precision-floats Use quad precision floats for internal computations
-es 1|0 --enable-semantics 1|0 Use --enable-semantics 1|0 when 1, and simple multiplication otherwise
-flist --file-list Use –file-list used to eval process
-norm --normalized-form Prints signals in normalized form and exits
-A <dir> --architecture-dir <dir> Add the directory <dir> to the architecture search path
-I <dir> --import-dir <dir> Add the directory <dir> to the import search path
-L <file> --library <file> Link with the LLVM module <file>
-O <dir> --output-dir <dir> Specify the relative directory of the generated output code, and the output directory of additional generated files (SVG, XML, etc.)
-e --export-dsp Export expanded DSP (all included libraries)
-inpl --in-place generates Code working when input and output buffers are the same (in scalar mode only)
-inj <f> --inject <f> inject source file <f> into architecture file instead of compile a dsp file
-ftz --flush-to-zero Flush to zero the code added to recursive signals [0:no (default), 1:fabs based, 2:mask based (fastest)]
-fm <file> --fast-math <file> Uses optimized versions of mathematical functions implemented in <file>, take the /faust/dsp/fastmath.cpp file if ‘def’ is used

Controlling Code Generation

Several options of the Faust compiler allow to control the generated C++ code. By default computation is done sample by sample in a single loop. But the compiler can also generate vector and parallel code.

Vector Code Generation

Modern C++ compilers are able to do autovectorization, that is to use SIMD instructions to speedup the code. These instructions can typically operate in parallel on short vectors of 4 simple precision floating point numbers, leading to a theoretical speedup of \(\times4\).

Autovectorization of C/C++ programs is a difficult task. Current compilers are very sensitive to the way the code is arranged. In particular, complex loops can prevent autovectorization. The goal of the vector code generation is to rearrange the C++ code in a way that facilitates the autovectorization job of the C++ compiler. Instead of generating a single sample computation loop, it splits the computation into several simpler loops that communicates by vectors.

The vector code generation is activated by passing the --vectorize (or -vec) option to the Faust compiler. Two additional options are available: --vec-size <n> controls the size of the vector (by default 32 samples) and --loop-variant 0/1 gives some additional control on the loops.

To illustrate the difference between scalar code and vector code, let’s take the computation of the RMS (Root Mean Square) value of a signal. Here is the Faust code that computes the Root Mean Square of a sliding window of 1000 samples:

// Root Mean Square of n consecutive samples
RMS(n) = square : mean(n) : sqrt;

// Square of a signal
square(x) = x * x;

// Mean of n consecutive samples of a signal (uses fixpoint to avoid the 
// accumulation of rounding errors) 
mean(n) = float2fix : integrate(n) : fix2float : /(n); 

// Sliding sum of n consecutive samples
integrate(n,x) = x - x@n : +~_;

// Convertion between float and fix point
float2fix(x) = int(x*(1<<20));      
fix2float(x) = float(x)/(1<<20);    

// Root Mean Square of 1000 consecutive samples
process = RMS(1000);

The corresponding compute() method generated in scalar mode is the following:

virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
  FAUSTFLOAT* input0 = inputs[0];
  FAUSTFLOAT* output0 = outputs[0];
  for (int i = 0; (i < count); i = (i + 1)) {
    int iTemp0 = int((1048576.0f * mydsp_faustpower2_f(float(input0[i]))));
    iVec0[(IOTA & 1023)] = iTemp0;
    iRec0[0] = ((iRec0[1] + iTemp0) - iVec0[((IOTA - 1000) & 1023)]);
    output0[i] = FAUSTFLOAT(std::sqrt((9.53674362e-10f * float(iRec0[0]))));
    IOTA = (IOTA + 1);
    iRec0[1] = iRec0[0];
  }
}

The -vec option leads to the following reorganization of the code:

virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
  fInput0_ptr = inputs[0];
  FAUSTFLOAT* fInput0 = 0;
  fOutput0_ptr = outputs[0];
  FAUSTFLOAT* fOutput0 = 0;
  int iRec0_tmp[36];
  int* iRec0 = &iRec0_tmp[4];
  int fullcount = count;
  int index = 0;
  /* Main loop */
  for (index = 0; (index <= (fullcount - 32)); index = (index + 32)) {
    fInput0 = &fInput0_ptr[index];
    fOutput0 = &fOutput0_ptr[index];
    int count = 32;
    /* Recursive loop 0 */
    /* Pre code */
    iYec0_idx = ((iYec0_idx + iYec0_idx_save) & 2047);
    /* Compute code */
    for (int i = 0; (i < count); i = (i + 1)) {
      iYec0[((i + iYec0_idx) & 2047)] = 
      int((1048576.0f mydsp_faustpower2_f(float(fInput0[i]))));
    }
    /* Post code */
    iYec0_idx_save = count;
    /* Recursive loop 1 */
    /* Pre code */
    for (int j0 = 0; (j0 < 4); j0 = (j0 + 1)) {
      iRec0_tmp[j0] = iRec0_perm[j0];
    }
    /* Compute code */
    for (int i = 0; (i < count); i = (i + 1)) {
      iRec0[i] = ((iRec0[(i - 1)] + iYec0[((i + iYec0_idx) & 2047)]) - 
      iYec0[(((i + iYec0_idx) - 1000) & 2047)]);
    }
    /* Post code */
    for (int j = 0; (j < 4); j = (j + 1)) {
      iRec0_perm[j] = iRec0_tmp[(count + j)];
    }
    /* Vectorizable loop 2 */
    /* Compute code */
    for (int i = 0; (i < count); i = (i + 1)) {
      fOutput0[i] = FAUSTFLOAT(std::sqrt((9.53674362e-10f * float(iRec0[i]))));
    }
  }
  
  /* Remaining frames */
  if (index < fullcount) {
    fInput0 = &fInput0_ptr[index];
    fOutput0 = &fOutput0_ptr[index];
    int count = (fullcount - index);
    /* Recursive loop 0 */
    /* Pre code */
    iYec0_idx = ((iYec0_idx + iYec0_idx_save) & 2047);
    /* Compute code */
    for (int i = 0; (i < count); i = (i + 1)) {
      iYec0[((i + iYec0_idx) & 2047)] = int((1048576.0f * 
        mydsp_faustpower2_f(float(fInput0[i]))));
    }
    /* Post code */
    iYec0_idx_save = count;
    /* Recursive loop 1 */
    /* Pre code */
    for (int j0 = 0; (j0 < 4); j0 = (j0 + 1)) {
      iRec0_tmp[j0] = iRec0_perm[j0];
    }
    /* Compute code */
    for (int i = 0; (i < count); i = (i + 1)) {
      iRec0[i] = ((iRec0[(i - 1)] + iYec0[((i + iYec0_idx) & 2047)]) - 
      iYec0[(((i + iYec0_idx) - 1000) & 2047)]);
    }
    /* Post code */
    for (int j = 0; (j < 4); j = (j + 1)) {
      iRec0_perm[j] = iRec0_tmp[(count + j)];
    }
    /* Vectorizable loop 2 */
    /* Compute code */
    for (int i = 0; (i < count); i = (i + 1)) {
      fOutput0[i] = FAUSTFLOAT(std::sqrt((9.53674362e-10f * float(iRec0[i]))));
    }
  }
}

While the second version of the code is more complex, it turns out to be much easier to vectorize efficiently by the C++ compiler. With the exact same compilation options: -O3 -xHost -ftz -fno-alias -fp-model fast=2, the scalar version leads to a throughput performance of 129.144 MB/s, while the vector version achieves 359.548 MB/s, a speedup of x2.8 !

The vector code generation is built on top of the scalar code generation (see previous figure). Every time an expression needs to be compiled, the compiler checks if it requires a separate loop or not. Expressions that are shared (and are complex enough) are good candidates to be compiled in a separate loop, as well as recursive expressions and expressions used in delay lines.

The result is a directed graph in which each node is a computation loop (see figure below). This graph is stored in the class object and a topological sort is applied to it before printing the code.

Parallel Code Generation

Parallel code generation is activated by passing either the --openMP (or -omp) option or the --scheduler (or -sch) option. It implies that the -vec option as well as the parallel code generation are built on top of the vector code generation.

The OpenMP Code Generator

The --openMP (or -omp) option, when given to the Faust compiler, will insert appropriate OpenMP directives into the C++ code. OpenMP is a well established API that is used to explicitly define direct multi-threaded, shared memory parallelism. It is based on a fork-join model of parallelism (see figure above). Parallel regions are delimited by #pragma omp parallel constructs. At the entrance of a parallel region, a group of parallel threads is activated. The code within a parallel region is executed by each thread of the parallel group until the end of the region.

#pragma omp parallel
{
  // the code here is executed simultaneously by every thread of the parallel 
  // team
  ...
}

In order not to have every thread doing redundantly the exact same work, OpenMP provides specific work-sharing directives. For example #pragma omp sections allows to break the work into separate, discrete sections, each section being executed by one thread:

#pragma omp parallel
{
  #pragma omp sections
  {
    #pragma omp section
    {
      // job 1
    }
    #pragma omp section
    {
      // job 2
    }
    ...
  }
  ...
}

Adding Open MP Directives

As said before, parallel code generation is built on top of vector code generation. The graph of loops produced by the vector code generator is topologically sorted in order to detect the loops that can be computed in parallel. The first set \(S_0\) (loops \(L1\), \(L2\) and \(L3\)) contains the loops that don’t depend on any other loops, the set \(S_1\) contains the loops that only depend on loops of \(S_0\), (that is loops \(L4\) and \(L5\)), etc..

As all the loops of a given set \(S_n\) can be computed in parallel, the compiler will generate a sections construct with a section for each loop.

#pragma omp sections
{
  #pragma omp section
  for (...) {
    // Loop 1
  }
  #pragma omp section
  for (...) {
    // Loop 2
  }
  ...
}

If a given set contains only one loop, then the compiler checks to see if the loop can be parallelized (no recursive dependencies) or not. If it can be parallelized, it generates:

#pragma omp for
for (...) {
 // Loop code
}

otherwise it generates a single construct so that only one thread will execute the loop:

#pragma omp single
for (...) {
 // Loop code
}

Example of Parallel OpenMP Code

To illustrate how Faust uses the OpenMP directives, here is a very simple example, two 1-pole filters in parallel connected to an adder:

filter(c) = *(1-c) : + ~ *(c);
process = filter(0.9), filter(0.9) : +;

The corresponding compute() method obtained using the -omp option looks like this:

virtual void compute(int fullcount, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
  float fRec0_tmp[36];
  float fRec1_tmp[36];
  FAUSTFLOAT* fInput0 = 0;
  FAUSTFLOAT* fInput1 = 0;
  FAUSTFLOAT* fOutput0 = 0;
  float* fRec0 = &fRec0_tmp[4];
  float* fRec1 = &fRec1_tmp[4];
  fInput0_ptr = inputs[0];
  fInput1_ptr = inputs[1];
  fOutput0_ptr = outputs[0];
  #pragma omp parallel\
      firstprivate(fInput0, fInput1, fOutput0, fRec0, fRec1)
  {
    for (int index = 0; (index < fullcount); index = (index + 32)) {
      fInput0 = &fInput0_ptr[index];
      fInput1 = &fInput1_ptr[index];
      fOutput0 = &fOutput0_ptr[index];
      int count = min(32, (fullcount - index));
      #pragma omp sections
      {
        #pragma omp section
        {
          /* Recursive loop 0 */
          /* Pre code */
          for (int j0 = 0; (j0 < 4); j0 = (j0 + 1)) {
            fRec0_tmp[j0] = fRec0_perm[j0];
          }
          /* Compute code */
          for (int i = 0; (i < count); i = (i + 1)) {
            fRec0[i] = ((0.899999976f * fRec0[(i - 1)]) + 
            (0.100000001f * float(fInput0[i])));
          }
          /* Post code */
          for (int j = 0; (j < 4); j = (j + 1)) {
            fRec0_perm[j] = fRec0_tmp[(count + j)];
          }
        }
        #pragma omp section
        {
          /* Recursive loop 1 */
          /* Pre code */
          for (int j1 = 0; (j1 < 4); j1 = (j1 + 1)) {
            fRec1_tmp[j1] = fRec1_perm[j1];
          }
          /* Compute code */
          for (int i = 0; (i < count); i = (i + 1)) {
            fRec1[i] = ((0.899999976f * fRec1[(i - 1)]) + 
            (0.100000001f * float(fInput1[i])));
          }
          /* Post code */
          for (int j = 0; (j < 4); j = (j + 1)) {
            fRec1_perm[j] = fRec1_tmp[(count + j)];
          }
        }
      }
      #pragma omp single
      {
        /* Vectorizable loop 2 */
        /* Compute code */
        for (int i = 0; (i < count); i = (i + 1)) {
          fOutput0[i] = FAUSTFLOAT((fRec0[i] + fRec1[i]));
        }
      }
    }
  }
}

This code requires some comments:

  • The parallel construct #pragma omp parallel is the fundamental construct that starts parallel execution. The number of parallel threads is generally the number of CPU cores but it can be controlled in several ways.
  • Variables external to the parallel region are shared by default. The pragma firstprivate(fRec0,fRec1) indicates that each thread should have its private copy of fRec0 and fRec1. The reason is that accessing shared variables requires an indirection and is quite inefficient compared to private copies.
  • The top level loop for (int index = 0;...)... is executed by all threads simultaneously. The subsequent work-sharing directives inside the loop will indicate how the work must be shared between threads.
  • Please note that an implied barrier exists at the end of each work-sharing region. All threads must have executed the barrier before any of them can continue.
  • The work-sharing directive #pragma omp single indicates that this first section will be executed by only one thread (any of them).
  • The work-sharing directive #pragma omp sections indicates that each corresponding #pragma omp section, here our two filters, will be executed in parallel.
  • The loop construct #pragma omp for specifies that the iterations of the associated loop will be executed in parallel. The iterations of the loop are distributed across the parallel threads. For example, if we have two threads, the first one can compute indices between 0 and count/2 and the other one between count/2 and count.
  • Finally #pragma omp single indicates that this section will be executed by only one thread (any of them).

The Scheduler Code Generator

With the --scheduler (or -sch) option given to the Faust compiler, the computation graph is cut into separate computation loops (called “tasks”), and a “Work Stealing Scheduler” is used to activate and execute them following their dependencies. A pool of worked threads is created and each thread uses it’s own local WSQ (Work Stealing Queue) of tasks. A WSQ is a special queue with a Push operation, a “private” LIFO Pop operation and a “public” FIFO Pop operation.

Starting from a ready task, each thread follows the dependencies, possibly pushing ready sub-tasks into it’s own local WSQ. When no more tasks can be activated on a given computation path, the thread pops a task from it’s local WSQ. If the WSQ is empty, then the thread is allowed to “steal” tasks from other threads WSQ.

The local LIFO Pop operation allows better cache locality and the FIFO steal Pop “larger chuck” of work to be done. The reason for this is that many work stealing workloads are divide-and-conquer in nature, stealing one of the oldest task implicitly also steals a (potentially) large sub-tree of computations that will unfold once that piece of work is stolen and run.

Compared to the OpenMP model (-omp) the new model is worse for simple Faust programs and usually starts to behave comparable or sometimes better for “complex enough” Faust programs. In any case, since OpenMP does not behave so well with GCC compilers, and is unusable on OSX in real-time contexts, this new scheduler option has it’s own value. We plan to improve it adding a “pipelining” idea in the future.

Example of Parallel Scheduler Code

To illustrate how Faust generates the scheduler code, let’s reuse the previous example made of two 1-pole filters in parallel connected to an adder:

filter(c) = *(1-c) : + ~ *(c);
process = filter(0.9), filter(0.9) : +; 

When -sch option is used, the content of the additional architecture/scheduler.h file is inserted in the generated code. It contains code to deal with WSQ and thread management. The compute() and computeThread() methods are the following:

virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
  fInput0_ptr = inputs[0];
  fInput1_ptr = inputs[1];
  fOutput0_ptr = outputs[0];
  fCount = count;
  fIndex = 0;
  /* End task has only one input, so will be directly activated */
  /* Only initialize tasks with more than one input */
  initTask(fScheduler, 4, 2);
  /* Push ready tasks in each thread WSQ */
  initTaskList(fScheduler, -1);
  signalAll(fScheduler);
  computeThread(0);
  syncAll(fScheduler);
}
void computeThread(int num_thread) {
  int count = fCount;
  FAUSTFLOAT* fInput0 = 0;
  FAUSTFLOAT* fInput1 = 0;
  FAUSTFLOAT* fOutput0 = 0;
  int tasknum = 0;
  while ((fIndex < fCount)) {
    fInput0 = &fInput0_ptr[fIndex];
    fInput1 = &fInput1_ptr[fIndex];
    fOutput0 = &fOutput0_ptr[fIndex];
    count = min(32, (fCount - fIndex));
    switch (tasknum) {
      case 0: {
        /* Work Stealing task */
        tasknum = getNextTask(fScheduler, num_thread);
        break;
      }
      case 1: {
        /* Last task */
        fIndex = (fIndex + 32);
        if (fIndex < fCount) {
          /* End task has only one input, so will be directly activated */
          /* Only initialize tasks with more than one input */
          initTask(fScheduler, 4, 2);
          /* Push ready tasks in 'num_thread' WSQ */
          initTaskList(fScheduler, num_thread);
        }
        tasknum = 0;
        break;
      }
      case 2: {
        /* Recursive loop 2 */
        /* Pre code */
        for (int j0 = 0; (j0 < 4); j0 = (j0 + 1)) {
          fRec0_tmp[j0] = fRec0_perm[j0];
        }
        /* Compute code */
        for (int i = 0; (i < count); i = (i + 1)) {
          fRec0[i] = ((0.899999976f * fRec0[(i - 1)]) + 
          (0.100000001f * float(fInput0[i])));
        }
        /* Post code */
        for (int j = 0; (j < 4); j = (j + 1)) {
          fRec0_perm[j] = fRec0_tmp[(count + j)];
        }
        /* One output only */
        activateOneOutputTask(fScheduler, num_thread, 4, &tasknum);
        break;
      }
      case 3: {
        /* Recursive loop 3 */
        /* Pre code */
        for (int j1 = 0; (j1 < 4); j1 = (j1 + 1)) {
          fRec1_tmp[j1] = fRec1_perm[j1];
        }
        /* Compute code */
        for (int i = 0; (i < count); i = (i + 1)) {
          fRec1[i] = ((0.899999976f * fRec1[(i - 1)]) + 
          (0.100000001f * float(fInput1[i])));
        }
        /* Post code */
        for (int j = 0; (j < 4); j = (j + 1)) {
          fRec1_perm[j] = fRec1_tmp[(count + j)];
        }
        /* One output only */
        activateOneOutputTask(fScheduler, num_thread, 4, &tasknum);
        break;
      }
      case 4: {
        /* Vectorizable loop 4 */
        /* Compute code */
        for (int i = 0; (i < count); i = (i + 1)) {
          fOutput0[i] = FAUSTFLOAT((fRec0[i] + fRec1[i]));
        }
        tasknum = 1;
        break;
      }
    }
  }
}

Embedding the Faust Compiler Using libfaust

The combination of the awesome LLVM technology and libfaust (the library version of the Faust compiler) allows developers to compile and execute Faust DSP programs on the fly at full speed and without making compromises. In this section, we demonstrate how the Faust dynamic compilation chain can be used to embed the Faust compiler technology directly in applications or plug-ins.

Dynamic Compilation Chain

The Faust compiler uses an intermediate FIR representation (Faust Imperative Representation), which can be translated to several output languages. The FIR language describes the computation performed on the samples in a generic manner. It contains primitives to read and write variables and arrays, do arithmetic operations, and define the necessary control structures (for and while loops, if structure, etc.).

To generate various output languages, several backends have been developed: for C, C++, Java, JavaScript, asm.js, LLVM IR, webassemble, etc. The native LLVM based compilation chain is particularly interesting: it provides direct compilation of a DSP source into executable code in memory, bypassing the external compiler requirement.

LLVM

LLVM (formerly Low Level Virtual Machine) is a compiler infrastructure, designed for compile-time, link-time, and run-time optimization of programs written in arbitrary programming languages. Executable code is produced dynamically using a Just In Time compiler from a specific code representation, called LLVM IR. Clang, the LLVM native C/C++/Objective-C compiler is a front-end for the LLVM Compiler. It can, for instance, convert a C or C++ source file into LLVM IR code. Domain-specific languages like Faust can easily target the LLVM IR. This has been done by developing an LLVM IR backend in the Faust compiler.

Compiling in Memory

The complete chain goes from the Faust DSP source code, compiled in LLVM IR using the LLVM backend, to finally produce the executable code using the LLVM JIT. All steps take place in memory, getting rid of the classical file-based approaches. Pointers to executable functions can be retrieved from the resulting LLVM module and the code directly called with the appropriate parameters.

The Faust compiler has been packaged as an embeddable library called libfaust, published with an associated API. Given a Faust source code (as a file or a string), calling the createDSPFactoryXXX function runs the compilation chain (Faust + LLVM JIT) and generates the prototype of the class, as a llvm_dsp_factory pointer.

Note that the library keeps an internal cache of all allocated factories so that the compilation of the same DSP code – that is the same source code and the same set of normalized (sorted in a canonical order) compilation options – will return the same (reference counted) factory pointer.

deleteDSPFactory has to be explicitly used to properly decrement the reference counter when the factory is not needed anymore. You can get a unique SHA1 key of the created factory using its getSHAKey method.

Next, the createDSPInstance function (corresponding to the new className of C++) instantiates a llvm_dsp pointer to be used through its interface, connected to the audio chain and controller interfaces. When finished, delete can be used to destroy the dsp instance.

Since llvm_dsp is a subclass of the dsp base class, an object of this type can be used with all the available audio and UI classes. In essence, this is like reusing all architecture files already developed for the static C++ class compilation scheme like OSCUI, httpdUI interfaces, etc. (see TODO pointer to arch section).

Saving/Restoring the Factory

After the DSP factory has been compiled, the application or the plug-in running it might need to save it and then restore it. To get the internal factory compiled code, several functions are available:

  • writeDSPFactoryToIR: get the DSP factory LLVM IR (in textual format) as a string,
  • writeDSPFactoryToIRFile: get the DSP factory LLVM IR (in textual format) and write it to a file,
  • writeDSPFactoryToBitcode: get the DSP factory LLVM IR (in binary format) as a string
  • writeDSPFactoryToBitcodeFile: save the DSP factory LLVM IR (in binary format) in a file,
  • writeDSPFactoryToMachine: get the DSP factory executable machine code as a string,
  • writeDSPFactoryToMachineFile: save the DSP factory executable machine code in a file.

To re-create a DSP factory from a previously saved code, several functions are available:

  • readDSPFactoryFromIR: create a DSP factory from a string containing the LLVM IR (in textual format),
  • readDSPFactoryFromIRFile: create a DSP factory from a file containing the LLVM IR (in textual format),
  • readDSPFactoryFromBitcode: create a DSP factory from a string containing the LLVM IR (in binary format),
  • readDSPFactoryFromBitcodeFile: create a DSP factory from a file containing the LLVM IR (in binary format),
  • readDSPFactoryFromMachine: create a DSP factory from a string containing the executable machine code,
  • readDSPFactoryFromMachineFile: create a DSP factory from a file containing the executable machine code.

Additional Functions

Some additional functions are available in the libfaust API:

  • expandDSPFromString/expandDSPFromFile: creates a self-contained DSP source string where all needed librairies have been included. All compilations options are normalized and included as a comment in the expanded string,
  • generateAuxFilesFromString/generateAuxFilesFromFile: from a DSP source string or file, generates auxiliary files: SVG, XML, ps, etc. depending of the argv parameters.

Using the libfaust Library

The libfaust library is fully integrated to the Faust distribution. You’ll have to compile and install it in order to use it. For an exhaustive documentation/description of the API, we advise you to have a look at the code in the faust/dsp/llvm-dsp.h header file. Note that faust/dsp/llvm-c-dsp.h is a pure C version of the same API. Additional functions are available in faust/dsp/libfaust.h and their C version can be found in faust/dsp/libfaust-c.h.

More generally, a “typical” use of libfaust could look like:

// the Faust code to compile (could be in a file too)
string theCode = "import("stdfaust.lib");process = no.noise;";
// compiling in memory (createDSPFactoryFromFile could be used alternatively)
llvm_dsp_factory *m_factory = createDSPFactoryFromString( 
  "faust", theCode, argc, argv, "", m_errorString, optimize );
// creating the DSP instance for interfacing
dsp *m_dsp = m_factory->createDSPInstance();
// creating a generic UI to interact with the DSP
my_ui m_ui = new MyUI();
// linking the interface to the DSP instance
m_dsp->buildUserInterface( m_ui );
// initializing the DSP instance
m_dsp->init( 44100 );
// hypothetical audio callback
while(...){
  m_dsp->compute( 1, m_input, m_output );
}
// cleaning
delete m_dsp;
deleteDSPFactory( m_factory );
m_factory = NULL;

Thus, very few code is needed to embed Faust to your project!

Use Case Examples

The dynamic compilation chain has been used in several projects:

OSC Support

Overview

Most Faust architectures provide Open Sound Control (OSC) support (the implementation is based internally on the oscpack library by Ross Bencina). This allows applications to be remotely controlled from any OSC-capable application, programming language, or hardware device.

OSC support can be added to any Faust program (as long as the target architecture supports it: see tables below) simply by adding the [osc:on] metadata to the standard option metadata:

declare options "[osc:on]";

The following tables provides a list of Faust architectures providing OSC support.

Audio System Environment
Alsa GTK, Qt, Console
Jack GTK, Qt, Console
Netjack GTK, Qt, Console
PortAudio GTK, Qt

Linux Faust Architectures with OSC Support


Audio System Environment
CoreAudio Qt
Jack Qt, Console
Netjack Qt, Console
PortAudio Qt

OSX Faust Architectures with OSC Support


Audio System Environment
Jack Qt, Console
PortAudio Qt

Windows Faust Architectures with OSC Support


Environment
Android
iOS
JUCE
Bela

Other Faust Architectures with OSC Support


Simple Example

To illustrate how OSC support works let’s define a very simple noise generator with a level control (we’ll call it noise.dsp):

import("stdfaust.lib");
process = no.noise*hslider("level",0,0,1,0.01);

This example can be compiled as a standalone Jack Qt application with OSC support simply by running the following command:

faust2jaqt -osc noise.dsp

When the generated application is ran from the command line:

./noise 

various information is printed in the standard output, including:

Faust OSC version 0.93 application 'noise' is running on UDP ports 5510, 5511, 5512

Hence, the OSC module makes use of three different UDP ports:

  • 5510 is the listening port number: control messages should be addressed to this port.
  • 5511 is the output port number: control messages sent by the application and answers to query messages are sent to this port.
  • 5512 is the error port number: used for asynchronous error notifications.

These OSC parameters can be changed from the command line using one of the following options:

  • -port number sets the port number used by the application to receive messages.
  • -outport number sets the port number used by the application to transmit messages.
  • -errport number sets the port number used by the application to transmit error messages.
  • -desthost host sets the destination host for the messages sent by the application.
  • -xmit 0|1|2 turns transmission OFF, ALL, or ALIAS (default OFF). When transmission is OFF, input elements can be controlled using their addresses or aliases (if present). When transmission is ALL, input elements can be controlled using their addresses or aliases (if present), user’s actions and output elements (i.e., bargraph, etc.) are transmitted as OSC messages as well as aliases (if present). When transmission is ALIAS, input elements can only be controlled using their aliases, user’s actions and output elements are transmitted as aliases only.
  • -xmitfilter path allows to filter output messages. Note that path can be a regular expression (like /freeverb/Reverb1/*).

For example:

./noise -xmit 1 -desthost 192.168.1.104 -outport 6000

will run noise with transmission mode ON, using 192.168.1.104 on port 6000 as destination.

Automatic Port Allocation

In order to address each application individually, only one application can be listening on a single port at one time. Therefore when the default incoming port 5510 is already opened by some other application, an application will automatically try increasing port numbers until it finds an available port. Let say that we start noise and mixer (two Faust-generated applications with OSC support) on the same machine, we’ll get the following:

$ ./noise &
...
Faust OSC version 0.93 application 'noise' is running on UDP ports 5510, 5511, 5512
$ ./mixer
...
Faust OSC version 0.93 application 'mixer' is running on UDP ports 5513, 5511, 5512

The mixer application fails to open the default incoming port 5510 because it is already opened by noise. Therefore it tries to find an available port starting from 5513 and opens it. Please note that the two outcoming ports 5511 and 5512 are shared by all running applications.

Discovering OSC Applications

The commands oscsend and oscdump from the liblo package provide a convenient mean to experiment with OSC control and potentially debug applications with OSC support.

oscsend [hostname] [port] [address] [types] [values]: sends OSC messages via UDP. [types] is a string, the letters indicates the type of the following values: i=integer, f=float, s=string, etc.

oscdump [port]: receives OSC messages via UDP and dump to standard output

Note that OSC messages can be sent from any OSC-compatible applications (e.g., PureData, Max/MSP, etc.).

In the following examples, we’ll use two separate terminal windows. The first one will be used to send OSC messages to the noise application using oscsend. The second terminal will be used to monitor the messages sent by the application using oscdump. Commands executed on terminal 1 will be preceded by T1$. Messages received on terminal 2 will be preceded by T2:. To monitor on terminal T2 the OSC messages received on UDP port 5511, oscdump will be used:

T2$ oscdump 5511

Once set we can use the hello message to scan UDP ports for Faust applications. For example:

T1$ oscsend localhost 5510 "/*" s hello

gives us the root message address, the network and the UDP ports used by the noise application:

T2: /noise siii "192.168.1.102" 5510 5511 5512

Discovering the OSC Interface of an Application

The OSC interface of an application (the set of OSC messages we can use to control it) can be discovered by sending the get message to the root:

T1$ oscsend localhost 5510 /noise s get 

As an answer to this OSC message, a full description is printed in terminal T2:

T2: /noise sF "xmit" #F
T2: /noise ss "desthost" "127.0.0.1"
T2: /noise si "outport" 5511
T2: /noise si "errport" 5512
T2: /noise/level fff 0.000000 0.000000 1.000000

The root of the OSC interface is /noise. Transmission is OFF, xmit is set to false. The destination host for sending messages is 127.0.0.1, the output port is 5511 and the error port is 5512. The application has only one user interface element: /noise/level with current value 0.0, minimal value 0.0 and maximal value 1.0.

Widget’s OSC Address

Each widget of an application has a unique OSC address obtained by concatenating the labels of it’s surrounding groups with its own label.

There are potential conflicts between widget’s labels and the OSC address space. An OSC symbolic name is an ASCII string consisting of a restricted set of printable characters. Therefore to ensure compatibility spaces are replaced by underscores and some other characters (asterisk, comma, forward, question mark, open bracket, close bracket, open curly brace, close curly brace) are replaced by hyphens.

Here is as an example mix4.dsp, a very simplified monophonic audio mixer with 4 inputs and one output. For each input we have a mute button and a level slider:

input(v) = vgroup("input %v", *(1-checkbox("mute")) : *(vslider("level", 0, 0, 1, 0.01)));
process = hgroup("mixer", par(i, 4, input(i)) :> _);

If we query this application:

T1$ oscsend localhost 5510 "/*" s get 

We get a full description of its OSC interface on terminal T2:

T2: /mixer sF "xmit" #F
T2: /mixer ss "desthost" "127.0.0.1"
T2: /mixer si "outport" 5511
T2: /mixer si "errport" 5512
T2: /mixer/input_0/level fff 0.0000 0.0000 1.0000
T2: /mixer/input_0/mute  fff 0.0000 0.0000 1.0000
T2: /mixer/input_1/level fff 0.0000 0.0000 1.0000
T2: /mixer/input_1/mute  fff 0.0000 0.0000 1.0000
T2: /mixer/input_2/level fff 0.0000 0.0000 1.0000
T2: /mixer/input_2/mute  fff 0.0000 0.0000 1.0000
T2: /mixer/input_3/level fff 0.0000 0.0000 1.0000
T2: /mixer/input_3/mute  fff 0.0000 0.0000 1.0000

As we can see, each widget has a unique OSC address obtained by concatenating the top level group label “mixer,” with the “input” group label and the widget label (see the Labels as Pathnames Section). Please, note that blank spaces are replaced by underscores and metadata are removed during this operation.

All addresses must have a common root. This is the case in our example because there is a unique horizontal group mixer containing all widgets. If a common root is missing as in the following code:

input(v) = vgroup("input %v", *(1-checkbox("mute")) : *(vslider("level", 0, 0, 1, 0.01)));
process = par(i, 4, input(i)) :> _;

then a default vertical group is automatically create by the Faust compiler using the name of the file mix4 as label:

T2: /mix4 sF "xmit" #F
T2: /mix4 ss "desthost" "127.0.0.1"
T2: /mix4 si "outport" 5511
T2: /mix4 si "errport" 5512
T2: /mix4/input_0/level fff 0.0000 0.0000 1.0000
T2: /mix4/input_0/mute  fff 0.0000 0.0000 1.0000
T2: /mix4/input_1/level fff 0.0000 0.0000 1.0000
T2: /mix4/input_1/mute  fff 0.0000 0.0000 1.0000
T2: /mix4/input_2/level fff 0.0000 0.0000 1.0000
T2: /mix4/input_2/mute  fff 0.0000 0.0000 1.0000
T2: /mix4/input_3/level fff 0.0000 0.0000 1.0000
T2: /mix4/input_3/mute  fff 0.0000 0.0000 1.0000

Controlling the Application Via OSC

Any user interface element of the application can be controlled by sending one of the previously discovered messages/addresses. For example, to set the noise level of the application to 0.2 the following message can be sent:

T1$ oscsend localhost 5510 /noise/level f 0.2

If we now query /noise/level we get, as expected, the value 0.2:

T1$ oscsend localhost 5510 /noise/level s get
T2: /noise/level fff 0.2000 0.0000 1.0000

Turning Transmission ON

The xmit message at the root level is used to control the realtime transmission of OSC messages corresponding to user interface’s actions. For example:

T1$ oscsend localhost 5510 /noise si xmit 1

turns transmission in ALL mode. Now if we move the level slider we get a bunch of messages:

T2: /noise/level f 0.024000
T2: /noise/level f 0.032000
T2: /noise/level f 0.105000
T2: /noise/level f 0.250000
T2: /noise/level f 0.258000
T2: /noise/level f 0.185000
T2: /noise/level f 0.145000
T2: /noise/level f 0.121000
T2: /noise/level f 0.105000
T2: /noise/level f 0.008000
T2: /noise/level f 0.000000

This feature can be typically used for automation to record and replay actions on the user interface, or to remote control from one application to another. It can be turned OFF any time using:

T1$ oscsend localhost 5510 /noise si xmit 0

Use the ALIAS (xmit = 2) mode if you need to restrict the access to your program: when the ALIAS mode is used, only aliases of input elements (sliders, buttons…) can be used to control them, and output elements (bargraph) will only emit on their aliases.

Filtering OSC Messages

When the transmission of OSC messages is ON, all the user interface elements are sent through the OSC connection.

T2: /harpe/level f 0.024000
T2: /harpe/hand f 0.1
T2: /harpe/level f 0.024000
T2: /harpe/hand f 0.25
T2: /harpe/level f 0.024000
T2: /harpe/hand f 0.44
T2: /noise/level f 0.145000
T2: /harpe/hand f 0.78
T2: /noise/level f 0.145000
T2: /harpe/hand f 0.99

We can choose to filter unwanted parameters (or group of parameters). For example:

T1$ oscsend localhost 5510 /harpe si xmit 1 xmitfilter /harpe/level

As a result, we will receive:

T2: /harpe/hand f 0.1
T2: /harpe/hand f 0.25
T2: /harpe/hand f 0.44
T2: /harpe/hand f 0.78

To reset the filter, send:

T1$ oscsend localhost 5510 /harpe si xmit 1 xmitfilter

Using OSC Aliases

Aliases are a convenient mechanism to control a Faust application from a preexisting set of OSC messages.

Let’s say we want to control our previous noise example with TouchOSC on Android. The first step is to configure the TouchOSC host to 192.168.1.102 (the host running our noise application) and outgoing port to 5510.

Then we can use oscdump 5510 (after quitting the noise application in order to free port 5510) to visualize the OSC messages sent by TouchOSC. Let’s use for that the left slider of “simple layout”. Here is what we get:

T2: /1/fader1 f 0.000000
T2: /1/fader1 f 0.004975
T2: /1/fader1 f 0.004975
T2: /1/fader1 f 0.008125
T2: /1/fader1 f 0.017473
T2: /1/fader1 f 0.032499
T2: /1/fader1 f 0.051032
T2: ...
T2: /1/fader1 f 0.993289
T2: /1/fader1 f 1.000000

We can associate this OSC message to the noise level slider by inserting the metadata [osc:/1/fader1 0 1] into the slider’s label:

Several osc aliases can be inserted into a single label allowing the same widget to be controlled by several OSC messages

import("stdfaust.lib");
process = no.noise*hslider("level[osc:/1/fader1 0 1]",0,0,1,0.01);

Because the range of /1/fader1 is 0 to 1 (like the level slider), we can remove the range mapping information and write simply :

import("stdfaust.lib");
process = no.noise*hslider("level[osc:/1/fader1]",0,0,1,0.01);

TouchOSC can also send accelerometer data by enabling Settings/Options/Accelerometer. Using again oscdump 5510 we can visualize the messages sent by TouchOSC:

T2: ...
T2: /accxyz fff -0.147842 0.019752 9.694721
T2: /accxyz fff -0.157419 0.016161 9.686341
T2: /accxyz fff -0.167594 0.012570 9.683948
T2: ...

As we can see, TouchOSC sends the x, y and z accelerometers in a single message, as a triplet of values ranging approximately from -9.81 to 9.81. In order to select the appropriate accelerometer, we need to concatenate to /accxyz a suffix /0, /1 or /2. For example /accxyz/0 will correspond to x, /accxyz/1 to y, etc. We also need to define a mapping because the ranges are different:

import("stdfaust.lib");
process = no.noise * hslider("level[osc:/accxyz/0 0 9.81]",0,0,1,0.01);
alias description
[osc:/1/rotary1 0 1] top left rotary knob
[osc:/1/rotary2 0 1] middle left rotary knob
[osc:/1/rotary3 0 1] bottom left rotary knob
[osc:/1/push1 0 1] bottom left push button
[osc:/1/push2 0 1] bottom center left push button
[osc:/1/toggle1 0 1] top center left toggle button
[osc:/1/toggle2 0 1] middle center left toggle button
[osc:/1/fader1 0 1] center left vertical fader
[osc:/1/toggle3 0 1] top center right toggle button
[osc:/1/toggle4 0 1] middle center right toggle button
[osc:/1/fader2 0 1] center right vertical toggle button
[osc:/1/rotary4 0 1] top right rotary knob
[osc:/1/rotary5 0 1] middle right rotary knob
[osc:/1/rotary6 0 1] bottom right rotary knob
[osc:/1/push3 0 1] bottom center right push button
[osc:/1/push4 0 1] bottom right push button
[osc:/1/fader3 0 1] bottom horizontal fader
[osc:/accxyz/0 -10 10] \(x\) accelerometer
[osc:/accxyz/1 -10 10] \(y\) accelerometer
[osc:/accxyz/2 -10 10] \(z\) accelerometer

Examples of OSC Message Aliases for TouchOSC (Layout Mix2).

OSC Cheat Sheet

Default Ports

Port Description
5510 default listening port
5511 default transmission port
5512 default error port
5513 alternative listening ports

Command Line Options

Option Description
-port n set the port number used by the application to receive messages
-outport n set the port number used by the application to transmit messages
-errport n set the port number used by the application to transmit error messages
-desthost h set the destination host for the messages sent by the application
-xmit 0|1|2 turn transmission OFF, ALL or ALIAS (default OFF)
-xmitfilter s filter the Faust paths at emission time

Discovery Messages

Message Description
oscsend host port "/*" s hello discover if any OSC application is listening on port p
oscsend host port "/*" s get query OSC interface of application listening on port p

Control Messages

Message Description
oscsend host port "/*" si xmit 0|1|2 set transmission mode
oscsend host port widget s get get widget’s value
oscsend host port widget f v set widget’s value

Alias

Alias Description
"...[osc: address lo hi ]..." alias with \(lo \rightarrow min\), \(hi \rightarrow max\) mapping
"...[osc:' address]..." alias with min, max clipping

HTTP Support

Similarly to OSC, several Faust architectures also provide HTTP support. This allows Faust applications to be remotely controlled from any Web browser using specific URLs. Moreover OSC and HTTPD can be freely combined.

While OSC support is installed by default when Faust is built, this is not the case for HTTP. That’s because it depends on the GNU libmicrohttpd library which is usually not installed by default on the system. An additional make httpd step is therefore required when compiling and installing Faust:

make httpd
make
sudo make install

Note that make httpd will fail if libmicrohttpd is not available on the system.

HTTP support can be added to any Faust program (as long as the target architecture supports it: see tables below) simply by adding the [http:on] metadata to the standard option metadata:

declare options "[http:on]";

The following tables lists Faust’s architectures providing HTTP support:

Audio System Environment
Alsa GTK, Qt, Console
Jack GTK, Qt, Console
Netjack GTK, Qt, Console
PortAudio GTK, Qt

Linux Faust Architectures with HTTP Support


Audio System Environment
CoreAudio Qt
Jack Qt, Console
Netjack Qt, Console
PortAudio Qt

OSX Faust Architectures with HTTP Support


Audio System Environment
Jack Qt, Console
PortAudio Qt

Windows Faust Architectures with HTTP Support


A Simple Example

To illustrate how HTTP support works, let’s reuse our previous mix4.dsp example, a simple monophonic audio mixer with 4 inputs and one output. For each input we have a mute button and a level slider:

input(v) = vgroup("input %v", *(1-checkbox("mute")) : *(vslider("level", 0, 0, 1, 0.01)));
process  = hgroup("mixer", par(i, 4, input(i)) :> _);

This example can be compiled as a standalone Jack QT application with HTTP support using the command:

faust2jaqt -httpd mix4.dsp

The -httpd option embeds a small Web server into the generated application. Its purpose is to serve an HTML page implementing the interface of the app. This page makes use of JavaScript and SVG, and is quite similar to the native QT interface.

When the application is started from the command line:

./mix4 

various information are printed on the standard output, including:

Faust httpd server version 0.73 is running on TCP port 5510

As we can see, the embedded Web server is running by default on TCP port 5510. The entry point is http://localhost:5510. It can be open from any recent browser and it produces the page presented in the figure below:

JSON Description of the User Interface

The communication between the application and the Web browser is based on several underlying URLs. The first one is http://localhost:5510/JSON that returns a JSON description of the user interface of the application. This JSON description is used internally by the JavaScript code to build the graphical user interface. Here is (part of) the json returned by mix4:

{
  "name": "mix4",
  "address": "YannAir.local",
  "port": "5511",
  "ui": [
    {
      "type": "hgroup",
      "label": "mixer",
      "items": [
        {
          "type": "vgroup",
          "label": "input_0",
          "items": [
            {
              "type": "vslider",
              "label": "level",
              "address": "/mixer/input_0/level",
              "init": "0", "min": "0", "max": "1", 
              "step": "0.01"
            },
            {
              "type": "checkbox",
              "label": "mute",
              "address": "/mixer/input_0/mute",
              "init": "0", "min": "0", "max": "0", 
              "step": "0"
            }
          ]
        },
        
        ...
        
      ]
    }
  ]
}

Querying the State of the Application

Each widget has a unique “address” field that can be used to query its value. In our example here the level of the input 0 has the address /mixer/input_0/level. The address can be used to forge a URL to get the value of the widget: http://localhost:5510/mixer/input_0/level, resulting in:

/mixer/input_0/level 0.00000  

Multiple widgets can be queried at once by using an address higher in the hierarchy. For example to get the values of the level and the mute state of input 0 we use http://localhost:5510/mixer/input_0, resulting in:

/mixer/input_0/level 0.00000 
/mixer/input_0/mute  0.00000 

To get the all the values at once we simply use http://localhost:5510/mixer, resulting in:

/mixer/input_0/level 0.00000 
/mixer/input_0/mute  0.00000 
/mixer/input_1/level 0.00000 
/mixer/input_1/mute  0.00000 
/mixer/input_2/level 0.00000 
/mixer/input_2/mute  0.00000 
/mixer/input_3/level 0.00000 
/mixer/input_3/mute  0.00000 

Changing the Value of a Widget

Let’s say that we want to mute input 1 of our mixer. For that purpose, we can use the URL http://localhost:5510/mixer/input_1/mute?value=1 obtained by concatenating ?value=1 at the end of the widget URL.

All widgets can be controlled in a similar way. For example http://localhost:5510/mixer/input_3/level?value=0.7 will set the input 3 level to 0.7.

Proxy Control Access to the Web Server

A control application may want to access and control the running DSP using its Web server, but without using the delivered HTML page in a browser. Since the complete JSON can be retrieved, control applications can be purely developed in C/C++. A proxy version of the user interface can then be built, and parameters can be “set and get” using HTTP requests.

This mode can be started dynamically using the -server URL parameter. Assuming an application with HTTP support is running remotely at the given URL, the control application will fetch its JSON description, use it to dynamically build the user interface, and allow for the access of the remote parameters.

HTTP Cheat Sheet

Here is a summary of the various URLs used to interact with the application’s Web server.

Default Ports

Port Description
5510 default TCP port used by the application’s Web server
5511... alternative TCP ports

Command Line Options

Option Description
-port n set the TCP port number used by the application’s Web server
-server URL start a proxy control application accessing the remote application running on the given URL

URLs

URL Description
http://host:port the base URL to be used in proxy control access mode
http://host:port/JSON get a json description of the user interface
http://host:port/address get the value of a widget or a group of widgets
http://host:port/address?value=v set the value of a widget to v

JSON

Top Level

The JSON describes the name, host, and port of the application and a hierarchy of user interface items:

{
  "name": <name>,
  "address": <host>,
  "port": <port>,
  "ui": [ <item> ]
}

An <item> is either a group (of items) or a widget.

Groups

A group is essentially a list of items with a specific layout:

{
    "type": <type>,
    "label": <label>,
    "items": [ <item>, <item>,...]
}

The <type> defines the layout. It can be either "vgroup", "hgroup" or "tgroup"

Widgets

{
    "type": <type>,
    "label": <label>,
    "address": <address>,
    "meta": [ { "key": "value"},... ],
    "init": <num>,
    "min": <num>,
    "max": <num>,
    "step": <num>
},

Widgets are the basic items of the user interface. They can be of different <type>: "button", "checkbox", "nentry", "vslider", "hslider", "vbargraph" or "hbargraph".

MIDI and Polyphony Support

Similarly to OSC, several Faust architectures also provide MIDI support. This allows Faust applications to be controlled from any MIDI device (or to control MIDI devices). MIDI is also the preferable way to control Polyphonic instruments.

Configuring MIDI in Faust

MIDI support can be added to any Faust program (as long as the target architecture supports it: see tables below) simply by adding the [midi:on] metadata to the standard option metadata:

declare options "[midi:on]";

MIDI control is configured in Faust using metadata in UI elements. They are decoded by a special architecture that parses incoming MIDI messages and updates the appropriate control parameters, or send MIDI messages when the UI elements (i.e., sliders, buttons, etc.) are moved.

All MIDI configuration metadata in Faust follow the following format:

[midi:xxx yyy...]

This section provides a list of the most common metadata that can be used to configure of the MIDI behavior of a Faust program.

Below, when a 7-bit MIDI parameter is used to drive a button or a checkbox, its maximum value (127) maps to 1 (“on”) while its minimum value (0) maps to 0 (“off”).

[midi:ctrl num] Metadata

The [midi:ctrl num] metadata assigns MIDI CC (control) to a specific UI element. When used in a slider or a bargraph, this metadata will map the UI element value to the {0, 127} range. When used with a button or a checkbox, 1 will be mapped to 127, 0 will be mapped to 0.

Usage

toto = hslider("toto[midi:ctrl num]",...);

Where:

  • num: the MIDI CC number

Example

In the following example, the frequency of a sawtooth wave oscillator is controlled by MIDI CC 11. When CC11=0, then the frequency is 200Hz, when CC11=127, then the frequency is 1000Hz.

import("stdfaust.lib");
freq = hslider("frequency[midi:ctrl 11]",200,50,1000,0.01) : si.smoo;
process = os.sawtooth(freq);

[midi:keyon midikey] Metadata

The [midi:keyon midikey] metadata assigns the velocity value of a key-on MIDI message received on a specific midikey to a Faust parameter. When used in a slider or a bargraph, this metadata will map the UI element value to the {0, 127} range. When used with a button or a checkbox, 1 will be mapped to 127, 0 will be mapped to 0.

Usage

toto = hslider("toto[midi:keyon midikey]",...);

Where:

  • midikey: the MIDI key number

Example

In the following example, the frequency of a sawtooth wave oscillator is controlled by the velocity value received on key 62 when a key-on message is sent. Therefore, the frequency will only be updated when MIDI key 62 is pressed.

import("stdfaust.lib");
freq = hslider("frequency[midi:keyon 62]",200,50,1000,0.01) : si.smoo;
process = os.sawtooth(freq);

[midi:keyoff midikey] Metadata

The [midi:keyoff midikey] metadata assigns the velocity value of a key-off MIDI message received on a specific midikey to a Faust parameter. When used in a slider or a bargraph, this metadata will map the UI element value to the {0, 127} range. When used with a button or a checkbox, 1 will be mapped to 127, 0 will be mapped to 0.

Usage

toto = hslider("toto[midi:keyoff midikey]",...);

Where:

  • midikey: the MIDI key number

Example

In the following example, the frequency of a sawtooth wave oscillator is controlled by the velocity value received on key 62 when a key-off message is sent. Therefore, the frequency will only be updated when MIDI key 62 is released.

import("stdfaust.lib");
freq = hslider("frequency[midi:keyon 62]",200,50,1000,0.01) : si.smoo;
process = os.sawtooth(freq);

[midi:key midikey] Metadata

The [midi:key midikey] metadata assigns the velocity value of key-on and key-off MIDI messages received on a specific midikey to a Faust parameter. When used in a slider or a bargraph, this metadata will map the UI element value to the {0, 127} range. When used with a button or a checkbox, 1 will be mapped to 127, 0 will be mapped to 0.

Usage

toto = hslider("toto[midi:key midikey]",...);

Where:

  • midikey: the MIDI key number

Example

In the following example, the frequency of a sawtooth wave oscillator is controlled by the velocity value received on key 62 when key-on and key-off messages are sent. Therefore, the frequency will only be updated when MIDI key 62 is pressed and released.

import("stdfaust.lib");
freq = hslider("frequency[midi:key 62]",200,50,1000,0.01) : si.smoo;
process = os.sawtooth(freq);

[midi:keypress midikey] Metadata

The [midi:keypress midikey] metadata assigns the pressure (after-touch) value of a specific midikey to a Faust parameter. When used in a slider or a bargraph, this metadata will map the UI element value to the {0, 127} range. When used with a button or a checkbox, 1 will be mapped to 127, 0 will be mapped to 0.

Usage

toto = hslider("toto[midi:keypress midikey]",...);

Where:

  • midikey: the MIDI key number

Example

In the following example, the frequency of a sawtooth wave oscillator is controlled by the pressure (after-touch) values received on key 62.

import("stdfaust.lib");
freq = hslider("frequency[midi:keypress 62]",200,50,1000,0.01) : si.smoo;
process = os.sawtooth(freq);

[midi:pitchwheel] Metadata

The [midi:pitchwheel] metadata assigns the pitch-wheel value to a Faust parameter. When used in a slider or a bargraph, this metadata will map the UI element value to the {0, 16383} range. When used with a button or a checkbox, 1 will be mapped to 16383, 0 will be mapped to 0.

Usage

toto = hslider("toto[midi:pitchwheel]",...);

Example

In the following example, the frequency of a sawtooth wave oscillator is controlled by the pitch-wheel.

import("stdfaust.lib");
freq = hslider("frequency[midi:pitchwheel]",200,50,1000,0.01) : si.smoo;
process = os.sawtooth(freq);

[midi:start] Metadata

When used with a button or a checkbox, [midi:start] will trigger a value of 1 when a start MIDI message is received.

Usage

toto = checkbox("toto[midi:start]");

[midi:stop] Metadata

When used with a button or a checkbox, [midi:stop] will trigger a value of 0 when a stop MIDI message is received.

Usage

toto = checkbox("toto[midi:stop]");

[midi:clock] Metadata

When used with a button or a checkbox, [midi:clock] will deliver a sequence of successive 1 and 0 values each time a clock MIDI message is received (seen by Faust code as a square command signal, to be used to compute higher level information).

Usage

toto = checkbox("toto[midi:clock]");

MIDI Sync

MIDI clock-based synchronization can be used to slave a given Faust program using the metadata presented in the 3 past sections.

A typical Faust program will then use the MIDI clock stream to possibly compute the BPM information, or for any synchronization need it may have. Here is a simple example of a sinus generated which a frequency controlled by the MIDI clock stream, and starting/stopping when receiving the MIDI start/stop messages:

import("stdfaust.lib");

// square signal (1/0), changing state at each received clock
clocker = checkbox("MIDI clock[midi:clock]");    

// ON/OFF button controlled with MIDI start/stop messages
play = checkbox("ON/OFF [midi:start] [midi:stop]");    

// detect front
front(x) = (x-x') != 0.0;      

// count number of peaks during one second
freq(x) = (x-x@ma.SR) : + ~ _;   
   
process = os.osc(8*freq(front(clocker))) * play;

MIDI Polyphony Support

Polyphony is conveniently handled in Faust directly by Faust Architectures. Note that programming polyphonic instrument completely from scratch in Faust and without relying on architectures is also possible. In fact, this feature is indispensable if complex signal interactions between the different voices have to be described (like sympathetic strings resonance in a physical model, etc.). However, since all voices would always be computed, this approach could be too CPU costly for simpler or more limited needs. In this case describing a single voice in a Faust DSP program and externally combining several of them with a special polyphonic instrument aware architecture file is a better solution. Moreover, this special architecture file takes care of dynamic voice allocation and control MIDI messages decoding and mapping.

Polyphony support can be added to any Faust program (as long as the target architecture supports it) simply by adding the [nvoices:n] metadata to the standard option metadata where n is the maximum number of voices of polyphony to be allocated:

declare options "[nvoices:12]";

Standard Polyphony Parameters

Most Faust architectures allow for the implementation of polyphonic instruments simply by using a set of “standard user interface names.” Hence, any Faust program declaring the freq, gain, and gate parameter is polyphony-compatible. These 3 parameters are directly associated to key-on and key-off events and have the following behavior:

  • When a key-on event is received, gate will be set to 1. Inversely, when a key-off event is received, gate will be set to 0. Therefore, gate is typically used to trigger an envelope, etc.
  • freq is a frequency in Hz computed automatically in function of the value of the pitch contained in a key-on or a key-off message.
  • gain is a linear gain (value between 0-1) computed in function of the velocity value contained in a key-on or a key-off message.

Example: Simple Polyphonic Synthesizer

In the following example, the standard freq, gain, and gate parameters are used to implement a simple polyphonic synth.

import("stdfaust.lib");
freq = hslider("freq",200,50,1000,0.01);
gain = hslider("gain",0.5,0,1,0.01);
gate = button("gate");
process = os.sawtooth(freq)*gain*gate;

Note that if you execute this code in the Faust online editor with polyphony mode activated, you should be able to control this simple synth with any MIDI keyboard connected to your computer. This will only work if you’re using Google Chrome (most other browsers are not MIDI-compatible).

The previous example can be slightly improved by adding an envelope generator and controlling it with gain and gate:

import("stdfaust.lib");
freq = hslider("freq",200,50,1000,0.01);
gain = hslider("gain",0.5,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.01,0.01,0.8,0.1,gate)*gain;
process = os.sawtooth(freq)*envelope;

Warning: Note that all the active voices of polyphony are added together without scaling! This means that the previous example will likely click if several voices are played at the same time. It is the Faust programmer’s responsibility to take this into account in his code. For example, assuming that the number of active voices will always be smaller or equal to 4, the following safeguard could be added to the previous example:

process = os.sawtooth(freq)*envelope : /(4);

Configuring and Activating Polyphony

Polyphony can be activated “manually” in some Faust architectures using an option/flag during compilation (e.g., typically -poly or -nvoices in the faust2… scripts). That’s also how the Faust online editor works where a button can be used to turn polyphony on or off.

However, the most standard way to activate polyphony in Faust is to declare the [nvoices:n] metadata which allows us to specify the maximum number of voices of polyphony (n) that will be allocated in the generated program.

For example, the Faust program from the previous section could be modified such that:

declare options "[midi:on][nvoices:12]";
import("stdfaust.lib");
freq = hslider("freq",200,50,1000,0.01);
gain = hslider("gain",0.5,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.01,0.01,0.8,0.1,gate)*gain;
process = os.sawtooth(freq)*envelope;

which when compiled running (for example):

faust2jaqt faustProgram.dsp

will generate a MIDI-controllable polyphonic synthesizer.

Audio Effects and Polyphonic Synthesizer

While audio audio effects can be added directly to the process line of a Faust synthesizer, for example:

process = os.sawtooth(freq)*envelope : reverb;

it is not a good practice since a new instance of that effect will be created for each active voice of polyphony. The main consequence of this would be an increased CPU cost.

Similarly to process, Faust allows for the declaration of an effect line, which identifies an audio effect to be connected to the output of the polyphonic synthesizer.

For example, a simple reverb can be added to the previous example simply by writing:

declare options "[midi:on][nvoices:12]";
import("stdfaust.lib");
freq = hslider("freq",200,50,1000,0.01);
gain = hslider("gain",0.5,0,1,0.01);
gate = button("gate");
envelope = en.adsr(0.01,0.01,0.8,0.1,gate)*gain;
process = os.sawtooth(freq)*envelope <: _,_;
effect = dm.zita_light;

In this case, the polyphonic part is based on process and a single instance of the effect defined in effect will be created and shared by all voices.

Note that since dm.zita_light is a stereo effect, the output of process must be split into 2 signals. Also, be aware that this type of construction wont be visible in the corresponding block diagram that will only show what’s implemented in the process line.

Polyphony and Continuous Pitch

Key-on and key-off MIDI messages only send the “base pitch” of the instance of a note. Hence, if only the freq standard parameter is used to control the frequency of the synthesizer, its pitch will always be “quantized” to the nearest semitone. In order to be able to do glissandi, vibrato, etc., a variable associated to the pitch-wheel needs to be declared and must interact with the “base frequency” value retrieved from freq as such:

f = hslider("freq",300,50,2000,0.01);
bend = hslider("bend[midi:pitchwheel]",1,0,10,0.01);
freq = f*bend; // the "final" freq parameter to be used

The bend variable is controlled by the pitch-wheel thanks to [midi:pitchwheel] metadata. bend is used as a factor multiplied to the base frequency retrieved from freq. Therefore, the default value of bend should always be 1 which corresponds to the central position of the pitch wheel (MIDI value 64). A value smaller than 1 will decrease the pitch and a value greater than 1 will increase it.

While the above example will have the expected behavior, it is likely that clicking will happen when changing the value of bend since this parameter is not smoothed. Unfortunately, regular smoothing (through the use of si.smoo, for example) is not a good option here. This is due to the fact that instances of polyphonic voices are frozen when a voice is not being used. Since the value of bend might jump from one value to another when a voice is being reactivated/reused, continuous smoothing would probably create an “ugly sweep” in that case. Hence, si.polySmooth should be used in this context instead of si.smoo. This function shuts down smoothing for a given number of samples when a trigger is activated.

Reusing the example from the previous section, we can implement a click-free polyphonic synthesizer with continuous pitch control:

declare options "[midi:on][nvoices:12]";
import("stdfaust.lib");
f = hslider("freq",300,50,2000,0.01);
bend = hslider("bend[midi:pitchwheel]",1,0,10,0.01) : si.polySmooth(gate,0.999,1);
gain = hslider("gain",0.5,0,1,0.01);
gate = button("gate");
freq = f*bend; 
envelope = en.adsr(0.01,0.01,0.8,0.1,gate)*gain;
process = os.sawtooth(freq)*envelope <: _,_;
effect = dm.zita_light;

Observe the usage of si.polySmooth here: when gate=0 the signal is not smoothed, when gate=1 the signal is smoothed with a factor of 0.999 after one sample.

Complete Example: Sustain Pedal and Additional Parameters

Just for fun ;), we improve in this section the example from the previous one by implementing sustain pedal control as well as some modulation controlled by the modulation wheel of the MIDI keyboard.

Sustain pedal control can be easily added simply by declaring a sustain parameter controlled by MIDI CC 64 (which is directly linked to the sustain pedal) and interacting with the standard gate parameter:

s = hslider("sustain[midi:ctrl 64]",0,0,1,1);
t = button("gate");
gate = t+s : min(1);

Hence, gate will remain equal to 1 as long as the sustain pedal is pressed.

The simple synthesizer from the previous section (which is literally just a sawtooth oscillator) can be slightly improved by processing it with a dynamically-controlled lowpass filter:

declare options "[midi:on][nvoices:12]";
import("stdfaust.lib");
f = hslider("freq",300,50,2000,0.01);
bend = hslider("bend[midi:pitchwheel]",1,0,10,0.01) : si.polySmooth(gate,0.999,1);
gain = hslider("gain",0.5,0,1,0.01);
s = hslider("sustain[midi:ctrl 64]",0,0,1,1);
cutoff = hslider("cutoff[midi:ctrl 1]",1000,50,4000,0.01) : si.smoo;
t = button("gate");
freq = f*bend; 
gate = t+s : min(1);
envelope = en.adsr(0.01,0.01,0.8,0.1,gate)*gain;
process = os.sawtooth(freq)*envelope : fi.lowpass(3,cutoff) <: _,_;
effect = dm.zita_light;

MIDI CC 1 corresponds to the modulation wheel which is used here to control the cut-off frequency of the lowpass filter.

Architecture Files

This section will describe the overall organization of the Faust architecture system. In particular, the content of /architectures and /architectures/faust will be thoroughly described. The interfacing of the generated code with architectures will also be described and further developed in the companion tutorial.

Mathematical Documentation

The Faust compiler provides a mechanism to produce a self-describing documentation of the mathematical semantic of a Faust program, essentially as a pdf file. The corresponding options are -mdoc (short) or --mathdoc (long).

Goals of the Mathdoc

There are three main goals, or uses, of the Faust mathematical documentation generator:

  • to preserve signal processors, independently from any computer language but only under a mathematical form;
  • to bring some help for debugging tasks, by showing the formulas as they are really computed after the compilation stage;
  • to give a new teaching support, as a bridge between code and formulas for signal processing.

Installation Requirements

  • faust, of course!
  • svg2pdf (from the Cairo 2D graphics library), to convert block-diagrams, as Latex doesn’t embed SVG directly,
  • breqn, a Latex package to handle automatic breaking of long equations,
  • pdflatex, to compile the Latex output file.

Generating the Mathdoc

The easiest way to generate the complete mathematical documentation is to call the faust2mathdoc script on a Faust file, as the -mdoc option leaves the process of generating the documentation unfinished (only the source is produced).

Invoking the -mdoc Option

Calling directly faust -mdoc does only the first part of the work, generating:

  • a top-level directory, suffixed with -mdoc,
  • 5 subdirectories (cpp/, pdf/, src/, svg/, tex/),
  • a Latex file containing the formulas,
  • SVG files for block-diagrams.

At this stage:

  • cpp/ remains empty,
  • pdf/ remains empty,
  • src/ contains all the used Faust sources (even libraries),
  • svg/ contains SVG block-diagram files,
  • tex/ contains the generated Latex file.

Invoking faust2mathdoc

The faust2mathdoc script calls faust --mathdoc first, then it finishes the work:

  • moving the output C++ file into cpp/,
  • converting all SVG files into pdf files (you must have svg2pdf installed, from the Cairo 2D graphics library),
  • launching pdflatex on the Latex file (you must have both pdflatex and the breqn package installed),
  • moving the resulting pdf file into pdf/.

Online Examples

To get an idea of the results of this mathematical documentation, which captures the mathematical semantic of Faust programs, you can look at two pdf files online:

You can also generate all mdoc pdfs at once, simply invoking the make mathdoc command inside the examples/ directory:

  • for each %.dsp file, a complete %-mdoc directory will be generated,
  • a single allmathpdfs/ directory will gather all the generated pdf files.

Automatic Documentation

By default, when no <mdoc> tag can be found in the input Faust file, the -mdoc option automatically generates a Latex file with four sections:

  • Equations of process, gathering all formulas needed for process,
  • Block-diagram schema of process, showing the top-level block-diagram of process,
  • Notice of this documentation, summing up generation and conventions information,
  • Complete listing of the input code, listing all needed input files (including libraries).

Manual Documentation

You can specify yourself the documentation instead of using the automatic mode, with five xml-like tags. That allows you to modify the presentation and to add your own comments, not only on process, but also about any expression you’d like to. Note that as soon as you declare an <mdoc> tag inside your Faust file, the default structure of the automatic mode is ignored, and all the Latex stuff becomes up to you!

Six Tags

Here are the six specific tags:

  • <mdoc></mdoc>, to open a documentation field in the Faust code,
    • <equation></equation>, to get equations of a Faust expression,
    • <diagram></diagram>, to get the top-level block-diagram of a Faust expression,
    • <metadata></metadata>, to reference Faust metadatas (cf. declarations), calling the corresponding keyword,
    • <notice />, to insert the “adaptive” notice all formulas actually printed,
    • <listing [attributes] />, to insert the listing of Faust files called.

The <listing /> tag can have up to three boolean attributes (set to true by default):

  • mdoctags for <mdoc> tags;
  • dependencies for other files dependencies;
  • distributed for the distribution of interleaved Faust code between <mdoc> sections.

The mdoc Top-Level Tags

The <mdoc></mdoc> tags are the top-level delimiters for Faust mathematical documentation sections. This means that the four other documentation tags can’t be used outside these pairs.

In addition of the four inner tags, <mdoc></mdoc> tags accept free Latex text, including its standard macros (like \section, \emph, etc.). This allows to manage the presentation of resulting tex file directly from within the input Faust file.

The complete list of the Latex packages included by Faust can be found in the file architecture/latexheader.tex.

An Example of Manual Mathdoc

<mdoc>
\title{<metadata>name</metadata>}
\author{<metadata>author</metadata>}
\date{\today}
\maketitle

\begin{tabular}{ll}
    \hline
    \textbf{name}       & <metadata>name</metadata> \\
    \textbf{version}    & <metadata>version</metadata> \\
    \textbf{author}     & <metadata>author</metadata> \\
    \textbf{license}    & <metadata>license</metadata> \\
    \textbf{copyright}  & <metadata>copyright</metadata> \\
    \hline
\end{tabular}
\bigskip
</mdoc>
//-----------------------------------------------------------------
// Noise generator and demo file for the Faust math documentation
//-----------------------------------------------------------------

declare name        "Noise";
declare version     "1.1";
declare author      "Grame";
declare author      "Yghe";
declare license     "BSD";
declare copyright   "(c)GRAME 2009";

<mdoc>
\section{Presentation of the "noise.dsp" Faust program}
This program describes a white noise generator with an interactive volume, 
using a random function.

\subsection{The random function}
</mdoc>

random  = +(12345)~*(1103515245);

<mdoc>
The \texttt{random} function describes a generator of random numbers, which 
equation follows. You should notice hereby the use of an integer arithmetic on 
32 bits, relying on integer wrapping for big numbers.
<equation>random</equation>

\subsection{The noise function}
</mdoc>

noise   = random/2147483647.0;

<mdoc>
The white noise then corresponds to:
<equation>noise</equation>

\subsection{Just add a user interface element to play volume!}
</mdoc>

process = noise * vslider("Volume[style:knob]", 0, 0, 1, 0.1);

<mdoc>
Endly, the sound level of this program is controlled by a user slider, which 
gives the following equation: 
<equation>process</equation>

\section{Block-diagram schema of process}
This process is illustrated on figure 1.
<diagram>process</diagram>

\section{Notice of this documentation}
You might be careful of certain information and naming conventions used in this 
documentation:
<notice />

\section{Listing of the input code}
The following listing shows the input Faust code, parsed to compile this
mathematical documentation.
<listing mdoctags="false" dependencies="false" distributed="true" />
</mdoc>

The -stripmdoc Option

The listing of the input code contains all the mathdoc text. As it may be useless in certain cases, we provide an option to strip mathdoc contents directly at compilation stage: -stripmdoc (short) or --strip-mdoc-tags (long).

Localization of Mathdoc Files

By default, texts used by the documentator are in English, but you can specify another language (French, German and Italian at the moment), using the -mdlang (or --mathdoc-lang) option with a two-letters argument (en, fr, it, etc.).

The faust2mathdoc script also supports this option, plus a third short form with -l:

faust2mathdoc -l fr myfaustfile.dsp

If you would like to contribute to the localization effort, feel free to translate the mathdoc texts from any of the mathdoctexts-*.txt files, that are in the architecture directory (mathdoctexts-fr.txt, mathdoctexts-it.txt, etc.). As these files are dynamically loaded, just adding a new file with an appropriate name should work.

Summary of the Mathdoc Generation Steps

  • First, to get the full mathematical documentation done on your Faust file, call faust2mathdoc myfaustfile.dsp.
  • Then, open the pdf file myfaustfile-mdoc/pdf/myfaustfile.pdf.
  • That’s all !

Acknowledgments

Many persons are contributing to the Faust project, by providing code for the compiler, architecture files, libraries, examples, documentation, scripts, bug reports, ideas, etc. We would like in particular to thank:

  • Fons Adriaensen
  • Karim Barkati
  • Jérôme Barthélemy
  • Tim Blechmann
  • Tiziano Bole
  • Alain Bonardi
  • Thomas Charbonnel
  • Raffaele Ciavarella
  • Julien Colafrancesco
  • Damien Cramet
  • Sarah Denoux
  • Étienne Gaudrin
  • Olivier Guillerminet
  • Pierre Guillot
  • Albert Gräf
  • Pierre Jouvelot
  • Stefan Kersten
  • Victor Lazzarini
  • Matthieu Leberre
  • Mathieu Leroi
  • Fernando Lopez-Lezcano
  • Kjetil Matheussen
  • Hermann Meyer
  • Rémy Muller
  • Raphael Panis
  • Eliott Paris
  • Reza Payami
  • Laurent Pottier
  • Sampo Savolainen
  • Nicolas Scaringella
  • Anne Sedes
  • Priyanka Shekar
  • Stephen Sinclair
  • Travis Skare
  • Julius Smith
  • Mike Solomon
  • Michael Wilson

as well as our colleagues at GRAME:

  • Dominique Fober
  • Christophe Lebreton
  • Stéphane Letz
  • Yann Orlarey

We would like also to thank for their financial support: