ebook img

LAC 2011 - Paper: Compiling Signal Processing Code embedded in Haskell via LLVM PDF

2011·0.14 MB·English
Save to my drive
Quick download
Download
Most books are stored in the elastic cloud where traffic is expensive. For this reason, we have a limit on daily download.

Preview LAC 2011 - Paper: Compiling Signal Processing Code embedded in Haskell via LLVM

An LLVM-based Signal-Processing-Compiler embedded in Haskell Henning Thielemann Institut fu¨r Informatik, Martin-Luther-Universita¨t Halle-Wittenberg, Von-Seckendorff-Platz 1, 06110 Halle, Germany, [email protected] Abstract exponential We discuss a programming language for real-time audiosignalprocessingthatisembeddedinthefunc- tional language Haskell and uses the Low-Level Vir- tual Machine as back-end. With that framework we can code with the comfort and type safety of oscillator amplifier Haskell while achieving maximum efficiency of fast inner loops and full vectorisation. This way Haskell becomes a valuable alternative to special purpose Figure 1: Data flow for creation of a very sim- signal processing languages. ple percussive sound Keywords amplify Functional progamming, Haskell, Low-Level Virtual (exponential halfLife amp) machine, Embedded Domain Specific Language (osci Wave.saw phase freq) 1 Introduction Figure 2: Functional expression for the dia- Given a data flow diagram as in Figure 1 we gram in Figure 1 want to generate an executable machine pro- gram. First we must (manually) translate the • a representation of an LLVM loop body diagram to something that is more accessible that can be treated like a signal, described by a machine. Since we can translate data in Section 3.1, flows almost literally to function expressions, • a way to describe causal signal processes, we choose a functional programming language which is the dominant kind of signal trans- as the target language, here Haskell [Pey- formations in real-time audio processing ton Jones, 1998]. The result can be seen in andwhichallowsustocopeefficientlywith Figure 2. The second step is to translate the multiple uses of outputs and with feedback function expression to a machine oriented pre- of even small delays, guaranteed deadlock- sentation. This is the main concern of our pa- free, developed in Section 3.2, per. • and the handling of internal filter param- Since we represent signals as sequences of eters in a way that is much more flexible numbers, signal processing algorithms are usu- than traditional control rate/sample rate ally loops that process these numbers one after schemes, presented in Section 3.3. another. Thus our goal is to generate efficient loop bodies from a functional signal processing Duetospaceconstraintsweomittedsomeparts, representation. We have chosen the Low-Level like the use of vector arithmetic and according Virtual-Machine (LLVM) [Lattner and Adve, benchmarks, that you can find in [Thielemann, 2004] for the loop description, because LLVM 2010b]. provides a universal representation for machine 2 Background languages of a wide range of processors. The LLVM library is responsible for the third step, We want to generate LLVM code from a sig- namely the translation of portable virtual ma- nalprocessingalgorithmwritteninadeclarative chine code to actual machine code of the host way. We like to write code close to a data flow processor. diagram and the functional paradigm seems to Our contributions are be appropriate. We could design a new language specifically Java objects, we convert Haskell expressions for this purpose, but we risk the introduction to LLVM bitcode, send it to the LLVM Just-In- of design flaws. We could use an existing signal Time (JIT) compiler and then execute the re- processing language, but usually they do not sultingcode. Wecanfreelyexchangesignaldata scale well to applications other than signal pro- between pure Haskell code and LLVM gener- cessing. Alternatively we can resort to an ex- ated code. isting general purpose functional programming The EDSL approach is very popular among language or a subset of it, and write a compiler Haskell programmers. For instance interfaces with optimisations adapted to signal processing to the Csound signal processing language [Hu- needs. But writing a compiler for any modern dak et al., 1996] and the real-time software syn- “real-world” programming language is a task of thesiserSuperCollider[Drape, 2009]arewritten several years, if not decades. A compiler for a this way. This popularity can certainly be at- subset of an existing language however would tributed to the concise style of writing Haskell make it hard to interact with existing libraries. expressions and to the ease of overloading num- So we can still tune an existing compiler for an ber literals and arithmetic operators. We shall existing language, but given the complexity of note that the EDSL method has its own short- modern languages and their respective compil- comings,mostnotablythesharing problem that ers this is still a big effort. It might turn out we tackle in Section 3.2. that a change that is useful for signal process- In [Thielemann, 2004] we have argued exten- ing kills performance for another application. sively, why we think that Haskell is a good A much quicker way to adapt a language to a choice for signal processing. Summarised, the special purpose is the Embedded Domain Spe- key features for us are polymorphic but strong cificLanguage(EDSL)approach[Landin,1966]. static typing and lazy evaluation. Strong typ- Inthisterminology“embedded”means,thatthe ing means that we have a wide range of types domain specific (or “special purpose”) language thatthecompilercandistinguishbetween. This is actually not an entirely new language, but way we can represent a trigger or gate signal by a way to express domain specific issues using a sequence of boolean values (type Bool) and corresponding constructs and checks of the host this cannot be accidentally mixed up with a language. For example, writing an SQL com- PCM signal (sample type Int8), although both mand as string literal in Java and sending it to types may be represented by bytes internally. adatabase, is notanEDSL.In contrasttothat, We can also represent internal parameters of Hibernate [Elliott, 2004] is an EDSL, because signal processes by opaque types that can be it makes database table rows look like ordinary stored by the user but cannot be manipulated Javaobjectsanditmakestheuseofforeignkeys (cf. Section 3.3). Polymorphic typing means safe and comfortable by making foreign refer- that we can write a generic algorithm that can ences look like Java references. be applied to single precision or double preci- In the same way we want to cope with signal sionfloatingpointnumbers, tofixedpointnum- processing in Haskell. In the expression bers or complex numbers, to serial or vectorised signals. Static typing means that the Haskell amplify compiler can check that everything fits together (exponential halfLife amp) when compiling a program or parts of it. Lazy (osci Wave.saw phase freq) evaluation means, that we can transform audio the call to osci shall not produce a signal, but data, as it becomes available, while program- instead it shall generate LLVM code that be- ming in a style, that treats those streams, as if comes part of a signal generation loop later. In they would be available at once. thesameway amplifyassemblesthecodeparts The target of our embedded compiler is producedbyexponentialandoscianddefines LLVM. It differs from Csound and SuperCol- the product of their results as its own result. In lider in that LLVM is not a signal processing the end every such signal expression is actually system. It is a high-level assembler and we a high-level LLVM macro and finally, we pass it have to write the core signal processing build- to a driver function that compiles and runs the ingblocksourselves. However,oncethisisdone, code. Where Hibernate converts Java expres- assembling those blocks is as simple as writing sions to SQL queries, sends them to a database CsoundorchestrafilesorSuperColliderSCLang and then converts the database answers back to programs. We could have chosen a concrete machine language as target, but LLVM does a of LLVM’s virtual registers, namely V Float or much better job for us: It generates machine V Double, respectively. The types V and Code code for many different processors, thus it can areimportedfromaHaskellinterfacetoLLVM be considered a portable assembler. It also sup- [O’Sullivan and Augustsson, 2010]. Their real ports the vector units of modern processors and namesareValueandCodeGenFunction,respec- target dependent instructions (intrinsics) and tively. provides us with a large set of low-level to high- The type parameter is not restricted in level optimisations, that we can even select and any way, thus we can implement a generator arrange individually. We can run LLVM code of type Generator (V Float, V Float) for a immediately from our Haskell programs (JIT), stereosignal generatororGenerator (V Bool, but we can also write LLVM bitcode files for V Float)foragatesignalandacontinuoussig- debugging or external usage. nal that are generated synchronously. We do not worry about a layout in memory of an ac- 3 Implementation cording signal at this point, since it may be just We are now going to discuss the design of our an interim signal that is never written to mem- implementation [Thielemann, 2010a]. ory. E.g. the latter of the two types just says, that the generated samples for every call to the 3.1 Signal generator generator can be found in two virtual registers, In our design a signal is a sequence of sample whereoneregisterholdsabooleanandtheother values and a signal generator is a state transi- one a floating point number. tion system, that ships a single sample per re- We like to complement this general descrip- quest while updating the state. E.g. the state tion with the simple example of an exponential of an exponential curve is the current ampli- curve generator. tude and on demand it returns the current am- plitude as result while decreasing the amplitude exponential :: state by a constant factor. In the same way an Float -> Float -> Generator (V Float) oscillator uses the phase as internal state. Per exponential halfLife amp = request it applies a wave function on the phase (valueOf amp, and delivers the resulting value as current sam- \y0 -> do ple. Additionally it increases the phase by the y1 <- mul y0 (valueOf oscillator frequency and wraps around the re- (2**(-1/halfLife))) sult to the interval [0,1). This design is much return (valueOf True, (y0, y1))) inspired by [Coutts et al., 2007]. For simplification we use the fixed type Float According to this model we define an LLVM but in the real implementation the type is flex- signalgeneratorinHaskellessentially asapair ible. The implementation is the same, only the of an initial state and a function, that returns real type of exponential is considerably more a tuple containing a flag showing whether there complicated because of many constraints to the aremoresamplestocome,thegeneratedsample type parameters. and the updated state. ThefunctionvalueOfmakesaHaskellvalue type Generator a = forall state. available as constant in LLVM code. Thus the (state, power computation with ** in the mul instruc- state -> Code (V Bool, (a, state))) tion is done by Haskell and then implanted into the LLVM code. This also implies that Please note, that the actual type definition in the power is computed only once. The whole the library is a bit different and much larger for transition function, that is the second element technical reasons. of the pair, is a lambda expression, also known The lower-case identifiers are type variables as anonymous function. It starts with a back- that can be instantiated with actual types. The slash and its argument y0, which identifies the variable a is for the sample type and state for virtual register, that holds the current internal the internal state of the signal generator. Since state. It returns always True because the curve Generator is not really a signal but a descrip- never terminates and it returns the current am- tion for LLVM code, the sample type cannot plitude y0 as current sample and the updated be just a Haskell number type like Float or amplitude computed by a multiplication to be Double. Instead it must be the type for one found in the register identified by y1. We have seen, how basic signal generators that manages a pointer into this buffer as inter- work,however,signalprocessingconsistslargely nal state. This generator has a real-world use of transforming signals. In our framework a sig- when reading a signal from a file. We see that naltransformationisactuallyageneratortrans- our model of signal generators does not impose formation. That is, we take apart given gener- a restriction on the kind of signals, but it well ators and build something new from them. For restricts the access to the generated data: We examplethecontrolledamplifierdissectstheen- can only traverse from the beginning to the end velope generator and the input generator and of the signal without skipping any value. This assembles a generator for the amplified signal. is however intended, since we want to play the signals in real-time. amplify :: Generator (V Float) -> 3.2 Causal Processes Generator (V Float) -> While the above approach of treating signal Generator (V Float) transformations as signal generator transforma- amplify (envInit, envTrans) tions is very general, it can be inefficient. For (inInit, inTrans) = example, for a signal generator x the expression ((envInit, inInit), mix x x does not mean that the signal repre- (\(e0,i0) -> do sented by x is computed once and then mixed (eCont,(ev,e1)) <- envTrans e0 with itself. Instead, the mixer runs the signal (iCont,(iv,i1)) <- inTrans i0 generator x twice and adds the results of both y <- mul ev iv instances. I like to call that the sharing prob- cont <- and eCont iCont lem. It is inherent to all DSLs that are embed- return (cont, (y, (e1,i1))))) ded into a purely functional language, since in thoselanguagesobjectshavenoidentity,i.e.you So far our signals only exist as LLVM code, cannot obtain an object’s address in memory. but computing actual data is straightforward: The sharing problem also occurs, if we process render :: the components of a multi-output signal pro- Generator (V Float) -> cess individually, for instance the channels of V Word32 -> V (Ptr Float) -> a stereo signal or the lowpass, bandpass, high- Code (V Word32) pass components of a state variable filter. E.g. render (start, next) size ptr = do for delaying the right channel of a stereo sig- (pos,_) <- arrayLoop size ptr start $ nal we have to write stereo (left x) (delay \ ptri s0 -> do (right x)) and we run into the sharing prob- (cont,(y,s1)) <- next s0 lem, again. ifThen cont () (store y ptri) We see two ways out: The first one is relying return (cont, s1) on LLVM’s optimiser to remove the duplicate ret pos code. HoweverthismayfailsinceLLVMcannot remove duplicate code if it relies on seemingly The ugly branching that is typical for assembly independentstates,oninteractionwithmemory languages including that of LLVM is hidden in or even on interaction with the outside world. our custom functions arrayLoop and ifThen. Another drawback is that the temporarily gen- Haskell makes a nice job as macro assembler. erated code may grow exponentially compared Again, we only present the most simple case to the code written by the user. E.g. in here. The alternative to filling a single buffer with signal data is to fill a sequence of chunks, let y = mix x x that are created on demand. This is called z = mix y y lazy evaluation and one of the key features of in mix z z Haskell. At this point, we might wonder, whether the the generator x is run eight times. presented model of signal generators is gen- The second way out is to store the results eral enough to match all kinds of signals, that of a generator and share the storage amongst can appear in real applications. The answer is all users of the generator. We can do this by “yes”, since given a signal there is a generator rendering the signal to a lazy list, or preferably that emits that signal. We simply write the sig- to a lazily generated list of chunks for higher nal to a buffer and then use a signal generator, performance. This approach is a solution to the general case and it would also work if there are (valueOf n, signal processes involved that shrink the time \(a,toDo) -> do line, like in mix x (timeShrink x). cont <- icmp IntULT (valueOf 0) toDo Whilethisworksinthegeneralcase,thereare stillToDo <- sub toDo (valueOf 1) manycaseswhereitisnotsatisfying. Especially return (cont, (a, stillToDo))) in the example mix x x we do not really need Thefunctionapplyforapplyingacausalpro- to store the result of x anywhere, since it is cess to a signal generator has the signature consumed immediately by the mixer. Storing the result is at least inefficient in case of a plain apply :: Causal a b -> Haskell singly linked list and even introduces Generator a -> Generator b higher latency in case of a chunk list. and its implementation is straightforward. The So what is the key difference between mix x function is necessary to do something useful x and mix x (timeShrink x)? It is certainly, with causal processes, but it loses the causal- that in the first case data is processed in a syn- ity property. For sharing we want to make use chronousway. Thusitcanbeconsumed(mixed) offactslikethattheserialcompositionofcausal as it is produced (generated by x). However, processesiscausal,too,butifwehavetoexpress the approach of signal transformation by signal the serial composition of processes f and g by generator transformation cannot model this be- apply f (apply g x), then we cannot make haviour. When considering the expression mix useofsuchlaws. Thesolutionistocombinepro- x (f x) we have no idea whether f maintains cesses with processes rather than transforma- the “speed” of its argument generator. That is, tions with signals. E.g. with >>> denoting the we need a way to express that f emits data syn- serial composition we can state that g >>> f is chronously to its input. For instance we could a causal process. define In the base Haskell libraries there is already type Map a b = a -> Code b the Arrow abstraction, that was developed for the design of integrated circuits in the Lava that represents a signal transformation of type project, but it proved to be useful for many Generator a -> Generator b. Itcouldbeap- other applications. The Arrow type class pro- plied to a signal generator by a function apply vides a generalisation of plain Haskell func- with type tions. For making Causal an instance of Arrow we must provide the following minimal set of Map a b -> Generator a -> Generator b methods and warrant the validity of the arrow and where we would have written f x before, laws [Hughes, 2000]. we would write apply f x instead. arr :: (a -> b) -> Causal a b It turns out that Map is too restrictive. Our (>>>) :: Causal a b -> signal process would stay synchronous if we al- Causal b c -> Causal a c low a running state as in a recursive filter and if first :: Causal a b -> Causal(a,c)(b,c) weallowterminationofthesignalprocessbefore theendoftheinputsignalasintheHaskelllist Theinfixoperator>>>implements(serial)func- function take. Thus, what we actually use, is a tion composition, the function first allows for definition that boils down to parallel composition, and the function arr gen- erates stateless transformations including rear- type Causal a b = forall state. rangement of tuples as needed by first. It (state, (a, state) -> turns out, that all of these combinators main- Code (V Bool, (b, state))) . taincausality. Theyallowustoexpressallkinds of causal processes without feedback. If f and With this type we can model all kinds of causal mix are causal processes, then we can translate processes, that is, processes where every out- the former mix x (f x) to put sample depends exclusively on the current andpastinputsamples. Thetakefunctionmay arr (\x -> (x,x)) >>> second f >>> mix serve as an example for a causal process with where second p = swap >>> p >>> swap termination. swap = arr (\(a,b) -> (b,a)) . take :: Int -> Causal a a For implementation of feedback we need only take n = one other combinator, namely loop. loop :: freqMod :: Generator (V a) -> c -> Causal (a,c) (b,c) -> Causal a b Causal (V a) (V a) . In retrospect, our causal process data type The function loop feeds the output of type c looks very much like the signal generator type. of a process back to its input channel of the It just adds a parameter to the transition func- same type. In contrast to the loop method tion. Vice versa the signal generator data type of the standard ArrowLoop class we must de- could be replaced by a causal process with no lay the value by one sample and thus need an input channel. We could express this by initial value of type c for the feedback signal. Because of the way, loop is designed, it cannot type Generator a = Causal () a run into deadlocks. In general deadlocks can occur whenever a signal processor runs ahead where () is a nullary tuple. However for clarity of time, that is, it requires future input data reasons we keep Generator and Causal apart. in order to compute current output data. Our 3.3 Internal parameters notion of a causal process excludes this danger. It is a common problem in signal processing In fact, feedback can be considered another thatrecursivefilters[Hamming, 1989]arecheap instance of the sharing problem and loop is in execution, but computation of their internal its solution. For instance, if we want to com- parameters (mainly feedback coefficients) is ex- pute a comb filter for input signal x and out- pensive. A popular solution to this problem put signal y, then the most elegant solution in is to compute the filter parameters at a lower Haskell is to represent x and y by lists and sampling rate [Vercoe, 2009; McCartney, 1996]. write the equation let y = x + delay y in Usually, the filter implementations hide the ex- y which can be solved lazily by the Haskell istence of internal parameters and thus they runtime system. In contrast to that if x and y have to cope with the different sampling rates are signal generators, this would mean to pro- themselves. duce infinitely large code since it holds In this project we choose a more modular way. We make the filter parameters explicit y = x + delay y but opaque and split the filtering process into = x + delay (x + delay y) generation of filter parameters, filter parameter = x + delay (x + delay (x + delay y)) resampling and actual filtering. Static typing ... asserts that filter parameters can only be used with the respective filters. Withloophoweverwecansharetheoutputsig- This approach has several advantages: nalywithitsoccurrencesontherighthandside. Therefore, the code would be • Afilteronlyhastotreatinputsofthesame samplingrate. Wedonothavetoduplicate y = apply (mixFanout >>> second delay) x the code for coping with input at rates dif- where mixFanout = ferent from the sample rate. arr (\(a,b) -> (a+b,a+b)) . • We can provide different ways of specify- ing filter parameters, e.g. the resonance of Since the use of arrow combinators is some- a lowpass filter can be controlled either by howlessintuitivethanregularfunctionapplica- theslopeorbytheamplificationoftheres- tion and Haskell’s recursive let syntax, there onant frequency. is a preprocessor that translates a special arrow • We can use different control rates in the syntax into the above combinators. Further on same program. there is a nice abstraction of causal processes, • We can even adapt the speed of filter pa- namely commutative causal arrows [Liu et al., rameter generation to the speed of changes 2009]. in the control signal. We like to note that we can even express sig- • For a sinusoidal controlled filter sweep we nalprocessesthatarecausalwithrespecttoone can setup a table of filter parameters for input and non-causal with respect to another logarithmically equally spaced cutoff fre- one. E.g. frequency modulation is causal with quencies and traverse this table at varying respect to the frequency control but non-causal rates according to arcus sine. with respect to the input signal. This can be • Classical handling of control rate filter pa- expressed by the type rameter computation can be considered as resampling of filter parameters with con- dio streams with them in real-time. This short- stant interpolation. If there is only a small coming is resolved with our approach. number of internal filter parameters, then Another special purpose language is ChucK we can resample with linear interpolation [WangandCook,2004]. Distinguishingfeatures of the filter parameters. of ChucK are the generalisation to many dif- The disadvantage of our approach is that we ferent rates and the possibility of programming cannot write something simple like lowpass while the program is running, that is while the (sine controlRate) (input sampleRate) soundisplaying. AsexplainedinSection3.3we anymore, but with Haskell’s type class mech- can already cope with control signals at differ- anism we let the Haskell compiler choose the ent rates, however the management of sample right filter for a filter parameter type and thus rates at all could be better if it was integrated come close to the above concise expression. inourframeworkforphysicaldimensions. Since the Haskell systems Hugs and GHC both have 4 Related Work a fine interactive mode, Haskell can in prin- ciple also be used for live coding. However it Our goal is to make use of the elegance of still requires better support by LLVM (shared Haskell programming for signal processing. libraries) and by our implementation. Our work is driven by the experience, that to- Efficient short-delay feedback written in day compiled Haskell code cannot compete a declarative manner can probably only be withtraditionalsignalprocessingpackageswrit- achieved by compiling signal processes to a ma- ten in C. There has been a lot of progress in chine loop. This is the approach implemented recent years, most notably the improved sup- bytheStructuredAudioOrchestraLanguageof port for arrays without overhead, the elimina- MPEG-4 [Scheirer, 1999] and Faust [Orlarey et tionoftemporaryarrays(fusion)andtheData- al.,2004]. FauststartedascompilertotheC++ Parallel Haskell project that aims at utilising programming language, but it does now also multiple cores of modern processors for array support LLVM. Its block diagram model very oriented data processing. However there is still much resembles Haskell’s arrows (Section 3.2). a considerable gap in performance between id- Adifferenceis,thatFaust’scombinatorscontain iomatic Haskell code and idiomatic C code. A more automatisms, which on the one hand sim- recentdevelopmentisanLLVM-backendforthe plifies binding of signal processors and on the GlasgowHaskellCompiler(GHC),thataddsall other hand means, that errors in connections ofthelow-leveloptimisationsofLLVMtoGHC. cannot be spotted locally. However we still need some tuning of the high- Before our project the compiling approach level optimisation and a support for processor embedded in a general purpose language was vectortypesinordertocatchupwithourEDSL chosen by Common Lisp Music [Schottstaedt, method. 2009], Lua-AV [Smith and Wakefield, 2007], In Section 2 we gave some general thoughts andFeldspar(Haskell)[programminggroupat about possible designs of signal processing lan- Chalmers University of Technology, 2009]. guages. Actually for many combinations of Of all listed languages only ChucK and features we find instances: The two well- Haskell are strongly and statically typed, and established packages Csound [Vercoe, 2009] and thus provide an extra layer of safety. We like SuperCollider [McCartney, 1996] are domain to count Faust as being weakly typed, since it specific untyped languages that process data provides only one integer and one floating point in a chunky manner. This implies that they type. have no problem with sharing signals between signal processors, but they support feedback 5 Conclusions and further work with short delay only by small buffers (slow) or by custom plugins (more development effort). The speed of our generated code is excel- Both packages support three rates: note rate, lent, yet the generating Haskell code looks id- control rate and sample rate in order to reduce iomatic. The next step is the integration of expensive computations of internal (filter) pa- the current low-level implementation into our rameters. With the Haskell wrappers [Hudak existing framework for signal processing, that et al., 1996; Drape, 2009] it is already possible works with real physical quantities and stati- tocontroltheseprogramsasiftheywerepartof callycheckedphysicaldimensions. Thereisalso Haskell, but it is not possible to exchange au- a lot of room for automated optimisations by GHC rules, be it for vectorisation or for reduc- Bryan O’Sullivan and Lennart Augusts- tion of redundant computations of frac. son. 2010. llvm: Bindings to the LLVM compiler toolkit. http://hackage.haskell. 6 Acknowledgments org/package/llvm-0.9.0.1, November. I like to thank the careful proofreaders of the Simon Peyton Jones. 1998. Haskell 98 lan- draft for their valuable suggestions. guageandlibraries, therevisedreport. http: References //www.haskell.org/definition/. Duncan Coutts, Roman Leshchinskiy, and Functional programming group at DonStewart. 2007. Streamfusion: Fromlists Chalmers University of Technology. 2009. to streams to nothing at all. In Proceedings feldspar-language: A functional embed- of the ACM SIGPLAN International Con- ded language for DSP and parallelism. ference on Functional Programming, ICFP http://hackage.haskell.org/package/ 2007, apr. feldspar-language-0.1, November. Rohan Drape. 2009. hsc3: Haskell Su- Eric Scheirer. 1999. Iso/iec 14496-3:1999: In- perCollider. http://hackage.haskell.org/ formationtechnology–codingofaudio-visual package/hsc3-0.7, June. objects – part 3: Audio – subpart 5: Struc- James Elliott. 2004. Hibernate: a developer’s tured audio orchestra language. Technical re- notebook. O’Reilly Media, Inc., 1005 Graven- port,InternationalOrganizationofStandard- stein Highway North, Sebastopol, CA 95472, ization. USA. Bill Schottstaedt. 2009. Common lisp music. Richard W. Hamming. 1989. Digital Filters. http://ccrma.stanford.edu/software/ Signal Processing Series. Prentice Hall, Jan- clm/. uary. Wesley Smith and Graham Wakefield. 2007. Paul Hudak, T. Makucevich, S. Gadde, and Real-time multimedia composition using lua. B. Whong. 1996. Haskore music notation – In Proceedings of the Digital Art Weeks 2007. an algebra of music. Journal of Functional ETH Zurich. Programming, 6(3), June. Henning Thielemann. 2004. Audio process- John Hughes. 2000. Generalising monads to ing using Haskell. In Gianpaolo Evangelista arrows. Science of Computer Programming, and Italo Testa, editors, DAFx: Conference 37:67–111, May. on Digital Audio Effects, pages 201–206. Fed- erico II University of Naples, Italy, October. Peter J. Landin. 1966. The next 700 pro- gramming languages. Communications of the Henning Thielemann. 2010a. Audio signal ACM, 9(3):157–166. processing embedded in Haskell via LLVM: Chris Lattner and Vikram Adve. 2004. Darcs repository. http://code.haskell. LLVM: A Compilation Framework for Life- org/synthesizer/llvm/. long Program Analysis & Transformation. In Henning Thielemann. 2010b. Compiling Sig- Proceedings of the 2004 International Sympo- nal Processing Code embedded in Haskell via sium on Code Generation and Optimization LLVM. http://arxiv.org/abs/1004.4796. (CGO’04), Palo Alto, California, March. Barry Vercoe. 2009. CSound. http://www. Hai Liu, Eric Cheng, and Paul Hudak. 2009. csounds.com/. Causal commutative arrows and their opti- mization. In ICFP ’09: Proceedings of the Ge Wang and Perry Cook. 2004. Chuck: a 14th ACM SIGPLAN international confer- programming language for on-the-fly, real- ence on Functional programming, pages 35– time audio synthesis and multimedia. In 46, New York, NY, USA. ACM. MULTIMEDIA ’04: Proceedings of the 12th annual ACM international conference on James McCartney. 1996. Super Collider. Multimedia, pages 812–815, New York, NY, http://www.audiosynth.com/, March. USA. ACM. Yann Orlarey, Dominique Fober, and Stephane Letz. 2004. Syntactical and seman- tical aspects of Faust. In Soft Computing.

See more

The list of books you might like

Most books are stored in the elastic cloud where traffic is expensive. For this reason, we have a limit on daily download.