VOLTAlearn

Learn Volta / Write your own DSP

State: a one-pole filter

Give a kernel memory with `process2` and build a filter from it.

lesson 32 of 39 · about 10 minutes

Open this lesson in the workbench →

A waveshaper forgets each sample the moment it is done with it. A filter cannot: to smooth a signal it has to remember where it was. process2 gives your law memory — a state cell it reads at every sample and hands back updated — and with one cell you can build the most important filter there is.

process2 (\x s -> [out, next]) 0.0 passes the law the input x and the cell s, and expects a list back: what to play, and what s should be on the next sample. The 0.0 after the lambda is the cell's starting value.

The one-pole lowpass is one line of state: y = s + a * (x - s) — move a fraction a of the way from where you were toward the input. Small a means slow, dark, smooth; a = 1 passes everything.

To tune it in Hz, the fraction comes from the cutoff and the sample rate: a = 1 - exp(-2π·cut / sampleRate). sampleRate is readable only inside kernels, and a let that depends only on it and constants is computed once per block, not per sample.

Write the law as a named lambda with the cutoff first — onePoleLp = \cut x s -> … — and apply it partially: process2 (onePoleLp 800.0) 0.0. The recipe's leading arguments can be numbers or macros; the rest are the kernel's.

The starting point#

The lesson opens with this song in the editor. Every edit recompiles and plays.

-- State: this pluck is a raw saw — every harmonic, all the way up.
-- Give it a filter you write yourself.
pluck = patch {
  source = saw (freq pitch),
  ampEnv = adsr 2ms 220ms 0 120ms,
  gain   = vel * 0.2
}

line = play pluck !"a2 c3 e3 a3 g3 e3 c3 e3"

What to watch#

Watch the spectrum while you lower the cutoff: the upper bars fall away at 6 dB per octave — gently, compared with the builtin 12 dB filters — and the scope's saw teeth round off into curves.

Goals#

The workbench checks these as you work; the solution is there when you want it.

  1. Give the kernel memory: process2 (\x s -> [y, y]) 0.0
  2. Tune it in Hz with sampleRate
  3. Stretch: ride the cutoff with a macro

Make it yours#

Return x - y instead of y (keep y as the next state): the same one cell becomes a highpass. Then chain a lowpass and a highpass to make a band.

Further reading#

Write a custom audio processor in the reference.