Faust Tutorial
Faust Tutorial
The portability of C++ entails the portability of Faust if one can bring the
good wrapper files that will make the C ++ code compilable as a standalone
application on your system.
This tutorial aim is to present the Faust syntax and grammar, and com-
mented examples.
1
Introduction 2
Conventions
Here are some font conventions used in this tutorial. The Faust source
code is written with a Typewriter font :
process = hslider("Frequency",1,1,2000,1):sinus;
The same font is used for C++ code, except for the keywords that are in
bold Roman :
The file names and paths are written with a Sans-Serif font :
/src/faust/architecture/alsa-gtk.cpp
Contents
Introduction 1
I Language description 5
1 Preview 7
1.1 An Algebra for Block Diagram Languages . . . . . . . . . . . 7
1.2 The semantic function . . . . . . . . . . . . . . . . . . . . . . 9
1.3 Block-diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2 Primitives 11
2.1 Plug boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.1 Identity box : . . . . . . . . . . . . . . . . . . . . . 11
2.1.2 Cut box : ! . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2 Math operators . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.2.1 Arithmetic operators . . . . . . . . . . . . . . . . . . . 12
2.2.2 Comparison operators . . . . . . . . . . . . . . . . . . 13
2.3 Bitwise operators . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.5 Casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.6 Foreign functions . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.7 Memories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.7.1 Simple delay mem . . . . . . . . . . . . . . . . . . . . . 15
2.7.2 Read-only table . . . . . . . . . . . . . . . . . . . . . . 15
2.7.3 Read-write table . . . . . . . . . . . . . . . . . . . . . 16
2.8 Selection switch . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.9 Graphic user interface . . . . . . . . . . . . . . . . . . . . . . 18
2.9.1 Button . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.9.2 Checkbox . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.9.3 Sliders . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.9.4 Numeric entry . . . . . . . . . . . . . . . . . . . . . . 19
2.9.5 Groups . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3
Contents 4
3 Composition operators 21
3.1 Serial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Parallel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.3 Split . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.4 Merge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.5 Recursive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.6 Precedence . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5 Compilation 31
5.1 Generated C++ code . . . . . . . . . . . . . . . . . . . . . . . 31
5.2 Command-line options . . . . . . . . . . . . . . . . . . . . . . 31
5.3 Wrapper files . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.4 How to compile the C++ file . . . . . . . . . . . . . . . . . . . 33
II Examples 35
7 Karplus-Strong 55
7.1 Presentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
7.2 Faust source code . . . . . . . . . . . . . . . . . . . . . . . . . 55
7.3 Block-diagram . . . . . . . . . . . . . . . . . . . . . . . . . . 56
7.4 Graphic user interface . . . . . . . . . . . . . . . . . . . . . . 60
7.5 Comments and explanations . . . . . . . . . . . . . . . . . . . 60
III Appendices 65
B Error messages 69
B.1 Localised errors . . . . . . . . . . . . . . . . . . . . . . . . . . 69
B.2 Wriggled errors . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Part I
Language description
5
Chapter 1
Preview
S = { s : N → R } = RN
7
Chapter 1 Preview 8
∃(n, m) ∈ N2 , p : Sn → Sm
Then, p is a function so we should write p (x1 , x2 , . . . , xn ) = (y1 , y2 , . . . , ym ),
where x1 , x2 , . . . , xn and y1 , y2 , . . . , ym are streams. But, to simplify the no-
tation from now, only some single parenthesis will be used :
p(x1 , x2 , . . . , xn ) = (y1 , y2 , . . . , ym )
Then p : S2 → S1 and :
Then one has to deal with a different stream processor, q that adds a
phase difference φ to a signal.
9 1.2 The semantic function
Then q : S1 → S1 and :
∀ x ∈ S, ∃ y ∈ S, q(x) = (y)
where ∀ t ∈ N, y(t) = x(t − φ)
This last example shows the importance of the difference between num-
bers and streams, and so between real-valued functions and stream proces-
sors.
[[ Faust expression ]] ∈ P
Let’s take the two examples shown in the previous section. The first one,
which sums two signals, has a direct Faust implementation noted +. Thus
we have the following relation :
[[ + ]] = p
The second example is slightly more complex unless the delay duration
is only of one sample. Then, the signal processor q will be written mem in a
Faust program :
[[ mem ]] = q
1.3 Block-diagrams
On diagrams, the box-name is drawn in the center of it, or above the
upper left corner of it when it contains something (a symbol or some other
boxes). When a box is made with a combination of boxes, it is drawn with
dashed lines.
The box sometime contains a dot normally in the upper left corner, as
on chipsets, that may show if the box is turned upside down. In that case,
the text isn’t rotated to keep it readable.
Primitives
In certain cases this box can be considered as a wire box. This is the
mono dimensional identity function : it does nothing else but transmit the
data stream. Because the Faust compiler operates a formal simplification of
the block-diagram before the C++ conversion, this kind of box never appears
— by any way — in the C++ code. This box is mostly used to clarify the
Faust source code.
This box terminates the stream. It’s a dead end. It has one input and
zero output. It is mostly used to make the number of input and output
match when connecting boxes.
11
Chapter 2 Primitives 12
∀ (x) ∈ S1 , [[ ! ]](x) = ( )
2.4 Constants
A constant box has no input and generates a constant signal. It is defined
from the literals C++ syntax. For example : [[ 10 ]]( ) = (λt . 10).
In the following paragraphs, the symbol # represents any digit or string
of digits.
1
See [Str00] if you are unfamiliar with these notations.
Chapter 2 Primitives 14
Integers ‘#’ represents an integer. ‘012’ and ‘12’ both represent a con-
stant box of which the output signal is the integer 12. ‘-12’ represents a
constant box of which the output signal is the integer −12.
2.5 Casting
There are two boxes to make the cast operations : float and int. It
will make a C++ implicit cast on a single input in order to generate the
output. Most of the time Faust will do the cast, but you may however
have to check the process for Faust isn’t always accurate and may even in
certain circumstances fail to do it. In certain cases, an absence of cast would
seriously prevent a good functioning of the program. A correct use of the
C++ literals will reduce the need in cast boxes.
prototype is the C++ prototype of the function you want to use, e.g.
float sin(float). The C++ function can have many arguments. All the
arguments of this foreign function will be used as box inputs, respecting
the same order. Because the C++ function can only have a maximum of one
output, then the box will have one or no output. Remember that Faust only
handles int and float (no pointer nor double).
include is the argument of the C++ command #include, e.g. <math.h>.
You must use the exact same argument as you would do to use the function
in a C++ program. So you can use both < > and " ", as in a C ++ file.
library is a filename added as a comment at the beginning of the C ++
file generated by Faust, useful to remind to the programmer to add this file
in the linking mechanism (see section 5.4, page 34).
Because the standard libraries don’t have to be explicitly linked to, then
you will often use a blank "" for this argument.
Here’s an example that provides the sin() function as a box :
2.7 Memories
The memories are useful to make delays, to store a few values, parameters
or data.
• initialising stream : open flow during initialisation time which fills the
data table.
During the execution, this box uses only one stream. So, at this time,
the box can be considered as having only one input which is the read index.
For example :
• size of table,
• initialising stream,
17 2.8 Selection switch
• write-index that must be a positive integer between zero and the table
size minus one,
• read-index.
∀ (i, s1 , s2 ) ∈ S3 ,
[[ select2 ]](i, s1 , s2 ) = (λt . (1 − i(t)) · s1 (t) + i(t) · s2 (t) )
Chapter 2 Primitives 18
If index is 0, then the output is stream 0, and if index is 1, then the output
is stream 1. If index is greater than 1, then the output is 0, and an error
can occur during execution.
The select3 box is exactly the same except that you can choose between
3 streams.
2.9.1 Button
The button primitive has the following syntax :
button("label")
This box is a monostable trigger. It has no input, and one output that
is set to 1 if the user click the button, and else to 0.
2.9.2 Checkbox
The checkbox is a bistable trigger. After a mouse click, the output is
set to 1. If you click again the output is set to 0. The state changes on
mouse up.
2.9.3 Sliders
The slider boxes hslider (horizontal) and vslider (vertical) provide
some powerful controls for the parameters. Here’s the syntax :
This produce a slider, horizontal or vertical, that let the user pick a
value between min and max −step. The initial value is start. When the user
moves the slider, the value changes by steps of the value of step. All the
parameters can be floats or ints.
The associated box has no input, and one output which is the value that
the slider displays.
2.9.5 Groups
The primitives hgroup and vgroup gather GUI objects in a group :
The group gather the GUI objects contained in the Faust expression : they
are not boxes.
On the contrary, the tgroup primitive spreads the GUI objects found in
a faust expression into tabs, eg :
A = hslider("Slider 1",50,0,100,1);
B = hslider("Slider 2",50,0,100,1);
C = hslider("Slider 3",50,0,100,1);
process = tgroup("Sliders",(A,B,C));
Here is what one can get after Faust and C ++ compilation (with GTK) :
Chapter 3
Composition operators
3.1 Serial
Notation : A : B
This operator makes a serial connection of the two operand boxes. It
means that the outputs of A are connected to the inputs of B, respecting
their order. The first output of A is connected to the first input of B, etc. . .
The remaining inputs or outputs are directly used as inputs or outputs of
the resulting block-diagram.
21
Chapter 3 Composition operators 22
3.2 Parallel
Notation : A , B
This operator makes a block-diagram by positioning the two operands
in parallel. There’s no connection between the boxes. All the inputs of
both the boxes are used as inputs of the resulting block-diagram, as for the
outputs.
3.3 Split
Notation : A <: B
This operator is slightly more complex. The outputs of A are spread on
the inputs of B. This means that each output of A is connected to n inputs
of B, where n is the number of inputs of B, i B , divided by the number of
outputs of A, oA :
iB
n=
oA
So, because n have to be an integer, i B have to be a multiple of oA . If it is
not, the Faust compiler ends with this error message :
The oA first inputs of B receive the outputs of A, as for the next o A inputs
of B. . . etc. . .
3.4 Merge
Notation : A :> B
As the notation suggested it, the merge operator does the inverse com-
position than the split operator. This time, that’s the number of inputs of
B, iB , which is lower than the number of outputs of A, o A . So, the signals
from A are added on the inputs of B with the following rule. The first i B
outputs of A go to the B inputs. Then, the next i B outputs of A do the
same, and so on. . . Hence, oA has to be a multiple of iB .
Chapter 3 Composition operators 24
Since there’s the same kind of condition on inputs and outputs number
of the argument boxes as with the split operator, there will the same kind
of error message if those numbers aren’t compatible.
3.5 Recursive
Notation : A ∼ B
The recursive composition is the most complex one. It is useful to create
loops in a program.
This composition has restriction on number of inputs and outputs of
each of the boxes A and B. If iA and oA are the numbers of inputs and
outputs of A, and iB and oB , are the numbers of inputs and outputs of B,
you must have the following relation :
iB ≤ oA
and, oB ≤ iA
iA∼B = iA − oB
oA∼B = oA
The Figure 3.2 gives another example. In this second example we now
have :
3.6 Precedence
Because of the priority of some operators against some others, you can
sometime trim the expressions of its parenthesis. The precedence for each
operator is given in the next table, in the decreasing order, so the first is
the most priority :
So instead of
A = (B,C):>(D~E) ;
But note that the parenthesis clarify the expression so help in maintaining
the code.
To understand very well how the expression you wrote will be interpreted
by the compiler, you also need to know the associativity of the operators :
• a primitive box,
A box name has to begin with a letter, upper or lower-case, and can contain
some figures and underscores ‘ ’. The expression can be written on several
lines. And the order of the definitions doesn’t matter. You can use a box-
name that is defined anywhere, before or after its use. Just beware not to
make any recursive definition.
27
Chapter 4 Structure of a Faust program 28
The definitions are just shortcuts means that each occurrence of a box-
name in expressions are replaced by their associated expression at compila-
tion. A Faust program is really described by the definition of process you
get after this substitution mechanism. The process box can be split up in
boxes, from one to many. Thus, there’s always a lot of ways to write a Faust
program. The unused definitions are ignored.
Remember that the definition mechanism is just a simple naming mech-
anism, very different of the naming mechanism of C ++ that is in fact an
addressing mechanism that links a name to a memory address. There’s
here nothing comparable. This also explains why you can’t use recursive
definitions, eg :
A = B;
B = A;
process = A:B;
This will produce the following error message at compilation :
example.dsp:1: recursive definition of BoxIdent[B]
You need the recursive composition operator ~to do something recursive.
In the same way, you can’t have multiple definitions or re-definitions of a
name. You cannot write :
x = 1;
x = 2;
29 4.2 Comments
In that case, the Faust compiler will use the first definition, but will show
no error nor warning.
4.2 Comments
To make your source code readable, you may want to add some comments
to it. You can use the common C++-like comments // and /* ... */. The
first syntax makes uninterpreted all the characters up to the end of the
line. The second one allows you to make multiple lines comments. All the
characters between /* and */ will not be ridden by the compiler. Note that
such comments are not written in the C ++ output file.
4.3 Abstraction
An abstraction is a way to describe a generic combination of boxes.
Where the expression is a composition of the box1, box2, and maybe other
boxes. Here’s an example of what can be done with abstraction :
A(x,y) = (x,y):(y,x) ;
A(x,y) = (x,y):(y,x) ;
B = ... ; // definition of B
C = ... ; // definition of C
D = A(B,C) ;
will – locally – not be used. The formal parameters names locally mask any
definition with the same name.
You can use some already defined boxes in the definition of the abstrac-
tion.
A/B
m
(A,B ) : /
/( A )
m
( ,A) : /
This notation also works for the boxes you define. For example, let’s
have two definitions :
process = sinus(i);
which is equivalent to :
process = i:sinus;
When the box used as function has several inputs, they are considered as
arguments, separated with comma.
Chapter 5
Compilation
The faust compiler will build a C++ file from the program you wrote and
from a wrapper file you specify, that will encapsulate your Faust program
in a bigger C++ environment.
31
Chapter 5 Compilation 32
-d This option will verbose the compilation process with details, including
the given Faust source file.
Here’s an example :
That line will compile the reverb.dsp Faust file into the reverb.cpp C ++ file,
using the architecture/oss-gtk.cpp wrapper file.
jack-gtk The sound driver is Jack (Jack Audio Connection Kit : http:
//jackit.sourceforge.net). The graphic user interface is GTK.
jack-wx The sound driver is Jack. The graphic user interface is cross-
platform wxWindows (http://www.wxwindows.org).
minimal Just the compilable C++ file. The difference with no wrapper file
is that the virtual class dsp of which mydsp inherits is included in the
C++ file to make it ready-to-compile. This wrapper is handy to use
the stream processor as a module in a bigger C ++ program.
oss-wx The sound driver is OSS. The graphic user interface is wxWindows.
plot There’s no sound driver nor graphic user interface. It displays the
output values in a terminal. The graphical elements (sliders) can be
set with the command line options, by preceding the slider names with
‘ -- ’. If the name contains spaces, you must use single quotes ‘ ’ ’ to
bound the slider name. The special option --n let you choose the
number of samples you want to display.
sndfile There’s no sound driver nor graphic user interface. This wrapper
handle sound files. You must specify the input and output filenames.
The wrapper files are C++ files. You must add ‘.cpp’ to their names to
use them.
where source-file is the name of the C ++ file generated by Faust, and binary-
file is the name of the binary file (executable) you will obtain. libs depends
on the wrapper file you used. You must concatenate the corresponding
options to build the libs statement :
The wrapper files minimal and plot don’t need any specific libraries.
Beware to link the files required by some ffunction( ). The file to link
should be notified in a comment /* Link with ... */ at the beginning
of the C++ file (see section 2.6, page 15).
Part II
Examples
35
Chapter 6
This chapter presents some oscillators that will be used, then, to built
some synthesisers.
Presentation
A symmetric square wave oscillator is the most basic generator that can
be done with Faust. An asymmetric one, with a customisable cyclic ratio is
more interesting.
Faust source code
1 //---------------------------------
2 // A square wave oscillator
3 //---------------------------------
4
Block-diagram
See figure 6.1, page page 38.
Output
See figure 6.2, page 39. The command-line was :
37
Chapter 6 Oscillators and signal generators 38
0.5
-0.5
Therefore, we first provide the period in milliseconds with a slider T (line 5),
and then in number of samples with N (line 6). See figure 6.4.
T = hslider("Period",1,0.1,100.,0.1); // Period (ms)
N = 44100./1000.*T:int; // The period in samples
Line 7, the cyclic ratio can be chosen by the user. This is the percentage
of samples set to 1 during a period. The default value 0.5 will generate a
symmetric square wave. See figure 6.5.
a = hslider("Cyclic ratio",0.5,0,1,0.1); // Cyclic ratio
The signal we generate has one main flaw. The period converted in whole
number of samples. So the signal may not have the exact expected period.
We could have implement a compensation mechanism on several periods.
But such a mechanism would have produce some periods of variable duration
(i.e. a frequency modulation) that induces some uncontrolled additional
harmonics in the signal.
Presentation
This oscillator is the basic sinusoidal oscillator. It uses a rdtable to store
some values of the sin function in order to avoid their calculation during the
process. These values are then read at the speed that is computed from the
frequency parameter. It is supposed here that the sampling rate is 44 100 Hz.
Faust source code
Here’s the Faust source code e13 osc.dsp :
Chapter 6 Oscillators and signal generators 42
1 //-----------------------------------------------
2 // Sinusoidal Oscillator
3 //-----------------------------------------------
4
11 // Oscillator definition
12 //--------------------------
13 tablesize = 40000 ;
14 samplingfreq = 44100. ;
15
24 // User interface
25 //----------------
26 vol = hslider("volume", 0, 0, 1, 0.001);
27 freq = hslider("freq", 400, 0, 15000, 0.1);
28
29 //----------------
30 process = osc(freq) * vol;
Block-diagram
See figure 6.8, page 43.
Output
See figure 6.9, page 44. We used the plot wrapper file to get the values.
This is an easy way to debug when the signal processor has no input. Here’s
the command lines we typed :
to compile the C++ program. The list of flags depends on the wrapper file
you used. For example, if you choose a GTK interface, you’ll need the GTK
libraries.
Finally, we typed :
to launch the program and keep the output values in the file sinus.dat. Then,
GnuPlot made this graphical representation of the array of values.
1 +
+
+
+
++
+
++
++ +
+
+
+
+
++
+
++
++ +
+
+
+
+
++
+
++
++
+
+ +
+ + + + +
+ + + ++ + +
+ + + + + +
+ +
0.8 +
+ +
+ +
+
+ +
+ +
+
+
+
+ + + + +
+ + + +
+ + + + +
+
0.6 +
+ +
+ +
+ + +
+ +
+
+ + + +
+ + + + +
+ +
0.4 +
+
+ + +
+
+
+
+
+
+ + +
+ + + +
+ + +
0.2 + +
+
+
+ +
+ +
+
+ + +
+ + + +
+ + + +
0 + +
+
+
+ +
+ + +
+ +
+ + +
-0.2 +
+ + + +
+ + +
+ + +
+ +
-0.4 + + +
+ +
+
+ + +
+ + +
+ +
-0.6 +
+ +
+ +
+
+
+
+ + +
+ + +
+ + + +
-0.8 +
+ +
+ +
+
+ +
+
+
+ ++ + +
+
+ + +
+ +
+
+ ++ +
+ ++
+
-1 ++
++
+
++ ++
++
+
+
0 50 100 150 200 250
20 float fslider0;
21 float R0 0;
22 float fslider1;
23 float ftbl0[40000];
24 public:
25 virtual int getNumInputs() { return 0; }
26 virtual int getNumOutputs() { return 1; }
27 virtual void init(int samplingRate) {
28 fslider0 = 0.0;
29 R0 0 = 0.0;
30 fslider1 = 400.0;
31 SIG0 sig0;
32 sig0.init(samplingRate);
33 sig0.fill(40000,ftbl0);
34 }
35 virtual void buildUserInterface(UI∗ interface) {
36 interface→openVerticalBox("");
37 interface→addHorizontalSlider("volume", &fslider0,
38 0.0, 0.0, 1.0, 1.000000e-03);
39 interface→addHorizontalSlider("freq", &fslider1,
40 400.0, 0.0, 15000.0, 0.1);
41 interface→closeBox();
42 }
43 virtual void compute (int count, float∗∗ input, float∗∗ output)
44 {
45 float∗ output0 = output[0];
46 float ftemp0 = fslider0;
47 float ftemp1 = (fslider1 / 44100.0);
48 for (int i=0; i<count; i++) {
49 float ftemp2 = (R0 0 + ftemp1);
50 R0 0 = (ftemp2 - floor(ftemp2));
51 output0[i] = (ftbl0[int((R0 0 ∗ 40000))] ∗ ftemp0);
52 }
53 }
54 };
The name that will be replaced by the ffunction expression is sin. The
box it represents acts exactly as sin function. See figure 6.11.
PI = 3.1415926535897932385;
47 6.2 Harmonic oscillator
Then we need two more constants that will be the size of the table in
which we’ll save the values of sinus :
tablesize = 40000 ;
The table-size doesn’t really matter, and you can have good results even
with a very smaller table. Then the sampling-rate constant is defined :
samplingfreq = 44100. ;
See figure 6.14. Note that the table-size is an integer, whereas the sampling
frequency is a float.
Line 16, we defined an ‘infinite’ incremental counter that will give the
number of samples from 0 to n :
The output of time is an integer. It just add 1 to the previous value of the
output. At the first iteration, the delay of one sample due to the recursive
operator (see section 3.5, page 24) will set to 0 the input of the +(1) box
because signals are causal ones. Then the first output of the block +(1) ~ is
0 + 1 = 1. So this block is followed by minus 1 to reset it to 0 at beginning.
The incrementation cannot be infinite. When the maximal integer value
is reached, the output jump to the maximal negative value, and increment
again. Note that the +(1) syntax provides a box that adds 1 to the input
signal. This is a sweetened syntax for ,1:+. See figure 6.15.
Line 17, we defined the stream that will be used to feed the table. We
want to have the values of sinus for a whole period stored in the table. Then
we divide 2π by the table-size and we use time as variable. Thus the table
Chapter 6 Oscillators and signal generators 48
where n is the size of the table. The sin function is used as a box (see
figure 6.16) :
The table we’ll be read with a read-index that we must compute. The
matter is that we always read the table values at the sampling frequency.
Therefore, to generate any frequency, we must skip some values while reading
the table. Thus the read-index is an incremental counter which step depends
49 6.2 Harmonic oscillator
The floor box gives the largest integer not greater the value of the input
stream. In C++ floor(6.04) returns 6.0. Note that the -(floor) block
has two inputs. Then the split operator ‘ <: ’ spread the output of the wire
box on those two inputs. See figure 6.17.
phase(freq) = freq/samplingfreq :
(+ : decimal) ~ : *(tablesize) : int ;
When this abstraction will be used, freq has to be replaced by the definition-
name of the block that defines the frequency. As explained before, the
incrementing step is freq/samplingfreq. This step increments a value
for each sample. This value is then translated between 0 and 1.0 with the
decimal block. The obtained ratio is multiplied by the table-size and casted
to int to make a valid read-index. Then after i iterations, i.e. samples, the
value the table gives with this read-index is :
i f i
sin(2π · · · n) = sin(2π · f · )
n Fs Fs
f . See figure 6.18. The freq is grayed because this is the argument of the
phase abstraction.
Line 22, the oscillator is defined as an abstraction too. It takes the
same argument freq. As described before, we make table of sin values with
rdtable(tablesize,sinwaveform), and we then read it with the read-
index phase(freq).
Note that the syntax used here for the rdtable is equivalent to :
(tablesize,sinwaveform) : rdtable
The third input of the rdtable is connected to phase(freq). See figure 6.19.
Finally, the user interface is defined with two hslider. The first one,
line 26, is the volume of the output signal.
This is just a factor between 0 and 1.0. The default value, that is set when
the program is launched, is 0. Thus the user will have to move the slider to
hear something.
51 6.3 Noise generator
Here the lowest frequency allowed by the interface is 0 Hz. This has no
interest for two reasons. First, we can’t hear frequencies lower than about
15 Hz. Then, because the table only contains 40,000 values. So even if we
read one value at each sample, we’ll get an output signal frequency higher
than 1 Hz.
Finally, the global process is defined by :
We used the slider freq for the argument of the abstraction osc( ), and the
output signal is multiplied by the value the slider vol emits. See figure 6.20.
Presentation
This example presents two way to make some noise. The first one uses the
C++ random() function. The second one uses a simple function that based
on the int property. Because integers can’t be infinite, if you increment
one, at a moment it will wrap to a negative number, and restart to increase.
If you increment this integer of a non-constant value and big enough, you’ll
have a pseudo-randomised numbers sequence.
The Faust program we’ll do compares this to noises. The user interface
has only one button that let the user switch between the two noises.
Faust source code
1 //-----------------------------------------------
2 // Two noises compared
3 //-----------------------------------------------
4
Chapter 6 Oscillators and signal generators 52
5 RANDMAX = 2147483647;
6
10 random2 = (*(1103515245)+12345) ~ ;
11 noise2 = random2 * (1.0/RANDMAX);
12
13 compare(a,b) = (a*(1-button("Switch")) +
14 b*button("Switch"));
15
Block-diagram
See figure 6.21, page 53.
Output
See figure 6.22, page 54. There’s in fact two output streams, but because
they are identical, only one has been represented. The first graph represents
the output when the button is unclicked (with volume set to 0.5), ie the
output of the noise1 block-diagram. The second graph, figure 6.23, page 54
shows the output when the button is clicked.
Graphic user interface
The figure 6.24 shows the user interface it produces with the oss-gtk.cpp
wrapper file.
Comments and explanations
The first algorithm uses the random() C ++ function. In the Faust pro-
gram, it is used in the noise1 block-diagram. The second one is more rustic.
It consists in incrementing a number of a big variable value that will make
wrap the integer to negative values, and so on. This algorithm should use
less resources because there’s no call to any external function.
The compare abstraction is exactly the equivalent of the select2 box
that could have been used instead. The output of the button("Switch")
box is 0 when the button is unclicked, and 1 when it is clicked. Therefore,
the output of compare(a,b) is a when the button is unclicked, and b else.
The output of the resulting block-diagram of compare applied to the two
noises is then multiplied by a volume factor that is interfaced with a slider.
The output is split in two streams, with the split composition operator <:
to have a stereo output. The two channels (left and right) are identical.
53 6.3 Noise generator
0.5
’noise1.dat’
0.4
0.3
0.2
0.1
-0.1
-0.2
-0.3
-0.4
-0.5
0 50 100 150 200 250 300 350 400 450 500
Figure 6.22: 500 first values from 2noises.dsp, with ‘Switch’ button unclicked
0.5
’noise2.dat’
0.4
0.3
0.2
0.1
-0.1
-0.2
-0.3
-0.4
-0.5
0 50 100 150 200 250 300 350 400 450 500
Figure 6.23: 500 first values from 2noises.dsp, with ‘Switch’ button clicked
Karplus-Strong
7.1 Presentation
The KS algorithm was presented by Karplus and Strong in 1983 [Roa96].
This algorithm can generate metallic picked-string sounds, and, by exten-
sion, some metallic sounds (drums, strings. . . ). It has been introduced in a
chipset called Digitar.
1 //-----------------------------------------------
2 // Karplus - Strong
3 //-----------------------------------------------
4
5 // noise generator
6 //---------------------
7 random = (*(1103515245) + 12345) ~ ;
8 RANDMAX = 2147483647 ;
9 noise = random * (1.0/RANDMAX) ;
10
11 // delay lines
12 //---------------------
13 index(n) = & (n-1) ~ +(1) ; // n = 2**i
14 delay(n,d) = n, 0.0, index(n), , (index(n)-int(d))
15 & (n-1) : rwtable ;
16
17 // trigger
18 //--------
55
Chapter 7 Karplus-Strong 56
23 // user interface
24 //-----------------
25 play(n) = button("play"):trigger(n) ;
26 dur = hslider("duration", 128, 2, 512, 1) ;
27 att = hslider("attenuation", 0, -10, 10, 0.1) ;
28
35 process = karplusStrong;
7.3 Block-diagram
The whole block-diagram is to big to be displayed on a single page. Some
definitions and a portion of code are first presented. They are used then in
the diagram of the whole process.
The figure 7.1 page 56 represents the sub-block-diagram noise.
+ : *( (1-bt)/(2+att/100) )
The figure 7.5 page 59 represents the whole block-diagram for process.
The most basic modifier is a mean function. Here the modifier is more
complex. There’s some specific delays in it, and it fades out the sound.
Noise generator
The table must be fed with some new noise values. Here’s the noise
generator used in the e05 karplus strong.dsp, line 7 to 9.
First, we generate an integer random value. The integers are bounded
by a maximal value and a negative minimal value. When the maximal value
is reached while incrementing a variable, it jumps to the minimal value. The
current pseudo-random integers generator uses both a multiplication and an
addition to make a non-constant increment value. See figure 7.8.
The parameters are chosen to avoid any periodic result. In fact, the gener-
ated sequence may be periodic after a long moment.
The sound streams in Faust are floats between −1.0 and +1.0. The
generated random stream is between the max-integer and the min-integer.
So we defined a constant RANDMAX which is used to normalize the stream
(between −1.0 and +1.0). This constant depends on the number of bits on
which the integers are coded. So it depends on the C ++ compiler that will
be used.
RANDMAX = 2147483647 ;
noise = random * (1.0/RANDMAX) ;
See figure 7.1, page 56. Note that the division with RANDMAX has to involve
a float somewhere to make a float division, otherwise, we would always have
get 0. So we could have defined RANDMAX as a float and divide random directly
by RANDMAX to gain one operation. But RANDMAX is a constant. So when we
write (1.0/RANDMAX), it will be simplified by the Faust compiler. The C ++
translation of this portion is :
Chapter 7 Karplus-Strong 62
Delay lines
The recirculation-wave-table in fact uses some delay lines [Roa96]. Those
delay lines are based on rwtables.
We first implemented an incremental index that will be used to read and
write in the rwtable. It will loop-count from 0 to n − 1. This index uses the
bitwise operator & instead of modulo %, so n must be a power of 2 : n = 2 i .
This is an abstraction.
The delay is then defined on a rwtable :
The table is initialised with silence, i.e. the constant stream 0.0. The write
index is index(n). The read index is index(n)-int(d). n is the size of the
table and d is the delay. The read index is reset between 0 and n − 1 with
another &. See figure 7.2, page 57.
Trigger
The user will click the “Play” button for any duration. So we make a
trigger that generates a signal of controlled duration on mouse-down event.
First, we just keep a single pulse at the pulse edge. This is impulse,
line 19 :
The figure 7.9 shows chronograms. The mem box add a delay of one sample
to the input signal. The difference between the original and the dephased
signal produces a signal with two pulses large of one sample at the pulse
edges. The greater-than box just let the first positive pulse.
The abstraction release(n) is used to produce a decreasing slope from
1.0 to 0 and then negative values, and of which duration is given by the
output stream of n. The slope begins when a single pulse comes to the
input.
63 7.5 Comments and explanations
User interface
We first have a “play” button that will key on the sound. This button
is implemented by the abstraction play(n) :
play(n) = button("play"):trigger(n);
The output will be the one of trigger(n). The aim is just to control the
duration of the launching stream that is user event.
Two sliders are then described :
The first one dur is the duration of the Heaviside unit step generated by
trigger. It will replace the formal parameter n in the previous abstractions.
The second one, att is an attenuation factor. It will be used in order to
alter mean used as modifier.
process = karplusStrong;
Part III
Appendices
65
Appendix A
The following bugs and limitations have been reported for the Faust
compiler v1.0a. This list isn’t exhaustive.
A.1 Bugs
• The multiple definitions are not detected. It should be tested if they
are equivalent or at least identical.
• Some automatic casts are not performed by the Faust compiler. For
example, it lets the user make modulo (%) operations with floats, which
will produce an error at the compilation of the C ++ code.
• That’s a small bug but, in the error message ERROR infering write table
type, it should be written inferring.
A.2 Limitations
• The symbolic simplification isn’t accurate enough.
• There’s not any predefined constant. Some useful constants like π and
the sampling-rate will be predefined in the next versions.
• The errors detected during the compilation process (not during the
parse process) cannot be localised due to the simplification mechanism.
67
Appendix A Known bugs and limitations 68
• The compiler only generates a C++ file. The user has to compile it.
The next versions should produce a wrapper-dependent Makefile that
would help compiling.
Appendix B
Error messages
Here are some common error messages that the Faust compiler v1.0a is
susceptible to generate at compilation. There’s two types of errors.
First, there are some syntax errors that are detected during the parsing
operation. Those errors can be localised in the source file. So the error
message begins with the filename and the line number at which the error has
been detected. Some other errors, specific to the language can be detected
at this level.
The second type is some errors that are detected during the compilation.
The Faust compiler gathers all the definition in a single expression that will
be simplified. Therefore, it is impossible to find the origin of the error. The
generated error message will not show the localisation of the error, which
sometime makes the debugging laborious. They are qualified of wriggled
errors because of there elusiveness.
1 box1 = 1
2 box2 = 2;
3 process = box1,box2;
69
Appendix B Error messages 70
1 t = ~ (+(1) ;
2 process = t / 2147483647. ;
1 process = ffunction;
1 process = +)1);
1 process = <:+ ;
The same confusion is possible with the merge operator and greater-than.
• test.dsp:1: undefined symbol BoxIdent[turlututu]
Happens when you use a undefined name.
1 process = turlututu;
1 process = "01001";
71 B.2 Wriggled errors
1 A = B;
2 B = A;
3 process = A:B;
The message is repeated for the second line and cause another one. So here
is the complete error message :
1 process = rdtable;
• ERROR infering read table type, wrong write index type : RSEVN
Actually, this error message should be “inferring” and “wrong read index
type”.
• ERROR infering write table type, wrong write index type : RSEVN
The same for rwtable.
Appendix B Error messages 72
Bibliography
[OFL02] Y. Orlarey, D. Fober, and S. Letz, “An algebra for block diagram
languages,” in Proceedings of International Computer Music Conference
(ICMA, ed.), pp. 542–547, 2002.
73
Index
checkbox, 18
+, -, *, /, %, 12 coercion, see cast
/* */, //, 29 comments, 29
<<, >>, 13 compilation, 31–34
<, >, <=, >=, !=, ==, 13 C++, 33
&, |, ^, 13 options, 31
, 11 composition operators, 21
!, 11 ∼ , see recursive
, , see parallel
abstraction, 29 :> , see merge
array, see table : , see serial
<: , see split
block-diagram, 27
merge, 23
primitives, see box
parallel, 22
box
precedence, 25
arithmetic operators, 12
recursive, 24
bitwise comparison op., 13
serial, 21
bitwise shift op., 13
split, 22
casting, 14
constants, 13
comparison operators, 13
constants, 13 definition, 27
cut (!), 11
delay, 15 error messages, 69
foreign function, 14 localised, 69
GUI, 18 wriggled, 71
identity ( ), 11 expression, 27
memories, 15
primitives, 11–20 ffunction, 14
read-only table, 15 float, 14
read-write table, 16 function, 30
selection switch, 17 graphic user interface, 18
wire, see identity group, 19
buffer, 31
bugs, 67 hgroup, 19
button, 18 hslider, 19
cast, 14 int, 14
74
75 Index
limitations, 67–68
literals, see constants
mem, 15
nentry, 19
numbers, see constants
operators
arithmetic, 12, 30
box, 12
composition, 21
oscillators
harmonic, 41
noise, 51
square, 37
rdtable, 15
rwtable, 16
sampling-rate, 67
select2, 17
select3, 17
slider, 19
syntactic sugar, 30
table, 15–17
tgroup, 20
types, 14
vgroup, 19
vslider, 19