Faust Soft Computing
Faust Soft Computing
Abstract This paper presents some syntactical and seman- will also describe the complete set of primitives of the lan-
tical aspects of FAUST (Functional AUdio STreams), a pro- guage.
gramming language for real-time sound processing and syn- In section 4 we will give a concrete example of usage
thesis. The programming model of FAUST combines two ap- of Faust with the Karplus-Strong algorithm. In section 5 we
proaches : functional programming and block-diagrams com- will give an overview of how the Faust compiler works. We
position. It is based on a block-diagram algebra. It has a well will end the paper with some concluding remarks and future
defined formal semantic and can be compiled into efficient directions of work.
C/C++ code.
2.1 Definitions
2.3 Parallel composition (A, B)
Let B be a set of primitive blocks (this set is leaved undefined
at this stage) and let D be the set of block-diagrams build on The parallel composition operator associates two block-dia-
top of B. A block-diagram D ∈ D is either an identity block grams one on top of the other, without connections. The in-
( ), a cut block (!), a primitive block B ∈ B, or composition puts (resp. the outputs) of (A, B) are the inputs (resp. the
of two block-diagrams based on one of the five composition outputs) of A and B as defined in the following rule :
operator of the algebra. More formally a block-diagram D ∈
D is defined recursively by the following syntactic rule : A : iA → oA B : iB → oB
(A, B) : iA + iB → oA + oB
D = |! | B | (D1 : D2 ) | (D1 , D2 )
| (D1 <: D2 ) | (D1 :> D2 ) | (D1 ∼ D2 )
The sequential composition operator is used to connect the 2.4 Split composition (A <: B)
outputs of A to the corresponding inputs of B (such that
Aout [i] is connected to Bin [i]). The inputs of (A : B) are This split composition operator is used to distribute the out-
the inputs of A and the outputs of (A : B) are the outputs of puts of A to several inputs of B. It requires that the number
B. of inputs of B is an exact multiple of the number of outputs
If the number of inputs and outputs are not the same, of A. For example if A has 3 outputs and B has 6 inputs,
the exceeding outputs of A (resp. the exceeding inputs of B) then each output of A will be connected to 2 inputs of B. The
form additional outputs (resp. inputs) of the resulting block- general rule is that, if A : iA → m and B : oA ∗ k → q
diagram (see figure 1). The number of inputs and outputs of then Aout [i] is connected to Bin [i + j ∗ oA ] where j < k.
the resulting block-diagram is given by the following three The inputs (resp. the outputs) of (A <: B) are the inputs of
rules : A (resp. the outputs of B) as defined in the following rule :
A : iA → oA B : iB → oB oA = iB A : iA → oA B : oA ∗ k → oB
(A : B) : iA → oB (A <: B) : iA → oB
3 2.6 Recursive composition (A ∼ B)
A : iA → oA B : iB → oB oB ≤ iA iB ≤ oA
(A ∼ B) : iA − oB → oA
A : iA → iB ∗ k B : iB → oB The identity block and cut block are typically used to cre-
(A :> B) : iA → oB ate complex routings. For example to cross two connections,
that is to connect Aout [0] to Bin [1] and Aout [1] to Bin [0] one
can write :
A : ( , <:!, , , !) : B
Our block-diagram algebra is related to Gh. Stefanescu [6] 3.1.2 Constant Signals A signal is a constant signal if it
Algebra of Flownomials (AoF) proposed to represent directed always delivers the same value : ∃v ∈ R, ∀t, s(t) = v. We
flowgraphs (blocks diagrams in general including flowcharts) notate Sk ⊂ S the subset of constant signals.
and their behaviors.
The AoF is presented as an extension of Kleene’s calculus
3.1.3 Integer Signals A signal is an integer signal if it al-
of regular expressions. It is based on three operations and var-
ways delivers integer values : ∀t, s(t) ∈ Z. We notate Si ⊂ S
ious constants used to describe the branching structure of the
the subset of integer signals. Moreover we have Si = N → Z.
flowgraphs. They all have a direct translation into our BDA
as shown table 1.
Although the AoF and the BDA are equivalent in that 3.1.4 Constant Integer Signals A signal is an constant in-
they can both represent any block-diagram, the AoF lacks the teger signal if it always delivers the same integer value :
high-level composition operations offered by the BDA and it ∃k ∈ Z, ∀t, s(t) = k. We notate Sik ⊂ S the subset of con-
is less suited for a practical programming language. stant integer signals. We have Sik = Si ∩ Sk .
3.1.7 Semantic function In order to explicitly refer to the 3.2.3 Split composition In the split composition, the output
mathematical meaning of a block-diagram D and to distin- signals of D1 : n → m are duplicated k times and distributed
guish it from its syntactic representation we will used seman- to the inputs of D2 : m.k → p :
tic brackets : [[ ]]. The notation [[ D ]] means : the signal pro-
cessor represented by block-diagram D. Therefore [[ . ]] as a [[D1 ]](x1 , . . . xn ) = (s1 , . . . sm )
1 k
semantic function translating block-diagrams into signal pro- z }| { z }| {
cessors : [[ . ]] : D → P. [[D2 ]](s1 , . . . sm , . . . , s1 , . . . sm ) = (y1 , . . . yp )
[[D1 <: D2 ]](x1 , . . . xn ) = (y1 , . . . yp )
3.2 Semantic of the Block-Diagram Algebra 3.2.4 Merge composition In the merge composition, the out-
put signals of D1 : n → m.k are added together by groups of
k signals and sent to the corresponding input of D2 : m → p
In the previous section we have informally presented the topo- :
logical semantic of the Block-Diagram Algebra, how things
are connected, but without any references to the actual mean- [[D1 ]](x1 , . . . xn ) = (s1 , . . . sm.k )
Pk−1 Pk−1
ing of these connections. In this paragraph we will define the [[D2 ]]( j=0 (s1+j.m ), . . . j=0 (sm+j.m )) = (y1 , . . . , yp )
signal processing semantic of the various composition oper-
[[D1 :> D2 ]](x1 , . . . xn ) = (y1 , . . . yp )
ators.
[[D1 ]](x1 , . . . , xn ) = (y1 , . . . , ym ) The set B of Faust primitives follows as much as possible
[[D2 ]](s1 , . . . , so ) = (t1 , . . . , tp ) the set of C/C++ operators. In order to guarantee the role of
[[D1 , D2 ]](x1 , . . . , xn , s1 , . . . , so ) = (y1 , . . . , ym , t1 , . . . , tp ) signal processing specification language of Faust, typical sig-
nal processing operations are not part of the primitives. They
The associativity holds also for the parallel composition : are typically implemented in Faust and provided as external
(D1 , D2 ), D3 = D1 , (D2 , D3 ) libraries.
Yann Orlarey et al.
6
[[ @ ]] : S × Sik → S
[[ ./ ]] : S2 → S
[[ @ ]](x, d) = (y)
[[ ./ ]](s1 , s2 ) = (y)
y(t + d(t)) = x(t)
1 if s1 (t) ./ s2 (t)
y(t) =
0 else The mem represent a 1-sample delay :
[[ mem ]] : S → S
3.3.3 Bitwise primitives Bitwise primitives corresponding [[ mem ]](x) = (y)
to the five C/C++ operators <<, >>, &, |, ∧ are also pro- y(t + 1) = x(t)
vided. Again the semantics scheme of each of these primi-
tives is the same. For an operation ? ∈ {<<, >>, &, |, ∧} We have ∀s, [[ mem ]](x) = [[ @ ]](x, 1).
we have :
[[ ? ]] : S2 → S
[[ ? ]](s1 , s2 ) = (y)
y(t) = s1 (t) ? s2 (t)
Fig. 10 The mem box represents a one sample delay
[[ rdtable ]] : Sik × S × Si → S
[[ rdtable ]](n, v, i) = (y)
y(t) = v(i(t))
Fig. 11 The read-only table primitive 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.
[[ select2 ]] : Si × S2 → S
[[ select2 ]](i, s[0], s[1]) = (y)
y(t) = s[i(t)](t) Fig. 15 The checkbox primitive box
The index signal i is such that ∀t, i(t) ∈ {O, 1}. The select3
box is exactly the same except that it selects between 3 sig- Here’s the syntax :
nals :
checkbox("label")
[[ select3 ]] : Si × S3 → S
The slider boxes hslider (horizontal) and vslider
[[ select3 ]](i, s[0], s[1], s[2]) = (y)
(vertical) provide some powerful controls for the parameters.
y(t) = s[i(t)](t)
Here’s the syntax :
Fig. 18 The Faust implementation The resonator uses a delay line implemented using a rwtable
(see figure 21). It averages two consecutive samples with de-
lay d and d − 1 and feeds back the result with an attenuation
a into the table.
index(n) = &(n-1)∼+(1);
4.1 The noise generator delay(n,d) = rwtable(n, 0.0, index(n), ,
(index(n)-int(d)) & (n-1)) ;
The noise generator is based on a very simple random num- resonator(d,a) = (+ <: (delay(4096, d-1)
+ delay(4096, d))/2.0)∼*(1.0-a) ;
ber generator which values are scaled down between −1 and
+1. The following Faust definitions correspond to the block-
diagram of figure 19.
4.4 Putting all together
random = (*(1103515245)+12345) ∼ ;
noise = random *(1.0/2147483647.0); The description in now almost complete. We can put all the
pieces together with the user interface description and define
9 5.1 The compilation process
5.1.6 Simplification and normalization The signal expres- Because of it simple and well defined semantic, the lan-
sions are rearranged and simplified by executing all the com- guage can be easily compiled into efficient C++ code. Pre-
putations that can be done at compilation time producing sig- liminary performance tests on the generated code are encour-
nal expressions in normal form. aging. In some cases the Faust code significantly outperforms
hand-written code.
5.1.7 Sharing of recursive signals The previous steps may The compiler can be improved in several ways, in par-
have produced different, but α-equivalent, representations of ticular by extending its capacities of symbolic simplification
recursive signals. Because they are syntactically different they and normalization and by improving the generation of SIMD
are not automatically shared by the hash-consing technique. code.
This step replaces all α-equivalent subtrees with a common The semantic of the language can also be enlarged. Right
shared subtree. now Faust only deals with scalar signals. The next step is
to add arbitrary data types in particular vectors and matrices
5.1.8 Reuse annotation The subtrees are annotated with a which are needed for image and video processing as well as
reuse flag indicating if the computation should be stored in spectral based transformations.
a temporary variable to be later reused. The notion of reuse
can be quite subtle. It concerns expressions that have several
occurrences in the global expression (space reuse), but also References
expressions with only one occurrence but in a higher speed
context (time reuse). 1. J. B. Dennis and D. P. Misunas. A computer architecture for
highly parallel signal processing. In Proceedings of the ACM
1974 National Conference, pages 402–409. ACM, November
5.1.9 Code generation The compiler generates a C++ class 1974.
that implements the Faust specification. This class may be 2. G. Kahn. The semantics of a simple language for parallel pro-
wrapped into an architecture code that implements a specific gramming. In Proceedings of the IFIP Congress 74. North-
type of application or plugin format. The compiler can op- Holland, 1974.
tionally generate SIMD code using either ALTIVEC or SSE2 3. K. Karplus and A. Strong. Digital synthesis of plucked-string
intrinsics. and drum timbres. Computer Music Journal, 7(2):43–55, 1983.
4. E. A. Lee and T. M. Parks. Dataflow process networks. In Pro-
ceedings of the IEEE, volume 83, pages 773–801, May 1995.
5. Y. Orlarey, D. Fober, and S. Letz. An algebra for block diagram
5.2 Implementations and performances
languages. In ICMA, editor, Proceedings of International Com-
puter Music Conference, pages 542–547, 2002.
An implementation of the Faust compiler, written in C++, is 6. Gheorghe Stefanescu. The algebra of flownomials part 1: Binary
available at sourceforge (http://faudiostream.sourceforge.net). flownomials; basic theory. Report, Technical University Munich,
The code is quite portable and only depends on Lex and Yacc November 1994.
for the parser code. 7. Robert Stephens. A survey of stream processing. Acta Informat-
The performance of the code generate by the compiler is ica, 34(7):491–541, 1997.
quite good. In order to compare it with hand written code we
have reimplemented in Faust two audio effects:Freeverb, a
well known reverb written in C++, and Tapiir a multitap delay
also written in C++. Both applications are freely available on
Internet with the source code.
In both case, the Faust specification is far more compact
than the C++ code. The speed of both versions of the Free-
verb are equivalents (but the speed of the Faust version using
SIMD code generation is about twice faster). The Faust ver-
sion of Tapiir is twice faster than the original version even in
scalar mode.
6 Conclusion