commit 1eb17f1fb29e25493dba431d6dccf8dfecedc350 Author: Max Rottenkolber Date: Sun Jul 26 20:45:20 2015 +0200 Add Soundlab documentation. diff --git a/software/soundlab/lazy-signal-combinators.mk2 b/software/soundlab/lazy-signal-combinators.mk2 new file mode 100644 index 0000000..f867d14 --- /dev/null +++ b/software/soundlab/lazy-signal-combinators.mk2 @@ -0,0 +1,307 @@ +_Written by Max Rottenkolber , April 2013. Formatting has been +updated since._ + +< Introduction + + The described approach is mainly inspired from experience gained by + using analogue sound synthesizers. While every analogue synthesizer has + its own unique sound based on the physical parts it is made of, most do + share their key concepts. Usually a limited number of oscillators + generate signals resembling—more or less—sine waves which are then + modulated by being combined with each other in different ways. + + Soundlab—an experimental implementation of the presented approach—is + designed to enable the user to explore ways of signal combination. It + does so by defining an embedded _domain specific language_ which + provides axioms that generate primitive signals and axioms that combine + arbitrary signals into new signals. The semantics of the language are + based on a signal interface agreed on by every component. Furthermore + Soundlab allows the use of _Common Lisp's_ means of abstraction to + define compound signals and signal combinators. Primitive as well as + compound parts of the system form a homogeneous group of objects defined + by their shared interfaces, which grant the system power and flexibility + of a _Lisp_ system. + + There are of course many free software implementations (see for instance + [Overtone](http://overtone.github.io) and + [Csound](http://www.csounds.com)) of signal synthesis systems with + programming language interfaces. Soundlab is—when compared to + others—much simpler and entirely written and embedded in _Common Lisp_. + + Soundlab is free software licensed under the _GNU AGPL_ and can be + obtained from [GitHub](https://github.com/eugeneia/soundlab). + +> + + +< Rendering signals + + Before discussing signal synthesis, we must define ways for consuming + the synthesized signal as well as for verification of our results. + Because our domain is music, we need to be able to play back signals as + sound. Furthermore visualizing a signal can be useful for debugging + since some properties of a signal are better conceived visually than + aurally. + + For both forms of presentation a technique called _sampling_ is + used—which will not be described in detail here. All that is needed to + know for this approach, is that the sampling routine records a sequence + of linear amplitude values according to a time span and a function—or + signal—which maps values of time to values of amplitude. The resulting + sequence resembles the kind of data that can be fed into standard + digital sound adapters or plotting applications. + + #code Approximate type of a sampling function.# + (function ((function (real) real) real) + (sequence real)) + # + + Soundlab derives its signal type from this rationale. It also exports + two functions which record signals to standard _WAVE_ audio files and + _Gnuplot_ compatible data files respectively. Soundlab also chooses + arbitrary but sensible units and scales for time and amplitude. Time is + chosen to be a number in seconds greater than zero and amplitude is + chosen to be a number ranging from -1 to 1. Results of inputs to the + sampling routine exceeding these bounds are undefined. + +> + + +< Signal synthesis + + < Signals as functions + + As discussed in the previous section, functions are the natural way to + model a signal. Furthermore signals as functions encourage lazy + operations without enforcing them—which can later be useful for + aggressive optimizations. + + #code Type of a signal.# + (function (real) real) + # + + A crucial type of signal is the sine wave—since in theory, all signals + are sums of sine waves. _Common Lisp_ provides us with a sine function + {sin} which serves our purpose well. We could pass {#'sin} to a + sampling routine as is, which would produce a very low frequency signal + below the human hearing threshold. In order to specify other + frequencies a constructor {sine} is defined which accepts a frequency + in Hz and returns the respective sine signal. + + #code Constructor for primitive sine signals.# + (defun sine (frequency) + (lambda (x) (sin (* 2 pi frequency x)))) + # + + Additionally a constructor for chorded signals could be defined as a + function that takes two signals as arguments and returns a function + that sums and normalizes them according to the boundaries we defined in + the previous section. + + #code Constructor for a chord of two signals.# + (defun chord-2 (signal-1 signal-2) + (lambda (x) (* (+ (funcall signal-1 x) + (funcall signal-2 x)) + 1/2))) + # + + The {chord-2} function demonstrates the important traits of signals as + functions. A new signal in form of an anonymous function is being + compiled whenever we call {chord-2}. Because the actual processing of + the arguments is postponed until sampling occurs, operation on signals + is cheap. Furthermore calls to {chord-2} can be combined to create + chords with an arbitrary number of voices. + + > + + + < Signal combination + + As seen in the previous section, modeling signals as functions enables + us to write small, cheap and powerful signal combinators which can be + chained to arbitrary extent. When chosen carefully, a small set of + primitive combinators and signals can be used to create infinitely + complex sounds. + + #code Type of a signal combinator.# + (function (&rest (function (real) real)) + (function (real) real)) + # + + While building {soundlab}, some primitives turned out to be especially + useful. {flatline}—a constant signal constructor—serves a simple but + important purpose. It takes a number as its only argument and returns a + flat signal with a constant amplitude. When passed to a signal + combinator its purpose is usually to scale combinations of + signals. {add} is a general signal adder. It takes an arbitrary number + of signals and sums them. Likewise, {multiply} multiplies signals. The + {chord-2} combinator of the previous section can be defined more + generally using these primitives. + + #code Implementation of {flatline}.# + (defun flatline (amplitude) + (lambda (x) + (declare (ignore x)) + amplitude)) + # + + #code Generic implementation of {chord}.# + (defun chord (&rest signals) + (multiply (apply #'add signals) + (flatline (/ 1 (length signals))))) + # + + Note that—due to the normalization performed by {chord-2}—the + equivalent of {(chord a b c)} is + + #code # + (chord-2 (chord-2 a b) (chord-2 c (flatline 1))) + # + + as opposed to + + #code # + (chord-2 (chord-2 a b) c) + # + + which would produce the chord of {c} and the chord of {a} and {b} + instead of the chord of {a}, {b} and {c}. + + Furthermore, using signals as arguments to operations where constants + would suffice whenever possible has proven to be feasible and powerful. + Whenever a component is being modeled that would be controlled by a + knob or fader in an analogue synthesizer, then its digital counterpart + should be controlled by a signal. Take for instance a signal combinator + {mix*} whose purpose is to merge two signals—just like {chord}—while + additionally providing a way to control how much each input signal + amounts to the mixed signal. So what would have been a _Dry/Wet_ knob + on an analogue synthesizer becomes a signal in our case. Our {mix*} + takes three signals as arguments, two to be mixed and a third to + control their amounts. For ease of implementation we also introduce + {subtract}—the counterpart to {add}. + + #code Implementation of {mix*}.# + (defun mix* (signal-a signal-b ratio-signal) + (add (multiply signal-a + (subtract (flatline 1) + ratio-signal)) + (multiply signal-b + ratio-signal))) + # + + Staying within closure of the signal representation—that is trying hard + to define our operations on a uniform signal representation only—grants + the system a lot of power and flexibility. All of the presented signal + combinators can be plugged into each other without restriction. As of + now some care has to be taken to not produce signals exceeding the + defined boundaries—see _Rendering signals_. Additionally, some + combinators make use of non-audible signals. For instance {mix*} + expects {ratio-signal} to return values ranging from zero to one and + {multiply} is used in combination with {flatline} to moderate + signals. Soundlab fails to address the issue of having multiple + informal subtypes of signals. As of now the user has to refer to the + documentation of a combinator to find out if it expects certain + constraints—as is the case with {mix*}. Nevertheless, our few examples + can already be used to produce complex sounds. The code snippet below + works in Soundlab as is and produces a rhythmically phasing sound. + + #code Possible usage of the presented combinators.# + (defun a-4 () (sine 440)) + (defun a-5 () (sine 220)) + + ;; Normalize a sine to 0-1 for use as RATIO-SIGNAL. + (defun sine-ratio () + (multiply (add (sine 1) + (flatline 1)) + (flatline 1/2))) + + ;; Produce a WAVE file. + (export-function-wave + ;; A complex signal. + (mix* (chord (a-4) (a-5)) + (multiply (a-4) (a-5)) + (sine-ratio)) + ;; Length of the sampling in seconds. + 4 + ;; Output file. + #p"test.wav") + # + + > + +> + + +< The state of Soundlab + + As of the time of this writing Soundlab consists of roughly 500 lines + of source code. It depends on a minimal library for writing _WAVE_ files + and is written entirely in _Common Lisp_. The source code is fairly well + documented and frugal. + + While being compact Soundlab provides basic routines for working with + western notes and tempo, a few primitive waveforms, _ADSR_ envelopes + with customizable slopes and the ability to form arbitrary waveforms + from envelopes, a good handfull of signal combinators and last but not + least an experimental lowpass filter. A stable API is nowhere near in + sight but some trends in design are becoming clear. + + On the roadmap are classic sound synthesis features like resonance, + routines for importing signals from _WAVE_ files and many small but + essential details like bezier curved slopes for envelopes. + +> + + +< Conclusions + + Soundlab—even in its immature state—presents an opportunity to explore + abstract signal synthesis from scratch for engineers and artist + alike. Its simplicity encourages hacking and eases understanding. While + many complex problems surrounding signal synthesis remain unsolved, its + lazy combinatorial approach forms a powerful and extensible framework + capable of implementing classic as well as uncharted sound synthesis + features. + + The demonstrated approach proved to be especially suited to exploratory + sound engineering. Ad-hoc signal pipelines can be built quickly in a + declarative way, encouraging re-usability and creativity. In comparison + to other tools in the domain the line between using and extending the + system is blurry. Where _Csound_ lets the user declaratively configure + instruments and controls using _XML_, Soundlab emphasizes the user to + use its built-in primitives and all of _Common Lisp_ to stack layers of + signal sources and modulators on top of each other. When compared to + _Overtone_—a _Clojure_ front-end to the _SuperCollider_ audio + system—Soundlab's back-end independency and simplicity make it seem + more suited for exploration and hacking. Its core concepts are few and + simple and its codebase is tiny and modular despite some advanced + features like envelopes, musical scales and tempo, a lowpass filter and + many kinds of signal combinators being implemented. + + Many of _Common Lisp's_ idioms proved to be an ideal fit for the domain + of signal synthesis. Furthermore, embedding a signal synthesis language + in _Common Lisp_ provides the system with unmatched agility. While the + core approach is mainly built on top of functional paradigms, extensions + like signal subtype checking—as mentioned in section 3.2—could be + implemented using macros. + + I personally had tons of fun building and playing with Soundlab. I + encourage everyone interested in computerized music to dive into the + source code and experiment with the system—it really is that + simple. Feedback and contributions are welcome! + +> + + +< Acknowledgments + + Thanks to [Michael Falkenbach](http://soundpiloten.de) for teaching me a + whole lot about analogue audio hacking. + + Thanks to Vera Schliefer for introducing me to signal theory. + + Thanks to Drew Crampsie for providing the _Common Lisp_ community with + resources regarding the implementation of monadic combinators in the fom + of [Smug](https://github.com/drewc/smug). + +>