8.12.19. STRUDEL-19 — One-Shots: Fire-and-Forget Stings on Events
A strudel pattern is normally an endless loop. daStrudel adds the piece a game or tool needs for event audio: a pattern that plays once and then stops and dies. This is what lets a pattern be used as a sound effect — a win sting, a pickup chime, an impact — fired from host state, with no baked or shipped audio asset.
There are two halves:
stop —
once(pat)/playFor(pat, n)gate a pattern to its first cycle (or firstncycles) and emit silence forever after. These are purePatterncombinators; they compose with everything.die —
strudel_one_shot(pat, gain, quant_cycles, len_cycles)firespatas a self-retiring track: it plays, goes silent, and the track removes itself from the mix once every voice has finished ringing (so the release tail is never clipped).
8.12.19.1. Part A: once / playFor — the stop half
You can see the stop without listening: query the gated pattern and
watch the onsets vanish past the window. once keeps cycle 0;
playFor(pat, n) keeps the first n cycles:
let phrase <- once(note("c5 e5 g5 c6", "triangle"))
var c0 <- phrase(TimeSpan(start = 0.0lf, stop = 1.0lf)) // 4 onsets — plays
var c1 <- phrase(TimeSpan(start = 1.0lf, stop = 2.0lf)) // 0 onsets — silent forever
Because they are ordinary combinators, the gate composes with the rest
of the pattern algebra (fast, stack, the fluent setters, …)
before you ever turn it into a one-shot.
8.12.19.2. Part B: strudel_one_shot — the die half
Fire a sting from the host loop. The track is live while it sounds,
then auto-removes once its voices stop ringing — you never call
strudel_remove_track. strudel_active_tracks() reports how many
tracks are live, so the host (or a test) can ask “is the sting still
ringing?”:
let sting <- (note("c5 e5 g5 c6", "triangle")
|> gain(0.8) |> room(0.5) |> orbit(2) |> attack(0.005) |> release(0.4))
strudel_one_shot(sting, 1.0, 0.0, 0.5) // gain 1.0, immediate, half a cycle long
// ... keep calling strudel_tick() in the host loop; the track retires itself
Compose orbit / room / gain into pat to give the sting
its own reverb bus, independent of the music tracks.
8.12.19.3. Part C: Quantize to the beat
quant_cycles > 0 snaps the start to the global beat grid: 0 is
immediate, 0.25 is the next quarter-cycle beat. A sting triggered at
an arbitrary instant then waits a few milliseconds so it lands musically
in time instead of on the exact frame of the event:
strudel_one_shot(sting, 1.0, 0.25, 0.5) // snap to the next 1/4-cycle beat
8.12.19.4. Part D: Parametric SFX — a different sting per event
Each one-shot is a fresh, independent track, so the pattern can vary per event. Fire a higher sting as “events” intensify — pitch by impact, instrument by surface — all without a single audio file:
let phrases <- ["c4 e4 g4", "e4 g4 c5", "g4 c5 e5", "c5 e5 g5"]
// on event i:
let sting <- (note(phrases[i], "triangle")
|> gain(0.7) |> room(0.4) |> orbit(2) |> attack(0.005) |> release(0.3))
strudel_one_shot(sting, 1.0, 0.0, 0.5)
This is the discrete-event complement to the continuous adaptive
control (signal + orbit fades) covered in the comparison page:
sustained layers crossfade with signals and orbit levels; momentary
events fire one-shots.
See also
Full source: tutorials/daStrudel/daStrudel_19_one_shots.das
Previous tutorial: STRUDEL-18 — SFX Lab: a procedural sound-effect workbench
How daStrudel differs from strudel.cc (one-shots are a daStrudel extension): daslang strudel vs strudel.cc — Feature Comparison
Full daStrudel reference: Strudel (Live Coding)