8.12.4. STRUDEL-04 — Time Manipulation
So far every Pattern played at one cycle per cycle, in forward order.
This tutorial introduces the time algebra: four functions that take
a Pattern and return a new Pattern by rewriting the query timeline.
fast and slow change density, rev mirrors per-cycle, and
hurry couples speed with pitch. They all compose freely, and they
work on any Pattern — mini-notation, pure, or signals.
The mental model: a combinator wraps a Pattern with a function that
rewrites the query span on the way in and rewrites the result
haps on the way out. fast(2) queries the inner pattern with
span * 2, then divides every returned hap’s timestamps by 2.
slow(N) is exactly fast(1/N). rev mirrors the span within
each cycle and mirrors the haps back. That is the entire framework.
8.12.4.1. Part A: fast(N)
fast(N) repeats the pattern N times per cycle:
let pat <- note("c4 e4 g4 c5", "sine") |> sustain(0.4) |> fast(2.0lf)
play(pat, 4.0)
The four-note arpeggio plays twice per cycle. N accepts int,
float, or double — fast(1.5lf) gives 3 repetitions every
2 cycles, useful for polyrhythms (covered in tutorial 05).
8.12.4.2. Part B: slow(N)
slow(N) stretches one pass of the pattern over N cycles. It is
defined as fast(1.0lf / n):
let pat <- s("bd sd hh cp") |> slow(2.0lf)
play(pat, 6.0)
The four hits now span two cycles instead of one. Use this to stretch melodies, make pad-style sustains, or build a slow LFO out of a signal pattern.
8.12.4.3. Part C: rev
rev reverses event positions within each cycle:
let pat <- note("c4 e4 g4 c5", "sine") |> sustain(0.4) |> rev
play(pat, 4.0)
The arpeggio plays backward as c5 g4 e4 c4. This is per-cycle
mirroring — not a global reverse. Each cycle stands alone, so
rev |> rev is the identity. Combine it with jux to get instant
stereo widening (tutorial 07 covers combinators).
rev is callable bare or with parentheses: pat |> rev and
pat |> rev() are equivalent.
8.12.4.4. Part D: hurry(N)
hurry(N) speeds time up by N and multiplies the per-event
playback speed by N. For sample-based sounds (drum hits,
field-recordings), speed scales playback rate, which means pitch goes
up by log2(N) octaves:
let pat <- note("c3 e3 g3 c4", "sine") |> sustain(0.4) |> hurry(2.0lf)
play(pat, 4.0)
The pattern plays twice as fast and an octave higher. This is a single
function call away from the classic chipmunk effect. For synth voices
(sine, sawtooth, supersaw), hurry and fast differ
only in the speed field — pitch comes from note, not speed.
8.12.4.5. Part E: Composition
The four functions all return Patterns, so they compose with the pipe operator like any other transform:
let pat <- (note("c4 e4 g4 b4", "sine")
|> sustain(0.4)
|> rev
|> slow(2.0lf))
play(pat, 6.0)
Order matters — rev |> slow(2) reverses then stretches; slow(2) |>
rev stretches then reverses (and the two produce different rhythms in
general because rev operates per-cycle).
8.12.4.6. Part F: ply(N) — repeat each event in place
Where fast repeats the whole pattern, ply(N) subdivides each
event locally, playing it N times back-to-back inside its own slot:
let pat <- note("c4 e4 g4 c5", "sine") |> sustain(0.3) |> ply(3)
play(pat, 4.0)
The four-note phrase becomes twelve notes — each one stuttered three times. This is the canonical way to add a roll or ratchet to selected notes without changing the overall phrase length.
8.12.4.7. Part G: iter / iterBack / palindrome
These three restructure the pattern across cycles rather than within
one. iter(N) rotates the start point left by 1/N each cycle, so
c-e-g-a becomes e-g-a-c, then g-a-c-e, walking through every
rotation over N cycles. iterBack(N) rotates the other direction.
let pat <- note("c4 e4 g4 a4", "sine") |> iter(4)
play(pat, 8.0)
palindrome alternates the pattern forward on even cycles and reversed
on odd cycles — an ascending scale on cycle 0, descending on cycle 1:
let pat <- note("c4 e4 g4 c5", "sine") |> palindrome
play(pat, 8.0)
8.12.4.8. Part H: linger / compress — looping and windowing
linger(t) keeps only the first fraction t of the cycle and loops
it to fill the rest. linger(0.5) plays the first half twice:
let pat <- note("c4 e4 g4 c5 a4 g4 e4 c4", "sine") |> linger(0.5lf)
play(pat, 4.0)
compress(b, e) squeezes the whole pattern into the [b, e) window
of each cycle, leaving silence before b and after e:
let pat <- note("c4 e4 g4 c5", "sine") |> sustain(0.3) |> compress(0.25lf, 0.75lf)
play(pat, 4.0)
The arguments are cycle fractions, so compress(0.25, 0.75) confines
the phrase to the middle half. It is the inverse of fast plus an
offset — density unchanged, just relocated in time.
8.12.4.9. Part I: striate / chop — granular sample slicing
Both cut a sample into N pieces, but distribute them differently.
striate(N) spreads the N slices across the whole cycle, so
successive events reveal later portions of the sample. chop(N) keeps
the slices inside each event’s own slot — an in-place grain walk:
let pat <- s("bd sd hh cp") |> striate(4) |> gain(0.4)
play(pat, 4.0)
let pat2 <- s("bd") |> chop(4) |> gain(0.6)
play(pat2, 4.0)
These need a sample-ish source. The built-in drum synth (bd, sd,
hh, cp) works for a quick demo; load a real sample bank for the
classic granular textures. Use striate to smear a sample over the bar
and chop to granulate individual hits.
8.12.4.10. A note on early and late
You may have seen early(N) / late(N) in other Tidal/Strudel
documentation — they shift the pattern earlier or later by N cycles.
In this build they exist but are private to the strudel module
(used internally by off, the canon-style overlay combinator). To
shift a pattern, the public route is to chain slow and cat, or
to wait for them to be exported in a future release.
8.12.4.11. Where next
Tutorial 05 covers Euclidean rhythms — the euclid(pat, k, n)
combinator that distributes k events evenly across n steps and
produces traditional drum patterns from many cultures with a single
line.
See also
Full source: tutorials/daStrudel/daStrudel_04_time_manipulation.das
Previous tutorial: STRUDEL-03 — Mini-Notation Advanced
Next tutorial: STRUDEL-05 — Euclidean Rhythms
Related: Lambdas and Closures — Patterns are lambdas, and so are the wrappers fast/slow/rev build
Related: Functions — pipe operator |> is just function call syntax