Karplus and Strong found in 1983 that a burst of noise, fed into a delay line one period long and recirculated through a gentle lowpass, sounds like a plucked string. It is the smallest physical model there is — a delay line, an average and a gain — and every piece of it is something this act has already taught.
The starter's burstLaw is the pluck alone: for one period after the note starts (noteTime < 1.0 / hz) it passes white noise through, scaled by vel; after that, silence. A click per note. noteTime and vel are voice reads — inside a processO law they are plain numbers for this note.
Give the law a line instead of a cell: dline 50ms in the init, a handle buf in the binders. Read it one period back — d = sampleRate / hz samples — and add what you read to the burst: y = burst + g * dtap buf d. Push y back in as the line's next state. The burst now goes round and round, once per period: a pitched tone at exactly the note's frequency.
Average two neighbouring taps, 0.5 * (dtap buf d + dtap buf (d + 1.0)): that is the loss filter. It damps the high harmonics a little on every trip, so the tone darkens as it decays, like a real string. It also delays by half a sample, so read at d = sampleRate / hz - 0.5 to stay in tune. The gain g = exp (-6.9 / (decay * hz)) makes the ring fall 60 dB in decay seconds whatever the pitch.
Wrap it as oscillator string (decay: Float = 2.0) = \hz -> hz |> processO (ksLaw decay) (dline 50ms) noise and call string (freq pitch) { decay = 3.0 }. The finished, tuned version with a brightness control and deterministic noise is ksPluck in osclib — open it in the Reference panel and compare.
The starting point#
The lesson opens with this song in the editor. Every edit recompiles and plays.
-- A plucked string (Karplus–Strong): the law below only makes the pluck —
-- one period of noise at each note start (`noteTime` is seconds since the
-- note began). Feed that burst into a delay line one period long and let
-- it recirculate, a little quieter each time, and it becomes a string.
burstLaw = \hz n s ->
let burst = if noteTime < 1.0 / hz then n * vel else 0.0 in
[burst, s]
pluck = patch {
source = freq pitch |> processO burstLaw 0.0 noise,
ampEnv = adsr 1ms 3s 0 200ms,
gain = 0.25
}
tune = play pluck !"e3 b3 e4 g4 b4 g4 e4 b3"
What to watch#
Watch the scope: the starter draws one short burst per note; with the line and the loop each burst turns into a repeating waveform that slowly smooths into a sine as the loss filter eats its harmonics.
Goals#
The workbench checks these as you work; the solution is there when you want it.
- Give the law a delay line and read it one period back
- Close the loop: the loss filter, a gain, and the output pushed back in
- Name it:
oscillator string (decay: Float = 2.0) = … - Stretch: damp the string when the key lifts
Make it yours#
Replace the white-noise burst with a filtered one (a one-pole on n), or pluck with a single impulse instead of noise. How does the attack's colour change the whole note?
Further reading#
DSP cookbook in the reference.