Implementing a Polyphonic MIDI Software using Coroutines, Realtime Garbage Collection, Closures, Auto-Allocated Variables, Dynamic Scoping, and Continuation Passing Style Programming

Kjetil Matheussen Norwegian Center for Technology in and the Arts. (NOTAM) [email protected]

Abstract however the programming only concerns han- This paper demonstrates a few programming tech- dling blocks of samples where we only control a niques for low-latency sample-by-sample audio pro- signal graph, there are several high level alter- gramming. Some of them may not have been used natives which makes it relatively easy to do a for this purpose before. The demonstrated tech- straightforward implementation of a MIDI soft niques are: Realtime memory allocation, realtime synth. Examples of such high level music pro- garbage collector, storing instrument data implicitly gramming systems are SuperCollider [McCart- in closures, auto-allocated variables, handling signal ney, 2002], Pd [Puckette, 2002], CSound4 and buses using dynamic scoping, and continuation pass- many others. ing style programming. But this paper does not describe use of block processing. In this paper, all samples are in- Keywords dividually calculated. The paper also explores Audio programming, realtime garbage collection, possible advantages of doing everything, alloca- coroutines, dynamic scoping, Continuation Passing tion, initialization, scheduling, etc., from inside Style. the realtime audio thread. At least it looks like everything is performed 1 Introduction inside the realtime audio thread. The under- lying implementation is free to reorganize the This paper demonstrates how to implement a code any way it wants, although such reorga- MIDI software synthesizer (MIDI soft synth) nizing is not performed in Snd-RT yet. using some unusual audio programming tech- Future work is making code using these tech- niques. The examples are written for Snd-RT niques perform equally, or perhaps even better, [Matheussen, 2008], an experimental audio pro- than code where allocation and initialization of gramming system supporting these techniques. data is explicitly written not to run in the real- The techniques firstly emphasize convenience time audio thread. (i.e. few lines of code, and easy to read and modify), and not performance. Snd-RT1 runs on top of Snd2 which again runs on top of the 2 MIDI software synthesizer Scheme interpreter Guile.3 Guile helps gluing The reason for demonstrating a MIDI soft synth all parts together. instead of other types of music programs such It is common in music programming only to as a generator or a reverb, is compute the sounds themselves in a realtime that the behavior of a MIDI soft synth is well priority thread. Scheduling new notes, alloca- known, plus that a MIDI soft synth contains tion of data, data initialization, etc. are usually many common challenges in audio and music performed in a thread which has a lower prior- programming: ity than the audio thread. Doing it this way 1. Generating samples. To hear sound, we helps to ensure constant and predictable CPU need to generate samples at the Audio usage for the audio thread. But writing code Rate. that way is also more complicated. At least, when all samples are calculated one by one. If 2. Handling Events. MIDI data are read at a rate lower than the audio rate. This rate is 1 http://archive.notam02.no/arkiv/doc/snd-rt/ commonly called the Control Rate. 2http://ccrma.stanford.edu/software/snd/ 3http://www.gnu.org/software/guile/guile.html 4http://www.csound.com 3. Variable polyphony. Sometimes no notes coroutine to make it run. The spawned are playing, sometimes maybe 30 notes are coroutine will run automatically as soon6 playing. as the current coroutine yields (by calling yield or wait), or the current coroutine 4. Data allocation. Each playing note re- ends. quires some data to keep track of frequency, phase, envelope position, volume, etc. The Coroutines are convenient in music pro- challenges are; How do we allocate memory gramming since it often turns out practi- for the data? When do we allocate memory cal to let one dedicated coroutine handle for the data? How do we store the memory only one voice, instead of mixing the voices holding the data? When do we initialize manually. Furthermore, arbitrarily placed the data? pauses and breaks are relatively easy to im- plement when using coroutines, and there- 5. Bus routing. The sound coming from the fore, supporting dynamic control rate simi- tone generators is commonly routed both lar to ChucK [Wang and Cook, 2003] comes through an envelope and a reverb. In ad- for free. dition, the tones may be autopanned, i.e. panned differently between two loudspeak- (wait n) waits n number of frames before con- ers depending on the note height (similar tinuing the execution of the current corou- to the direction of the sound coming from tine. a piano or a pipe organ). (sound ...) spawns a special kind of coroutine where the code inside sound is called one time per sample. (sound coroutines are 3 Common Syntax for the Examples stored in a tree and not in a priority queue The examples are written for a variant of since the order of execution for sound the programming language Scheme [Steele and coroutines depends on the bus system and Sussman, 1978]. Scheme is a functional lan- not when they are scheduled to wake up.) guage with imperative operators and static A simple version of the sound macro, scoping. called my-sound, can be implemented like A number of additional macros and special this: operators have been added to the language, and some of them are documented here because of (define-stalin-macro (my-sound . body) the examples later in the paper. ‘(spawn (while #t ,@body (...) is a macro which first trans- (wait 1)))) forms the code inside the block into clean R4RS code [Clinger and Rees, 1991] un- However, my-sound is inefficient compared 5 derstood by the Stalin Scheme compiler. to sound since my-sound is likely to do (Stalin Scheme is an R4RS compiler). Af- a coroutine context switch at every call ter Stalin is finished compiling the code, the to wait.7 sound doesn’t suffer from this produced object file is dynamically linked problem since it is run in a special mode. into Snd-RT and scheduled to immediately This mode makes it possible to run tight run inside the realtime audio thread. loops which does not cause a context switch (define-stalin signature ...) defines variables until the next scheduled event. and functions which are automatically in- (out sample) sends out data to serted into the generated Stalin scheme the current bus at the current time. (the code if needed. The syntax is similar to current bus and the current time can be define. thought of as global variables which are im- (spawn ...) spawns a new coroutine [Conway, plicitly read from and written to by many 1963; Dahl and Nygaard, 1966]. Corou- 6Unless other coroutines are placed earlier in the tines are stored in a priority queue and it is queue. not necessary to explicitly call the spawned 7I.e. if two or more my-sound blocks or sound blocks run simultaneously, and at least one of them is a my- 5Stalin - a STAtic Language ImplementatioN, sound block, there will be at least two coroutine context http://cobweb.ecn.purdue.edu/ qobi/software.html. switches at every sound frame. operators in the system)8 By default, the :where is just another way to declare local current bus is connected to the sound card, variables. For example, but this can be overridden by using the (+ 2 b in macro which is explained in more de- :where b 50) tail later. If the channel argument is omitted, the is another way of writing sample is written both to channel 0 and 1. (let ((b 50)) It makes sense only to use out inside a (+ 2 b)) sound block. The following example plays a 400Hz sine sound to the sound card: There are three reason for using :where instead of let. The first reason is that ( the use of :where requires less parenthe- (let ((phase 0.0)) sis. The second reason is that reading the (sound code sometimes sounds more natural this (out (sin phase)) (inc! phase (hz->radians 400))))) way. (I.e “add 2 and b, where b is 50” in- stead of “let b be 50, add 2 and b”.) The (range varname start end ...) is a simple lo- third reason is that it’s sometimes easier op iterator macro which can be imple- to understand the code if you know what mented like this:9 you want to do with a variable, before it is defined. (define-macro (range varname start end . body) (define loop (gensym)) 4 Basic MIDI Soft Synth ‘(let ,loop ((,varname ,start)) (cond ((<,var ,end) We start by showing what is probably the sim- ,@body plest way to implement a MIDI soft synth: (,loop (+ ,varname 1))))))

(range note-num 0 128 (wait- :options ...) waits until MIDI data ( is received, either from an external inter- (define phase 0.0) face, or from another program. (define volume 0.0) (sound wait-midi has a few options to specify the (out (* volume (sin phase)))) kind of MIDI data it is waiting for. In the (inc! phase (midi->radians note-num))) examples in this paper, the following op- (while #t tions for wait-midi are used: (wait-midi :command note-on :note note-num (set! volume (midi-vol))) :command note-on (wait-midi :command note-off :note note-num Only wait for a note on MIDI message. (set! volume 0.0)))) :command note-off This program runs 128 instruments simul- Only wait for a note off MIDI mes- taneously. Each instrument is responsible for sage. playing one tone. 128 variables holding volume :note number are also used for communicating between the Only wait for a note which has MIDI parts of the code which plays sound (running at note number number. the audio rate), and the parts of the code which reads MIDI information (running at the control 10 Inside the wait-midi block we also have rate ). access to data created from the incom- There are several things in this version which ing midi event. In this paper we use are not optimal. Most important is that you (midi-vol) for getting the velocity (con- 10Note that the control rate in Snd-RT is dynamic, verted to a number between 0.0 and 1.0), similar to the music programming system ChucK. Dy- and (midi-note) for getting the MIDI namic control rate means that the smallest available note number. time-difference between events is not set to a fixed num- ber, but can vary. In ChucK, control rate events are 8 Internally, the current bus is a coroutine-local vari- measured in floating numbers, while in Snd-RT the mea- able, while the current time is a global variable. surement is in frames. So In Chuck, the time difference 9The actual implementation used in Snd-RT also can be very small, while in Snd-RT, it can not be smaller makes sure that “end” is always evaluated only one time. than 1 frame. would normally not let all instruments play all to free memory used by sounds not playing the time, causing unnecessary CPU usage. You anymore to avoid running out of memory. would also normally limit the polyphony to a Luckily though, freeing memory is taken care fixed number, for instance 32 or 64 simultane- of automatically by the Rollendurchmesserzeit- ously sounds, and then immediately schedule sammler garbage collector, so we don’t have new notes to a free instrument, if there is one. to do anything special:

1| (define-stalin (softsynth) 5 Realtime Memory Allocation 2| (while #t 3| (wait-midi :command note-on As mentioned, everything inside 4| (define osc (make-oscil :freq (midi->hz (midi-note)))) 5| (define tone (sound (out (* (midi-vol) (oscil osc))))) runs in the audio realtime thread. Allocating 6| (spawn memory inside the audio thread using the OS al- 7| (wait-midi :command note-off :note (midi-note) 8| (stoptone)))))) location function may cause surprising glitches 9| 10| ( in sound since it is not guaranteed to be an 11| (softsynth)) O(1) allocator, meaning that it may not al- ways spend the same amount of time. There- In this program, when a note-on message is fore, Snd-RT allocates memory using the Rol- received at line 3, two coroutines are scheduled: lendurchmesserzeitsammler [Matheussen, 2009] garbage collector instead. The memory alloca- 1. A sound coroutine at line 5. tor in Rollendurchmesserzeitsammler is not only 2. A regular coroutine at line 6. running in O(1), but it also allocates memory extremely efficiently. [Matheussen, 2009] Afterwards, the execution immediately jumps In the following example, it’s clearer that in- back to line 3 again, ready to schedule new strument data are actually stored in closures notes. 11 which are allocated during runtime. In ad- So the MIDI soft synth is still polyphonic, dition, the 128 spawned coroutines themselves and contrary to the earlier versions, the CPU require some memory which also needs to be is now the only factor limiting the number of allocated: simultaneously playing sounds.12

( (range note-num 0 128 7 Auto-Allocated Variables (spawn (define phase 0.0) In the following modification, the (define volume 0.0) CLM [Schottstaedt, 1994] oscillator oscil (sound will be implicitly and automatically allocated (out (* volume (sin phase)))) (inc! phase (midi->radians note-num))) first time the function oscil is called. After (while #t the generator is allocated, a pointer to it is (wait-midi :command note-on :note note-num stored in a special memory slot in the current (set! volume (midi-vol))) (wait-midi :command note-off :note note-num coroutine. (set! volume 0.0))))) Since oscil is called from inside a sound coroutine, it is natural to store the generator in 6 Realtime Garbage Collection. the coroutine itself to avoid all tones using the (Creating new instruments only same oscillator, which would happen if the auto- when needed) allocated variable had been stored in a global variable. The new definition of softsynth now The previous version of the MIDI soft synth did looks like this: allocate some memory. However, since all mem- ory required for the lifetime of the program were (define-stalin (softsynth) (while #t allocated during startup, it was not necessary to (wait-midi :command note-on free any memory during runtime. (define tone (sound (out (* (midi-vol) But in the following example, we simplify the (oscil :freq (midi->hz (midi-note))))))) code further by creating new tones only when (spawn (wait-midi :command note-off :note (midi-note) they are needed. And to do that, it is necessary (stop tone)))))) 11Note that memory allocation performed before any sound block can easily be run in a non-realtime thread 12Letting the CPU be the only factor to limit before scheduling the rest of the code to run in realtime. polyphony is not necessarily a good thing, but doing so But that is just an optimization. in this case makes the example very simple. The difference between this version and the similar functionality) are necessary, so it would previous one is subtle. But if we instead look be nice to have a better way to handle them. at the reverb instrument in the next section, it would span twice as many lines of code, and the 9 Routing Signals with Dynamic code using the reverb would require additional Scoping. (Getting rid of manually logic to create the instrument. handling sound buses) A slightly less verbose way to create, read and 8 Adding Reverb. (Introducing write signal buses is to use dynamic scoping signal buses) to route signals. The bus itself is stored in a A MIDI soft synth might sound unprofessional coroutine-local variable and created using the or unnatural without some reverb. In this ex- in macro. ample we implement John Chowning’s reverb13 Dynamic scoping comes from the fact that and connect it to the output of the MIDI soft out writes to the bus which was last set up synth by using the built-in signal bus system: by in. In other words, the scope for the current bus (the bus used by out) follows (define-stalin (reverb input) the execution of the program. If out isn’t ( :size (* .013 (mus-srate)) (somehow) called from in, it will instead write (+ (comb :scaler 0.742 :size 9601 allpass-composed) (comb :scaler 0.733 :size 10007 allpass-composed) to the bus connected to the soundcard. (comb :scaler 0.715 :size 10799 allpass-composed) (comb :scaler 0.697 :size 11597 allpass-composed) :where allpass-composed For instance, instead of writing: (send input :through (all-pass :feedback -0.7 :feedforward 0.7) (define-stalin bus (make-bus)) (all-pass :feedback -0.7 :feedforward 0.7) (all-pass :feedback -0.7 :feedforward 0.7) (define-stalin (instr1) (all-pass :feedback -0.7 :feedforward 0.7))))) (sound (write-bus bus 0.5)))

(define-stalin bus (make-bus)) (define-stalin (instr2) (sound (write-bus bus -0.5))) (define-stalin (softsynth) (while #t ( (wait-midi :command note-on (instr1) (define tone (instr2) (sound (sound (write-bus bus (out (read-bus bus)))) (* (midi-vol) (oscil :freq (midi->hz (midi-note))))))) (spawn we can write: (wait-midi :command note-off :note (midi-note) (stop tone)))))) (define-stalin (instr1) (sound (out 0.5))) (define-stalin (fx-ctrl input clean wet processor) (+ (* clean input) (define-stalin (instr2) (* wet (processor input)))) (sound (out -0.5)))

( ( (spawn (sound (softsynth)) (out (in (instr1) (sound (instr2))))) (out (fx-ctrl (read-bus bus) 0.5 0.09 reverb)))) What happened here was that the first time in was called in the main block, it spawned a Signal buses are far from being an “unusual new coroutine and created a new bus. The new technique”, but in text based languages they coroutine then ran immediately, and the first are in disadvantage compared to graphical mu- thing it did was to change the current bus to sic languages such as Max [Puckette, 2002] or the newly created bus. The in macro also made Pd. In text based languages it’s inconvenient to sure that all sound blocks called from within write to buses, read from buses, and most im- the in macro (i.e. the ones created in instr1 and portantly; it’s hard to see the signal flow. How- instr2 ) is going to run before the main sound ever, signal buses (or something which provides block. (That’s how sound coroutines are stored in a tree) 13as implemented by Bill Schottstaedt in the file ”jc-reverb.scm” included with Snd. The fx-ctrl function When transforming the MIDI soft synth to is a copy of the function fxctrl implemented in Faust’s use in instead of manually handling buses, it Freeverb example. will look like this: ;; source, but used two times from the same corou- ;; Don’t need the bus anymore: tine, both channels will use the same coroutine- (define-stalin bus (make-bus)) local variables used by the reverb; a delay, four ;; softsynth reverted back to the previous version: (define-stalin (softsynth) comb filters and four all-pass filters. (while #t There are a few ways to work around this (wait-midi :command note-on (define tone problem. The quickest work-around is to re- (sound (out (* (midi-vol) code ’reverb’ into a macro instead of a function. (oscil :freq (midi->hz (midi-note))))))) (spawn However, since neither the problem nor any so- (wait-midi :command note-off :note (midi-note) (stop tone)))))) lution to the problem are very obvious, plus that it is slower to use coroutine-local variables than ;; A simpler main block: ( manually allocating them (it requires extra in- (sound structions to check whether the data has been (out (fx-ctrl (in (softsynth)) 14 0.5 0.09 allocated ), it’s tempting not to use coroutine- reverb)))) local variables at all. 10 CPS Sound Generators. (Adding Instead we introduce a new concept called CPS Sound Generators, where CPS stands for stereo reverb and autopanning) Continuation Passing Style. [Sussman and Using coroutine-local variables was convenient Steele, 1975] in the previous examples. But what happens if we want to implement autopanning and (a very simple) stereo reverb, as illustrated by the 10.1 How it works graph below? Working with CPS Sound Generators are simi- lar to Faust’s Block Diagrams composition. [Or- +-- reverb -> out ch 0 larey et al., 2004] A CPS Sound Generator can / also be seen as similar to a Block Diagram in softsynth--< Faust, and connecting the CPS Sound Genera- \ tors is quite similar to Faust’s Block Diagram +-- reverb -> out ch 1 Algebra (BDA). CPS Sound Generators are CPS functions First, lets try with the tools we have used so which are able to connect to other CPS Sound far: Generators in order to build up a larger function (define-stalin (stereo-pan input c) for processing samples. The advantage of build- (let* ((sqrt2/2 (/ (sqrt 2) 2)) (angle (- pi/4 (* c pi/2))) ing up a program this way is that we know what (left (* sqrt2/2 (+ (cos angle) (sin angle)))) data is needed before starting to process sam- (right (* sqrt2/2 (- (cos angle) (sin angle))))) (out 0 (* input left)) ples. This means that auto-allocated variables (out 1 (* input right)))) don’t have to be stored in coroutines, but can (define-stalin (softsynth) be allocated before running the sound block. (while #t (wait-midi :command note-on For instance, the following code is written in (define tone generator-style and plays a 400Hz sine sound to (sound (stereo-pan (* (midi-vol) the sound card: (oscil :freq (midi->hz (midi-note)))) (/ (midi-note) 127.0)))) (let ((Generator (let ((osc (make-oscillator :freq 400))) (spawn (lambda (kont) (wait-midi :command note-off :note (midi-note) (kont (oscil osc)))))) (stop tone)))))) (sound (Generator (lambda (sample) ( (out sample))))) (sound (in (softsynth) (lambda (sound-left sound-right) The variable kont in the function Generator (out 0 (fx-ctrl sound-left 0.5 0.09 reverb)) is the continuation, and it is always the last ar- (out 1 (fx-ctrl sound-right 0.5 0.09 reverb)))))) gument in a CPS Sound Generator. A continua- At first glance, it may look okay. But tion is a function containing the rest of the pro- the reverb will not work properly. The rea- gram. In other words, a continuation function son is that auto-generated variables used for 14It is possible to optimize away these checks, but coroutine-local variables are identified by their doing so either requires restricting the liberty of the position in the source. And since the code programmer, some kind of JIT-compilation, or doing a for the reverb is written only one place in the whole-program analysis. will never return. The main reason for program- (gen-sound :options generator) is the same ming this way is for generators to easily return as writing more than one sample, i.e have more than one output.15 (let ((gen generator)) (sound :options Programming directly this way, as shown (gen (lambda (result0) above, is not convenient, so in order to make (out 0 result0))))) programming simpler, some additional syntax ...when the generator has one output. If have been added. The two most common oper- the generator has two outputs, it will look ators are Seq and Par, which behave similar to 16 like this: the ’:’ and ’,’ infix operators in Faust. (let ((gen generator)) Seq creates a new generator by connecting gen- (sound :options (gen (lambda (result0 result1) erators in sequence. In case an argument is (out 0 result0) not a generator, a generator will automat- (out 1 result1))))) ically be created from the argument. ...and so forth. For instance, (Seq (+ 2)) is the same as writing The Snd-RT preprocessor knows if a variable or expression is a CPS Sound Generator by (let ((generator0 (lambda (arg1 kont0) (kont0 (+ 2 arg1))))) looking at whether the first character is capitol. (lambda (input0 kont1) For instance, (Seq (Process 2)) is equal to (generator0 input0 kont1))) (Process 2), while (Seq (process 2)) is equal to and (Seq (+ (random 1.0)) (+ 1)) is the (lambda (input kont) (kont (process 2 input))), same as writing regardless of how ’Process’ and ’process’ are defined. (let ((generator0 (let ((arg0 (random 1.0))) (lambda (arg1 kont0) (kont0 (+ arg0 arg1))))) 10.2 Handling auto-allocated variables (generator1 (lambda (arg1 kont1) (kont1 (+ 1 arg1))))) oscil and the other CLM generators are macros, (lambda (input kont2) (generator0 input (lambda (result0) and the expanded code for (oscil :freq 440) looks (generator1 result0 kont2))))) like this: ;; Evaluating ((Seq (+ 2) (+ 1)) 3 display) ;; will print 6! (oscil_ (autovar (make_oscil_ 440 0.0)) 0 0)

Par creates a new generator by connecting gen- Normally, autovar variables are translated erators in parallel. Similar to Seq, if an into coroutine-local variables in a separate step argument is not a generator, a generator performed after macro expansion. However, using the argument will be created auto- when an auto-allocated variable is an argu- matically. ment for a generator, the autovar surrounding For instance, (Par (+ (random 1.0)) (+ 1)) is removed. And, similar to other arguments is the same as writing: which are normal function calls, the initializa- tion code is placed before the generator func- (let ((generator0 (let ((arg0 (random 1.0))) tion. For example, (Seq (oscil :freq 440)) is ex- (lambda (arg1 kont0) 17 (kont0 (+ arg0 arg1))))) panded into: (generator1 (lambda (arg1 kont1) (kont1 (+ 1 arg1))))) (let ((generator0 (let ((var0 (make_oscil_ 440 0.0))) (lambda (input2 input3 kont1) (lambda (kont) (generator0 input2 (kont (oscil_ var0 0 0)))))) (lambda (result0) (lambda (kont) (generator1 input3 (generator0 kont))) (lambda (result1) (kont1 result0 result1)))))))

;; Evaluating ((Par (+ 2)(+ 1)) 3 4 +) will return 10! 17Since the Snd-RT preprocessor doesn’t know the number of arguments for normal functions such as oscil , 15 Also note that by inlining functions, the Stalin this expansion requires the preprocessor to know that scheme compiler is able to optimize away the extra syn- this particular Seq block has 0 inputs. The preprocessor tax necessary for the CPS style. should usually get this information from the code calling 16Several other special operators are available as well, Seq, but it can also be defined explicitly, for example like but this paper is too short to document all of them. this: (Seq 0 Cut (oscil :freq 440)). 10.3 The Soft Synth using CPS Sound ming. Generators 13 Acknowledgments (define-stalin (Reverb) (Seq (all-pass :feedback -0.7 :feedforward 0.7) Thanks to the anonymous reviewers and Anders (all-pass :feedback -0.7 :feedforward 0.7) Vinjar for comments and suggestions. (all-pass :feedback -0.7 :feedforward 0.7) (all-pass :feedback -0.7 :feedforward 0.7) (Sum (comb :scaler 0.742 :size 9601) References (comb :scaler 0.733 :size 10007) (comb :scaler 0.715 :size 10799) William Clinger and Jonathan Rees. 1991. (comb :scaler 0.697 :size 11597)) (delay :size (* .013 (mus-srate))))) Revised Report (4) On The Algorithmic Lan- guage Scheme. (define-stalin (Stereo-pan c) (Split Identity (* left) Melvin E. Conway. 1963. Design of a sepa- (* right) rable transition-diagram compiler. Commu- :where left (* sqrt2/2 (+ (cos angle) (sin angle))) :where right (* sqrt2/2 (- (cos angle) (sin angle))) nications of the ACM, 6(7):396–408. :where angle (- pi/4 (* c pi/2)) :where sqrt2/2 (/ (sqrt 2) 2))) O.-J. Dahl and K. Nygaard. 1966. SIMULA:

(define-stalin (softsynth) an ALGOL-based simulation language. Com- (while #t munications of the ACM, 9(9):671–678. (wait-midi :command note-on (define tone (gen-sound Kjetil Matheussen. 2008. Realtime Music (Seq (oscil :freq (midi->hz (midi-note))) Programming Using Snd-RT. In Proceedings (* (midi-vol)) (Stereo-pan (/ (midi-note) 127))))) of the International Music Confer- (spawn ence. (wait-midi :command note-off :note (midi-note) (stop tone)))))) Kjetil Matheussen. 2009. Conservative Gar- (define-stalin (Fx-ctrl clean wet Fx) gage Collectors for Realtime Audio Process- (Sum (* clean) (Seq Fx ing. In Proceedings of the International Com- (* wet)))) puter Music Conference, pages 359–366 (on- ( line erratum). (gen-sound (Seq (In (softsynth)) James McCartney. 2002. Rethinking the (Par (Fx-ctrl 0.5 0.09 (Reverb)) (Fx-ctrl 0.5 0.09 (Reverb)))))) Language: SuperCollider. Computer Music Journal, 26(2):61–68. 11 Adding an ADSR Envelope Y. Orlarey, D. Fober, and S. Letz. 2004. Syn- tactical and semantical aspects of faust, soft And finally, to make the MIDI soft synth sound computing. decent, we need to avoid clicks caused by sud- denly starting and stopping sounds. To do this, Miller Puckette. 2002. Max at Seventeen. we use a built-in ADSR envelope generator (en- Computer Music Journal, 26(4):31–43. tirely written in Scheme) for ramping up and W. Schottstaedt. 1994. Machine Tongues down the volume. Only the function softsynth XVII: CLM: Music V Meets Common Lisp. needs to be changed: Computer Music Journal, 18(2):30–37. (define-stalin (softsynth) Jr. Steele, Guy Lewis and Gerald Jay (while #t (wait-midi :command note-on Sussman. 1978. The Revised Report on (gen-sound :while (-> adsr is-running) SCHEME: A Dialect of LISP. Technical Re- (Seq (Prod (oscil :freq (midi->hz (midi-note))) (midi-vol) port 452, MIT. (-> adsr next)) (Stereo-pan (/ (midi-note) 127)))) Gerald Jay Sussman and Jr. Steele, (spawn Guy Lewis. 1975. Scheme: An inter- (wait-midi :command note-off :note (midi-note) (-> adsr stop))) preter for extended lambda calculus. In :where adsr (make-adsr :a 20:-ms Memo 349, MIT AI Lab. :d 30:-ms :s 0.2 :r 70:-ms)))) Ge Wang and Perry Cook. 2003. ChucK: a Concurrent and On-the-fly Audio Program- 12 Conclusion ming Language, Proceedings of the ICMC. This paper has shown a few techniques for doing low-latency sample-by-sample audio program-