Contents

Closures

Closures are anonymous functions that capture their environment. They connect patterns to synthesis.

Basic syntax

(parameter) -> expression

A closure takes parameters and returns the result of an expression:

// Closure that doubles its input
(x) -> x * 2

// Closure with multiple parameters
(freq, amp) -> sine(freq) * amp

Using closures

Closures are commonly used with patterns and higher-order functions:

// Pattern triggers closure for each note
n"c4 e4 g4"
    |> ((freq) -> sine(freq) * ar(trigger(4)))
    |> out(@)

Closures as voices

Closures are “voices” in Akkado: they define how control data becomes sound.

// Define a synth voice
voice = (freq) ->
    saw(freq) |> lp(@, 1000) * ar(trigger(4))

// Use with a pattern
n"c3 e3 g3 c4" |> voice |> out(@)

Multiple parameters

Closures can receive multiple values:

// Frequency and velocity
(freq, vel) -> sine(freq) * vel * ar(trigger(4))

Closure with pipes

The pipe operator works inside closures:

synth = (f) ->
    saw(f)
    |> lp(@, 800 + sine(2) * 400)
    |> saturate(@, 2)

synth(110) |> out(@)

Capturing variables

Closures capture variables from their surrounding scope:

cutoff = 1200
filter_voice = (freq) -> saw(freq) |> lp(@, cutoff)

filter_voice(220) |> out(@)

Returning closures from fn

A fn whose body is a closure expression returns a function value that captures the outer params:

fn make_filter(cut) -> (sig) -> lp(sig, cut, 0.7, 0.5, 0.5)

filt = make_filter(1000)
noise() |> filt(@) |> out(@)

The captured params are bound when the factory is called and remain read-only inside the returned closure. Nested factories work too: fn f(a) -> (b) -> (c) -> a + b + c.

Partial application

Use _ in any argument position to create a closure with that slot left open:

fn add(a, b) -> a + b
add3 = add(3, _)         // (x) -> add(3, x)
add3(4)                  // 7

soft_lp = lp(_, 500, 0.7, 0.5, 0.5)
noise() |> soft_lp(@) |> out(@)

Each _ becomes a parameter of the resulting closure in left-to-right order. Works for both builtins and user-defined functions. Useful with higher-order helpers: map(arr, add(3, _)).

Function composition

compose(f, g, ...) builds a left-to-right chain. compose(a, b) is (x) -> b(a(x)):

fn double(x) -> x * 2
fn inc(x) -> x + 1
f = compose(double, inc)   // (x) -> inc(double(x))
f(5)                       // 11

pipeline = compose(
    lp(_, 1000, 0.7, 0.5, 0.5),
    hp(_, 200, 0.7, 0.5, 0.5))
saw(440) |> pipeline(@) |> out(@)

compose() accepts 2 or more function-valued arguments: closures, fn refs, or partial applications. The result is itself a function value.

Related: Variables, Pipes & Holes