Introduction
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 us to "translate" any Faust digital signal processing (DSP) specification to a wide range of non-domain specific languages such as C++, C, JAVA, LLVM IR, 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.
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 us 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:
Blocks can be easily connected together using the :
"connection" composition:
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 (where is the pole):
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 us 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 the Quick Start Tutorial section of this manual.
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, LLVM IR, 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 is a discrete function of time . The value of a signal at time is written . The values of signals are usually needed starting from time . But to take into account delay operations, negative times are possible and are always mapped to zeros. Therefore for any Faust signal we have . In operational terms this corresponds to assuming that all delay lines are signals initialized with s.
- Faust considers two type of signals: integer signals and floating point signals . Exchanges with the outside world are, by convention, made using floating point signals. The full range is represented by sample values between and .
- The set of all possible signals is .
- A group of signals (a n-tuple of signals) is written . The empty tuple, single element of is notated .
- A signal processors , is a function from n-tuples of signals to m-tuples of signals . The set 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 . It takes two input signals and and produces an output signal such that .
Numbers are signal processors too. For example the number has signature . It takes no input signals and produce an output signal such that .