8.12.8. STRUDEL-08 — Effects & Filters

Strudel splits effects into two groups, distinguished by where they live in the signal graph (the same table appears in tutorial 07):

Per-voice vs per-orbit FX

Scope

Setters

Per-voice (independent per note)

gain, pan, speed, lpf, hpf, bpf, phaser, tremolo, compressor, shape, crush, coarse, djf, fm, vowel

Per-orbit (one shared bus per orbit)

room / roomsize, delay / delaytime / delayfeedback, chorus

This split lets you give each layer of a stack its own reverb and delay character (per-orbit), while still personalising every note’s filter and modulation (per-voice). Every per-voice setter also has a pattern-valued overload — feed it a signal to make the parameter move over time; see tutorial 09.

8.12.8.1. Part A: per-voice filters — lpf and hpf

lpf(freq) cuts harmonics above freq Hz; hpf(freq) cuts below. Compare a muffled sawtooth (lpf 200) against a thin one (hpf 2000):

// Dark, all-bass:
let pat_lpf <- atom("sawtooth") |> note(48.0) |> sustain(1.0) |> lpf(200.0)

// Thin, all-treble:
let pat_hpf <- atom("sawtooth") |> note(48.0) |> sustain(1.0) |> hpf(2000.0)

Each voice carries its own filter coefficients, so two stacked voices can be filtered differently.

8.12.8.2. Part B: per-orbit reverb — room and roomsize

room(amount) sets the wet send to the orbit’s reverb bus; roomsize(N) controls the room dimensions. Both live on the bus, so all voices on the same orbit share one reverb instance:

let pat <- note("c4 ~ c4 ~", "sine") |> attack(0.001) |> decay(0.15) |> sustain(0.0) |> release(0.01) |> room(0.8) |> roomsize(2.0)

8.12.8.3. Part C: per-orbit delay — tempo-aware by default

delay(amount), delaytime(seconds) and delayfeedback(0..1) drive the orbit’s delay line. delaytime defaults to a tempo-aware 3/16 cycle (resolved from delaysync and the current cps), so plain delay(0.5) already locks to the tempo even without a delaytime setter. Tutorial 10 covers the resolver in detail.

let pat <- note("c4 ~ e4 ~ g4 ~ c5 ~", "sine") |> attack(0.001) |> decay(0.15) |> sustain(0.0) |> release(0.01) |> delay(0.5) |> delaytime(0.5) |> delayfeedback(0.4)

8.12.8.4. Part D: per-voice phaser

phaser(rateHz) sweeps a notch through the spectrum at the given rate. Because it is per-voice, every note carries its own phase — two stacked voices stay independent, which is not how shared-bus live-coding systems behave.

let pat <- run(8) |> scale("D:pentatonic") |> sound("sawtooth") |> sustain(1.0) |> release(0.5) |> phaser(2.0)

8.12.8.5. Part E: orbits — give each layer its own bus FX

Different orbits get independent bus FX instances. Below, orbit 0 sits in a tight small room while orbit 1 sits inside a cathedral:

let pat <- stack([
    note("c4 ~ e4 ~", "sine") |> attack(0.001) |> decay(0.15) |> sustain(0.0) |> release(0.01) |> room(0.8) |> roomsize(0.5) |> orbit(0),
    note("c3", "triangle") |> attack(0.5) |> decay(0.2) |> sustain(0.6) |> release(0.5) |> room(0.8) |> roomsize(8.0) |> orbit(1)
])

The pluck on orbit 0 sounds tight; the pad on orbit 1 swims in reverb — neither bleeds into the other.

8.12.8.6. Part F: crush & shape — per-voice waveshaping

crush(bits) reduces bit depth for lo-fi grit; coarse(n) decimates the sample rate by a factor of n; shape(amount) is a waveshaper that adds harmonic distortion. All per-voice:

note("c3 e3 g3 c4", "sawtooth") |> sustain(0.8) |> crush(4.0) |> coarse(4)
note("c3 e3 g3 c4", "sine")     |> sustain(0.8) |> shape(0.6)

8.12.8.7. Part G: tremolo & compressor — per-voice amplitude FX

tremolo(rateHz) pulses the amplitude (tremolodepth sets the depth); compressor(thresholdDb) tames dynamics so quiet and loud notes sit closer together:

note("d3 d3 d#3 d3", "supersaw") |> sustain(1.0) |> release(0.5) |> tremolo(8.0) |> tremolodepth(0.8)
s("bd sd hh cp") |> compressor(-20.0)

8.12.8.8. Part H: bpf & djf — more per-voice filters

bpf(freq) is a band-pass filter (bpq sets resonance/Q); djf(x) is a single DJ-style filter knob — below 0.5 it acts as a low-pass, above 0.5 a high-pass, so one parameter sweeps muffled to thin:

note("c3 e3 g3 c4", "sawtooth") |> sustain(0.8) |> bpf(800.0) |> bpq(6.0)
note("c3 e3 g3 c4", "sawtooth") |> sustain(0.8) |> djf(0.8)

8.12.8.9. Part I: chorus — per-orbit bus

chorus(amount) thickens a sound with slightly detuned copies. Like room and delay it lives on the orbit bus, so all voices on the same orbit share one chorus instance:

note("<[c3,e3,g3] [f3,a3,c4] [g3,b3,d4] [a3,c4,e4]>", "sawtooth") |> chorus(0.5) |> attack(0.05) |> decay(0.1) |> sustain(0.6) |> release(0.3) |> gain(0.4)