DSP Programming with Faust, Q and SuperCollider Yann Orlarey, Albert Graef, Stefan Kersten
To cite this version:
Yann Orlarey, Albert Graef, Stefan Kersten. DSP Programming with Faust, Q and SuperCollider. Linux Audio Conference, 2006, Karlsruhe, Germany. hal-02158894
HAL Id: hal-02158894 https://hal.archives-ouvertes.fr/hal-02158894 Submitted on 18 Jun 2019
HAL is a multi-disciplinary open access L’archive ouverte pluridisciplinaire HAL, est archive for the deposit and dissemination of sci- destinée au dépôt et à la diffusion de documents entific research documents, whether they are pub- scientifiques de niveau recherche, publiés ou non, lished or not. The documents may come from émanant des établissements d’enseignement et de teaching and research institutions in France or recherche français ou étrangers, des laboratoires abroad, or from public or private research centers. publics ou privés. DSP Programming with Faust, Q and SuperCollider
Yann ORLAREY Albert GRAF¨ Stefan KERSTEN Grame, Centre National Dept. of Music Informatics Dept. of Communication de Creation Musicale Johannes Gutenberg University Science Lyon, France Mainz, Germany Technical University [email protected] [email protected] Berlin, Germany [email protected]
Abstract diagram syntax. The functional programming Faust is a functional programming language for real- approach provides a natural framework for sig- time signal processing and synthesis that targets nal processing. Digital signals are modeled as high-performance signal processing applications and discrete functions of time, and signal processors audio plugins. The paper gives a brief introduction as second order functions that operate on them. to Faust and discusses its interfaces to Q, a general- Moreover Faust block-diagram composition op- purpose functional programming language, and Su- erators, used to combine signal processors to- perCollider, an object-oriented sound synthesis lan- gether, fit in the same picture as third order guage and engine. functions. Keywords Faust is a compiled language. The compiler Computer music, digital signal processing, Faust translates Faust programs into equivalent C++ programming language, functional programming, Q programs. It uses several optimization tech- programming language, SuperCollider niques in order to generate the most efficient code. The resulting code can usually compete 1 Introduction with, and sometimes outperform, DSP code di- Faust is a programming language for real-time rectly written in C. It is also self-contained and signal processing and synthesis that targets doesn’t depend on any DSP runtime library. high-performance signal processing applications Thanks to specific architecture files, a single and audio plugins. This paper gives a brief in- Faust program can be used to produce code for a troduction to Faust, emphasizing practical ex- variety of platforms and plugin formats. These amples rather than theoretic concepts which can architecture files act as wrappers and describe be found elsewhere (Orlarey et al., 2004). the interactions with the host audio and GUI A Faust program describes a signal proces- system. Currently more than 8 architectures sor, a DSP algorithm that transforms input are supported (see Table 1) and new ones can signals into output signals. Faust is a func- be easily added. tional programming language which models sig- nals as functions (of time) and DSP algorithms alsa-gtk.cpp ALSA application as higher-order functions operating on signals. jack-gtk.cpp JACK application Faust programs are compiled to efficient C++ sndfile.cpp command line application code which can be included in C/C++ appli- ladspa.cpp LADSPA plugin cations, and which can also be executed ei- max-msp.cpp Max MSP plugin ther as standalone programs or as plugins in supercollider.cpp Supercollider plugin other environments. In particular, in this pa- vst.cpp VST plugin per we describe Faust’s interfaces to Q, an in- q.cpp Q language plugin terpreted, general-purpose functional program- ming language based on term rewriting (Gr¨af, Table 1: The main architecture files available 2005), and SuperCollider (McCartney, 2002), for Faust the well-known object-oriented sound synthesis language and engine. In the following subsections we give a short and informal introduction to the language 2 Faust through two simple examples. Interested read- The programming model of Faust combines a ers can refer to (Orlarey et al., 2004) for a more functional programming approach with a block- complete description. 2.1 A simple noise generator 2.2 Invoking the compiler A Faust program describes a signal processor The role of the compiler is to translate Faust by combining primitive operations on signals programs into equivalent C++ programs. The √ (like +, −, ∗, /, , sin, cos,...) using an algebra key idea to generate efficient code is not to com- of high level composition operators (see Table pile the block diagram itself, but what it com- 2). You can think of these composition opera- putes. tors as a generalization of mathematical func- Driven by the semantic rules of the language tion composition f ◦ g. the compiler starts by propagating symbolic sig- nals into the block diagram, in order to discover f ∼ g recursive composition how each output signal can be expressed as a f , g parallel composition function of the input signals. f : g sequential composition These resulting signal expressions are then f <: g split composition simplified and normalized, and common subex- f :> g merge composition pressions are factorized. Finally these expres- sions are translated into a self contained C++ Table 2: The five high level block-diagram com- class that implements all the required computa- position operators used in Faust tion. To compile our noise generator example we A Faust program is organized as a set of use the following command : definitions with at least one for the keyword process (the equivalent of main in C). $ faust noise.dsp Our noise generator example noise.dsp only involves three very simple definitions. But it also shows some specific aspects of the language: This command generates the C++ code in Figure 1. The generated class con- random = +(12345) ~ *(1103515245); tains five methods. getNumInputs() and noise = random/2147483647.0; getNumOutputs() return the number of input process = noise * checkbox("generate"); and output signals required by our signal pro- cessor. init() initializes the internal state of The first definition describes a (pseudo) ran- the signal processor. buildUserInterface() dom number generator. Each new random num- can be seen as a list of high level commands, ber is computed by multiplying the previous one independent of any toolkit, to build the user by 1103515245 and adding to the result 12345. interface. The method compute() does the ac- The expression +(12345) denotes the op- tual signal processing. It takes 3 arguments: the eration of adding 12345 to a signal. It is number of frames to compute, the addresses of an example of a common technique in func- the input buffers and the addresses of the out- tional programming called partial application: put buffers, and computes the output samples the binary operation + is here provided with according to the input samples. only one of its arguments. In the same way The faust command accepts several options *(1103515245) denotes the multiplication of a to control the generated code. Two of them signal by 1103515245. are widely used. The option -o outputfile spec- The two resulting operations are recursively ifies the output file to be used instead of the composed using the ∼ operator. This opera- standard output. The option -a architecturefile tor connects in a feedback loop the output of defines the architecture file used to wrap the +(12345) to the input of *(1103515245) (with generate C++ class. an implicit 1-sample delay) and the output of For example the command faust -a q.cpp *(1103515245) to the input of +(12345). -o noise.cpp noise.dsp generates an exter- The second definition transforms the random nal object for the Q language, while faust -a signal into a noise signal by scaling it between jack-gtk.cpp -o noise.cpp noise.dsp gen- -1.0 and +1.0. erates a standalone Jack application using the Finally, the definition of process adds a simple GTK toolkit. user interface to control the production of the Another interesting option is -svg that gen- sound. The noise signal is multiplied by a GUI erates one or more SVG graphic files that rep- checkbox signal of value 1.0 when it is checked resent the block-diagram of the program as in and 0.0 otherwise. Figure 2. class mydsp : public dsp { private:
int R0_0; float fcheckbox0;
public:
virtual int getNumInputs() { return 0; } virtual int getNumOutputs() { return 1; } virtual void init(int samplingFreq) { fSamplingFreq = samplingFreq; Figure 2: Graphic block-diagram of the noise R0_0 = 0; generator produced with the -svg option fcheckbox0 = 0.0; } virtual void buildUserInterface(UI* ui) { 2.3.1 The noise generator ui->openVerticalBox("faust"); We simply reuse here the noise generator of the ui->addCheckButton("generate", previous example (subsection 2.1). &fcheckbox0); ui->closeBox(); random = +(12345) ~ *(1103515245); } noise = random/2147483647.0; virtual void compute (int count, float** input, float** output) { 2.3.2 The trigger float* output0; output0 = output[0]; The trigger is used to transform the signal de- float ftemp0 = 4.656613e-10f*fcheckbox0; livered by a user interface button into a pre- for (int i=0; i
For that purpose we first transforms the but- 2.3 The Karplus-Strong Algorithm ton signal into a 1-sample impulse correspond- ing to the raising front of the button signal. Karplus-Strong is a well known algorithm first Then we add to this impulse a kind of release presented by Karplus and Strong in 1983 that will decrease from 1.0 to 0.0 in exactly (Karplus and Strong, 1983). Whereas not com- n samples. Finally we produce a control sig- pletely trivial, the principle of the algorithm nal which is 1.0 when the signal with release is is simple enough to be described in few lines greater than 0.0. of Faust, while producing interesting metallic All these steps are combined in a four stages plucked-string and drum sounds. sequential composition with the operator ’:’. The sound is produced by an impulse of noise 2.3.3 The resonator that goes into a resonator based on a delay line The resonator uses a variable delay line imple- with a filtered feedback. The user interface con- mented using a table of samples. Two consecu- tains a button to trigger the sound production, tive samples of the delay line are averaged, at- as well as two sliders to control the size of both tenuated and fed back into the table. the resonator and the noise impulse, and the amount of feedback. index(n) = &(n-1) ~ +(1); delay(n,d,x)= rwtable( n, 0.0, index(n), itself have any facilities for other tasks typi- x, (index(n)-int(d))&(n-1) ); cally encountered in signal processing and syn- average(x) = (x+mem(x))/2; thesis programs, such as accessing the operating resonator(d,a) = (+ : delay(4096, d-1)) system environment, real-time processing of au- ~ (average : *(1.0-a)); dio and MIDI data, or presenting a user inter- face for the application. Thus, as we already 2.3.4 Putting it all together discussed in the preceding section, all Faust- The last step is to put all the pieces together generated DSP programs need a supporting in- in a sequential composition. The parameters of frastructure (embodied in the architecture file) the trigger and the resonator are controlled by which provides those bits and pieces. two user interface sliders. One of the architectures included in the Faust distribution is the Q language interface. Q dur = hslider("duration",128,2,512,1); is an interpreted functional programming lan- att = hslider("attenuation", 0.1,0,1,0.01); guage which has the necessary facilities for do- process = noise ing general-purpose programming as well as soft : *(trigger(dur)) real-time processing of MIDI, OSC a.k.a. Open : resonator(dur,att); Sound Control (Wright et al., 2003) and au- dio data. The Q-Faust interface allows Faust A screen shot of the resulting application DSPs to be loaded from a Q script at runtime. (compiled with the jack-gtk.cpp architecture) From the perspective of the Faust DSP, Q acts is reproduced in Figure 3. It is interesting to as a programmable supporting environment in note that despite the fact that the duration which it operates, whereas in Q land, the DSP slider is used twice, it only appears once in the module is used as a “blackbox” to which the user interface. The reason is that Faust enforces script feeds chunks of audio and control data, referential transparency for all expressions, in and from which it reads the resulting audio out- particular user interface elements. Things are put. By these means, Q and Faust programs can uniquely and unequivocally identified by their be combined in a very flexible manner to im- definition and naming is just a convenient short- plement full-featured software synthesizers and cut. For example in the following program, other DSP applications. process always generate a null signal: In this section we give a brief overview of the Q-Faust interface, including a simple but foo = hslider("duration", 128, 2, 512, 1); complete monophonic synthesizer example. For faa = hslider("duration", 128, 2, 512, 1); lack of space, we cannot give an introduction process = foo - faa; to the Q language here, so instead we refer the reader to (Gr¨af, 2005) and the extensive documentation available on the Q website at http://q-lang.sf.net. 3.1 Q module architecture Faust’s side of the Q-Faust interface consists of the Q architecture file, a little C++ code tem- plate q.cpp which is used with the Faust com- piler to turn Faust DSPs into shared modules which can be loaded by the Q-Faust module at runtime. This file should already be included in all recent Faust releases, otherwise you can also Figure 3: Screenshot of the Karplus-Strong ex- find a copy of the file in the Q-Faust distribution ample generated with the jack-gtk.cpp archi- tarball. tecture Once the necessary software has been in- stalled, you should be able to compile a Faust DSP to a shared module loadable by Q-Faust 3 Faust and Q as follows: Faust is tailored to DSP programming, and as such it is not a general-purpose program- $ faust -a q.cpp -o mydsp.cpp mydsp.dsp ming language. In particular, it does not by $ g++ -shared -o mydsp.so mydsp.cpp Note: If you want to load several different This will return another list of byte strings, DSPs in the same Q script, you have to make containing the 32 bit float samples produced by sure that they all use distinct names for the the DSP on its output channels, being fed with mydsp class. With the current Faust version this the given input data. can be achieved most easily by just redefining Some DSPs (e.g., synthesizers) don’t actually mydsp, to whatever class name you choose, dur- take any audio input, in this case you just spec- ing the C++ compile stage, like so: ify the number of samples to be generated in- stead: $ g++ -shared -Dmydsp=myclassname -o mydsp.so mydsp.cpp ==> faust_compute DSP 1024
3.2 The Q-Faust module Most DSPs also take additional control in- The compiled DSP is now ready to be used in put. The control variables are listed in the UI the Q interpreter. A minimal Q script which component of the faust_info return value. For just loads the DSP and assigns it to a global instance, suppose that there is a “Gain” param- variable looks as follows: eter listed there, it might look as follows:
/***********************************************/ smooth(c) = *(1-c) : +~*(c); freq N = 440*2^((N-69)/12); voice = gate : adsr(attk, decy, sust, rels) : gain V = V/127; *(osci(freq)+0.5*osci(2*freq)+ 0.25*osci(3*freq)) : process (_,_,_,note_on _ N V) *(gain : smooth(0.999)); = put FREQ (freq N) || put GAIN (gain V) || process = vgroup("synth", voice : *(vol)); put GATE 1 if V>0; = put GATE 0 if freq N = get FREQ; Figure 5: Faust source for the monophonic midi_loop = process (midi_get IN) || midi_loop; synth example audio_loop = write_audio_stream OUT (faust_compute SYNTH BUFSZ!0) || audio_loop; event to the corresponding control settings. In this simple example it does nothing more than /***********************************************/ responding to note on and off messages (as usual, a note off is just a note on with veloc- def POL = SCHED_RR, PRIO = 10; realtime = setsched this_thread POL PRIO; ity 0). The example also illustrates how MIDI messages are represented as an “algebraic” data synth = writes "Hit