Version: 1.0
Target: Phase 20+ (Post-algebraic effects)
Status: Implementation Complete
This guide introduces the TidalCycles-inspired pattern library for Turmeric, enabling live-coding of musical patterns with a powerful, composable DSL.
The tidal library is included in Turmeric's standard library. Import it to get started:
(import tidal)
Or import individual modules:
(import tidal/pattern tidal/temporal tidal/structural)
;; Create a simple cycle pattern
(def my-pattern (cycle 1 2 3 4))
;; Evaluate at different times
(my-pattern 0.0) ;; => 1
(my-pattern 1.0) ;; => 2
(my-pattern 2.0) ;; => 3
To hear your patterns, connect to a running SuperCollider server (scsynth):
(import scscm tidal/synth)
;; Connect to server
(def session (scscm-connect))
;; Play a pattern
(def player (make-player session "sine" (cycle 220.0 440.0 880.0) 120.0))
(player-start player)
;; Stop playing
(player-stop player)
Tidal uses beats as the primary time unit. One beat is one quarter note at the current tempo.
(def-type Beats float)
;; Convert between beats and seconds
(bpm->beats-per-sec 120.0) ;; => 2.0 (120 BPM = 2 beats/sec)
(beats->sec 1.0 120.0) ;; => 0.5 (1 beat at 120 BPM = 0.5 seconds)
A pattern is a function from time (in beats) to a value:
(def-type Pattern<T> (fn [Beats] :T))
;; A pattern that returns 440 at any time
(def p (const 440))
(p 0.0) ;; => 440
(p 100.0) ;; => 440
Patterns are pull-based: they evaluate on demand when you call them with a time value. This makes them easy to compose and reason about.
;; Always returns the same value
(def p (const 440.0))
;; Shorthand: P macro
(def p (P 440.0)) ;; Same as (const 440.0)
;; Cycles through values
(def p (cycle 1 2 3 4))
(p 0.0) ;; => 1
(p 0.5) ;; => 1
(p 1.0) ;; => 2
(p 3.0) ;; => 4
(p 4.0) ;; => 1 (wraps)
;; Shorthand: P macro with multiple args
(def p (P 1 2 3 4)) ;; Same as (cycle 1 2 3 4)
;; Returns position within cycle (0 to 1)
(def p (phase 1.0))
(p 0.0) ;; => 0.0
(p 0.25) ;; => 0.25
(p 0.5) ;; => 0.5
(p 0.75) ;; => 0.75
(p 1.0) ;; => 0.0 (wraps)
;; Custom cycle length
(def p (phase 2.0))
(p 0.0) ;; => 0.0
(p 1.0) ;; => 0.5
(p 2.0) ;; => 0.0
;; Apply pattern at specific time
(pat-at (cycle 1 2 3) 1.0) ;; => 2
;; Map pattern over a range of times
(map (fn [t] ((cycle 1 2 3) t)) [0.0 1.0 2.0]) ;; => [1 2 3]
;; Slow down by factor (pattern takes longer to complete)
(def slow-melody (slow 2 (cycle 1 2 3)))
;; At time 0: 1, time 2: 2, time 4: 3
;; Speed up by factor (pattern completes faster)
(def fast-melody (fast 2 (cycle 1 2 3)))
;; At time 0: 1, time 0.5: 2, time 1: 3
;; Stretch and compress are aliases
(stretch 2 p) ;; Same as (slow 2 p)
(compress 2 p) ;; Same as (fast 2 p)
;; Shift pattern forward in time
(def shifted (shift 0.5 (cycle 1 2 3)))
;; At time 0: evaluates at 0.5 => 2
;; At time 0.5: evaluates at 1.0 => 3
;; rot is an alias for shift
(rot 0.25 p)
;; Repeat pattern n times within its cycle
(def repeated (repeat 3 (cycle 1 2)))
;; Cycle: [1, 1, 1, 2, 2, 2]
;; At time 0-0.33: 1, 0.33-0.66: 1, 0.66-1: 1, etc.
;; Play once, then silence
(def one-shot (once (const 1) 0))
;; At time 0-1: 1, time >1: 0
;; Mirror pattern within its cycle
(def mirrored (mirror (cycle 1 2 3)))
;; In a 1-beat cycle: [0, 0.5) forward, [0.5, 1) backward
;; Reverse time within each cycle
(def reversed (rev (cycle 1 2 3)))
;; Play patterns in sequence
(def seq-pattern (seq (cycle 1 2) (cycle 3 4)))
;; At time 0-1: [1, 2], time 1-2: [3, 4], time 2-3: [1, 2]
;; Sequence with custom lengths
(def seq-n-pattern (seq-n [(cycle 1 2) 2.0] [(cycle 3 4) 1.0]))
;; First pattern plays for 2 beats, second for 1 beat
;; Play patterns simultaneously
(def stack-pattern (stack (cycle 1 2) (cycle 10 20)))
;; At time 0: [1, 10], time 1: [2, 20]
;; Stack with custom combiner
(def sum-pattern (stack-with (fn [v] (reduce + 0 v)) (cycle 1 2) (cycle 3 4)))
;; At time 0: 1+3=4, time 1: 2+4=6
;; Convenience combinators
(sum (cycle 1 2) (cycle 3 4)) ;; Add all values
(multiply (cycle 1 2) (cycle 3 4)) ;; Multiply all values
;; Only play when condition is true
(def when-pattern (when (cycle true false) (const 1) 0))
;; At time 0: 1, time 1: 0, time 2: 1
;; If-then-else for patterns
(def if-pattern (if-pat (cycle true false) (const 1) (const 2)))
;; Play on every nth beat
(def every-pattern (every 2 (const 1) 0))
;; At time 0: 1, time 1: 0, time 2: 1, time 4: 1
;; Play on specific beats (nth)
(def nth-pattern (nth 2 (const 1) 0))
;; Same as every but uses floor(time) mod n
;; Bjorklund's algorithm for evenly distributed hits
(def euclid-pattern (euclid 8 3))
;; 3 hits in 8 steps: 1 0 0 1 0 0 1 0
def euclid-pattern (euclid 8 5 1))
;; 5 hits in 8 steps with offset 1
;; Select from patterns based on index
(def select-pattern (select (cycle 0 1 0 1) (const 10) (const 20)))
;; At time 0: 10, time 1: 20, time 2: 10
;; First non-zero value
(def first-pattern (first-nonzero 0 (const 0) (const 0) (const 5)))
;; At any time: 5
;; Add constant or pattern
(add (cycle 1 2 3) 10) ;; Add 10 to each value
(add (cycle 1 2) (cycle 3 4)) ;; Add two patterns
;; Multiply
(mul (const 10) 2) ;; Multiply by 2
(mul (cycle 1 2) (const 2)) ;; Multiply pattern by 2
;; Subtract and divide
(sub (const 10) 3)
(div (const 10) 2)
;; Map value range [from-low, from-high] to [to-low, to-high]
(def scaled (range (cycle 0.0 0.5 1.0) 0.0 1.0 440.0 880.0))
;; 0.0 -> 440, 0.5 -> 660, 1.0 -> 880
;; Clamp values to range
(def clamped (clamp (cycle -5 5 15) 0.0 10.0))
;; -5 -> 0, 5 -> 5, 15 -> 10
;; Add random jitter
(def jittered (jitter (const 440.0) 10.0 440.0))
;; Randomly selects from patterns
(def random-select (rand-select (const 1) (const 2) (const 3)))
;; Noise patterns
(def perlin (noise 1.0 0.0)) ;; Perlin noise
(def white (white-noise)) ;; White noise
;; Sine wave (freq, amp, phase)
(def sine-pattern (sine 1.0 1.0 0.0))
;; Square wave (freq, amp, duty cycle)
(def square-pattern (square 1.0 1.0 0.5))
;; Sawtooth wave (freq, amp)
(def saw-pattern (saw 1.0 1.0))
;; Triangle wave (freq, amp)
(def tri-pattern (tri 1.0 1.0))
;; Attack-Release envelope
(def ar-env (ar 0.01 0.1 (const true)))
;; ADSR envelope (attack, decay, sustain, release, gate)
(def adsr-env (adsr 0.01 0.1 0.5 0.1 (const true)))
;; Play pattern with different time signature
(def polymeter-pattern (polymeter 2.0 (cycle 1 2 3)))
;; Pattern runs at 2x speed: at time 0: 1, time 0.5: 2, time 1: 3
(def half-meter (polymeter 0.5 (cycle 1 2 3)))
;; Pattern runs at 0.5x speed: at time 0: 1, time 2: 2, time 4: 3
;; Two patterns with different cycle lengths
(def polyrhythm-pattern (polyrhythm (cycle 1 2) 1.0 (cycle 10 20) 2.0))
;; Returns tuple: (pattern1-value, pattern2-value)
;; Pattern of patterns
(def outer (cycle (const 1) (const 2)))
(def nested (nest outer 1.0))
;; At time 0-1: pattern returns 1, so nested returns 1 at any time
;; At time 1-2: pattern returns 2, so nested returns 2 at any time
;; Stack patterns with different meters
(def stack-poly (stack-polymeter [
[(cycle 1 2) 1.0]
[(cycle 10 20) 2.0]
[(cycle 100 200) 0.5]
]))
;; Delayed copies of the same pattern
(def canon-pattern (canon (cycle 1 2 3) 4 0.25))
;; 4 copies, each delayed by 0.25 beats
;; Phase-shifted copies
(def spread-pattern (spread (cycle 1 2 3) 4 1.0))
;; 4 copies, spread across 1 beat
;; Pattern that respects time signatures
(def timesig-pattern (timesig (cycle 1 2 3) [(4.0 4) (3.0 4)]))
;; First 4 beats in 4/4, next 3 beats in 3/4
;; Create note pattern with pitch, velocity, duration, gate
(def note-pattern (note-pattern
(cycle 60 62 64) ;; Pitch (MIDI note)
(const 100) ;; Velocity
(const 0.25) ;; Duration (in beats)
(const true))) ;; Gate (on/off)
;; MIDI note to frequency
(note->freq 69) ;; => 440.0 (A4)
;; Create chord from multiple note patterns
(def chord (chord-pattern
(const 48) ;; C3
(const 52) ;; E3
(const 55))) ;; G3 (C major)
;; Arpeggiate a chord
(def arpeggio (arp-pattern (const [48 52 55]) 4.0))
;; Plays notes [48 52 55] at 4x speed (16th notes)
;; Drum pattern with (note, velocity) pairs
(def drum-pattern (drum-pattern [(36 127) (none 0) (38 100) (none 0)]))
;; Convenience helpers
(def kick (kick [0.0 2.0])) ;; Bass drum on beats 0 and 2
(def snare (snare [1.0 3.0])) ;; Snare on beats 1 and 3
(def hat (hat [0.0 0.5 1.0 1.5])) ;; Hi-hat on 8th notes
(import tidal/live)
;; Create a live session at 120 BPM
(def session (make-live-session 120.0))
;; Create a player
(def player (make-player "sine" (const 440.0) 120.0))
;; Start playing
(player-start player)
;; Stop playing
(player-stop player)
;; Replace pattern in a running player
(player-update player (cycle 220.0 440.0 880.0))
;; Swap multiple patterns
(swap-patterns player new-patterns-map)
;; Set default pattern (d1 in TidalCycles)
(def d1 (swap-patterns player my-pattern))
;; Silence all (hush in TidalCycles)
(def hush (swap-patterns player (const 0)))
;; Watch a file for changes and reload
(def watcher (watch-pattern "my-pattern.tur" session "sine" 120.0))
;; Evaluate pattern from string
(def p (eval-pattern "(cycle 1 2 3)"))
;; Pattern with internal state
(def counter (make-stateful-pattern 0
(fn [count time]
[(inc count) (const count)])))
;; Step the stateful pattern
(let [[new-state new-pat] (step-stateful counter 0.0)]
;; new-state: 1
;; new-pat: returns 0 at any time
)
;; Counter with modulo
(def counter-10 (counter-pattern 10))
;; Parse TidalCycles-style mini-notation
(def p (parse-mini "1 2 3")) ;; Same as (cycle 1 2 3)
(def p (parse-mini "1*2 2 3")) ;; Fast first element
def p (parse-mini "[1 2] [3 4]") ;; Stack patterns
;; Shorthand for parse-mini
(def p (s "1 2 3"))
;; Parse drum mini-notation
(def p (parse-drums "bd sd")) ;; Bass drum, snare
(def p (parse-drums "bd*2 sd")) ;; Two bass drums, snare
def p (s "bd(5,8) sd(3,8)") ;; Euclidean drum patterns
| Operator | Description | Example |
|---|---|---|
+ |
Add | "1+2" = (add (const 1) (const 2)) |
- |
Subtract | "1-2" = (sub (const 1) (const 2)) |
* |
Multiply | "1*2" = (mul (const 1) (const 2)) |
/ |
Divide | "1/2" = (div (const 1) (const 2)) |
< |
Slow | "1<2" = (slow 2 (const 1)) |
> |
Fast | "1>2" = (fast 2 (const 1)) |
[] |
Stack | "[1 2]" = (stack (const 1) (const 2)) |
(,) |
Euclidean | "(3,8)" = (euclid 8 3) |
;; Cache pattern results
(def cached-pat (cached (cycle 1 2 3) 1024))
;; Caches 1024 values for fast lookup
;; Inline simple patterns for better performance
(def inlined (inline-pattern (slow 2 (cycle 1 2))))
;; Fuse consecutive operations into one function
(def fused (fuse-pattern (slow 2 (fast 2 (cycle 1 2)))))
;; slow 2 * fast 2 = identity, so fuses to (cycle 1 2)
(import tidal)
;; Simple melody
(def melody (cycle 60 62 64 65 67 69 71 72))
;; Add harmony
(def harmony (stack
melody
(add melody (const 12)) ;; Octave up
(add melody (const 7)))) ;; Fifth
;; Add rhythm
(def rhythm (stack
(kick [0.0 2.0])
(snare [1.0 3.0])
(hat [0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5])))
;; Combine everything
(def full-pattern (stack melody harmony rhythm))
(import tidal/polyrhythm)
;; Kick in 4/4, snare in 3/4, hats in 6/8
(def poly-beat (stack
(polymeter 1.0 (kick [0.0 1.0 2.0 3.0]))
(polymeter 1.333 (snare [0.0 1.0 2.0]))
(polymeter 0.667 (hat [0.0 0.5 1.0 1.5 2.0 2.5]))))
(import tidal/polyrhythm tidal/transform)
;; Euclidean melody
(def euclid-melody (when (fn [t] (> ((euclid 16 5) t) 0.5))
(cycle 60 64 67 72) 0))
;; With random variation
(def generative (jitter euclid-melody 2.0 60.0))
;; Add harmony
(def full-generative (stack
generative
(add generative (const 12))
(add generative (const 7))))
See tidal-api.md for complete API documentation.
See tidal-cookbook.md for common patterns and recipes.
MIT License
Generated for Turmeric project - TidalCycles-like DSL