Thursday, February 3, 2011

a better audio language

One of my frequent frustrations with audio programming languages is that they're relatively unsafe, at least when compared to languages with sophisticated type systems. The problem is generally worsened with EDSLs. Consider this example csound:

aNull delayr 2
atap deltap 1
delayw asig
Most csound dsl's will provide 3 functions corresponding to delayr, deltap, and delayw, which are translated to the dsl in a very direct manner. If the delayr is left out, the dsl code is translated to invalid csound, detected only by the csound compiler. I find this frustrating because we can do better.

What if the delayr generates a token, which is then required by all functions that want to use it?

-- Haskell-mode now
myDelay asig = do
(dtok, _) <- delayr 2 adelay <- deltap dtok 1 delayw dtok asig return adelay
This is better, but it's still possible to accidentally omit a delayw from the Haskell code. Again, this is undetected except by the csound compiler at the final stage. What we really need is a way to ensure that a user can run delays freely within the context of a delay line, but only in that context. In Haskell, computations within contexts are generally represented by applicative functors. Let's try that approach:

-- don't export the constructor
newtype DelayCtxt a = DelayCtxt { unDelay :: CsoundInterp a }

deltap' :: CsoundInterp KSig -> DelayCtxt ASig
deltap' dtime = DelayCtxt $ dtime >>= deltap

runDelay :: CsoundInterp ASig -> CsoundInterp Float -> DelayCtxt a -> CsoundInterp a
runDelay mAsig mtime delcomp = do
maxdel <- mtime asig <- mAsig delayr maxdel rval <- unDelay delcomp delayw asig return rval
This is more like it. Since the DelayCtxt constructor isn't exported, the only way to create a DelayCtxt value is with deltap'. The only way to consume a DelayCtxt is with runDelay, which automatically adds the required delayr and delayw.

Unfortunately the user still needs to manually add the maximum delay time, but that's unavoidable if you're going to support varying delay times. Consider that the input signal could be coming from a controller in real-time; to statically guarantee it's under a given bound would require a bounded signal type, which would be equivalent to this static delay bound anyway.

This is the motivating example behind X-DSP, which was presented at the 2011 SEAMUS conference at the University of Miami.