Patterns
Sequences and pattern manipulation
Patterns are the core of Sanic. They define sequences of notes that play over time.
Basic Patterns
[C4 E4 G4] // Three quarter notes
[C4 C4 C4 C4] // Four quarter notes
[C4 _ _ _] // One note, three restsNotes in a sequence are played evenly across one bar (4 beats).
Note Durations (Weights)
Use :weight after a note to make it take more time relative to other notes:
// C4 takes twice the time of E4 and G4
[C4:2 E4 G4] // C4: 50%, E4: 25%, G4: 25%
// Dotted rhythm (3 + 1)
[C4:3 E4] // C4: 75%, E4: 25%
// Long rest between notes
[C4 _:2 E4] // C4: 25%, rest: 50%, E4: 25%Weights are relative. [C4:2 E4 G4] has total weight 4 (2+1+1).
Per-Note Probability
Add ? after a note for probability-based triggering. Notes may randomly not play:
[C4? E4 G4?] // C4 and G4 have 50% chance (default)
[C4?0.3 E4 G4?0.7] // C4 has 30%, G4 has 70% chance
[_?0.5] // Rest with 50% chance (fills with silence or note)
[Cm7?0.5] // Chord with probabilityGreat for adding variation and organic feel to patterns.
Per-Note Velocity
Add @ after a note to control its volume/intensity (0.0-1.0):
[C4@0.8 E4@0.5 G4@1.0] // Explicit velocities
[C4@0.3 E4 G4] // E4 and G4 use default (1.0)
[Cm7@0.6] // Chord with velocityCombined Modifiers
Modifiers can be combined. Order: Note:weight?probability@velocity
[C4:2?0.5@0.8 E4 G4] // Double duration, 50% chance, 80% velocity
[C4?0.5@0.8 E4@0.6?0.3 G4] // Order of ? and @ is flexibleMicrotonal Patterns
Add + or - cents after a note for microtonal pitch control (100 cents = 1 semitone):
// Quarter-tone Arabic scale (Hijaz-ish)
let hijaz = [D4 Eb4+50 F#4 G4 A4 Bb4+50 C5 D5]
hijaz |> saw |> lowpass(800)
// Just intonation approximations
let pure = [C4 E4-14 G4+2 C5] // Pure major third, pure fifth
pure |> sine
// Direct frequency control
let drone = [432hz 432hz 432hz 432hz]
drone |> sine |> gain(0.3)Cent offsets work with all modifiers: C4+50:2@0.8?0.5
Nested Subdivisions
Use nested brackets to create uneven rhythms:
// C4 gets half, [E4 G4] gets half (E4 and G4 each get quarter)
[C4 [E4 G4]]
// Deep nesting: C4: 1/2, E4: 1/4, [G4 B4]: 1/4
[C4 [E4 [G4 B4]]]This is useful for syncopated rhythms and polyrhythms.
Chords
Play multiple notes simultaneously by naming chords:
[Cm7 Fmaj7 G7] // Chord progression
[Am _ Dm _] // Chords with restsOr use the chord function:
chord("Cm7") // Returns [C4 Eb4 G4 Bb4]
chord("Fmaj7", 3) // F major 7 in octave 3Power Chords
Power chords consist of just root + perfect 5th (7 semitones up). Without the 3rd, they're neither major nor minor—just raw power. Essential for rock, metal, punk, and industrial music. Use pow or p5 suffix:
[Epow Apow Dpow Apow] // Classic rock progression
[E2pow A2pow D3pow] // With explicit octaves
[Ep5 _ Ap5 _] // Alternative syntaxPower Chord Reference
| Chord | Notes | Interval |
|---|---|---|
Epow | E4, B4 | Root + P5 |
E2pow | E2, B2 | Root + P5 (octave 2) |
Apow | A4, E5 | Root + P5 |
Dbpow | Db4, Ab4 | Root + P5 |
Classic Power Chord Sounds
// Punk rock - fast strumming
let punk = [Epow Epow Apow Apow | Dpow Dpow Epow Epow]
|> saw |> distortion(0.7) |> fast(2)
// Metal chug - palm muted style with gate
let metal = [E2pow _ E2pow _ | G2pow A2pow E2pow _]
|> saw |> distortion(0.9) |> gate(pattern: "xxx_xx_x", rate: 1/16)
// Industrial - with ring mod for metallic texture
let industrial = [E2pow _ E2pow G2pow | A2pow _ E2pow _]
|> saw |> distortion(0.8) |> ring(freq: 150, mix: 0.3)
// Grunge - loose and heavy
let grunge = [Epow _ _ Gpow | Apow _ Epow _]
|> supersaw |> distortion(0.6) |> lowpass(2000) |> reverb(0.3)Pattern Transforms
Transform patterns with built-in functions:
Speed
[C4 E4 G4] |> fast(2) // Play twice as fast
[C4 E4 G4] |> slow(2) // Play twice as slowReverse
[C4 E4 G4] |> reverse // G4 E4 C4Shuffle
Randomly reorder notes (different each cycle):
[C4 E4 G4 B4] |> shuffle |> sawRotate
[C4 E4 G4 B4] |> rotate(1) // E4 G4 B4 C4
[C4 E4 G4 B4] |> rotate(-1) // B4 C4 E4 G4Transpose
[C4 E4 G4] |> transpose(7) // Up a fifth
[C4 E4 G4] |> transpose(-12) // Down an octaveConditional Transforms
Apply transforms conditionally:
Every N Cycles
[C4 E4 G4] |> every(4, reverse) // Reverse every 4th cycle
[C4 E4 G4] |> every(2, transpose(7)) // Transpose every other cycleProbability
[C4 E4 G4] |> prob(0.5, transpose(12)) // 50% chance to transpose
[C4 E4 G4] |> degrade(0.3) // 30% chance to skip each noteConvenience functions for common probabilities:
[C4 E4] |> sometimes(reverse) // 50% probability
[C4 E4] |> often(reverse) // 75% probability
[C4 E4] |> rarely(reverse) // 25% probability
[C4 E4] |> almostAlways(reverse) // 90% probability
[C4 E4] |> almostNever(reverse) // 10% probabilityCombining Patterns
Stack (Parallel)
Play patterns simultaneously:
let drums = [kick _ snare _]
let bass = [C2 _ _ C2]
play stack(drums, bass)Cat (Sequential)
Play patterns one after another:
let intro = [C4 E4]
let verse = [G4 B4]
play cat(intro, verse)Repeat
Repeat a pattern n times:
play repeat(4, [C4 E4]) |> saw // Plays [C4 E4] 4 timesChoose
Randomly pick one pattern each cycle:
let a = [C4 D4 E4 F4]
let b = [G4 A4 B4 C5]
play choose(a, b) |> saw // Randomly plays a or b each cycleArpeggiator
The arp() function reorders notes in a pattern based on a direction. Works on chords and any pattern.
Basic Directions
play Cm7 |> arp(up) |> saw // C, Eb, G, Bb (low to high)
play Cm7 |> arp(down) |> saw // Bb, G, Eb, C (high to low)
play Cm7 |> arp(random) |> saw // Random order each cycleBouncing Directions
play Cm7 |> arp(updown) |> saw // C, Eb, G, Bb, G, Eb (bounce up then down)
play Cm7 |> arp(downup) |> saw // Bb, G, Eb, C, Eb, G (bounce down then up)Alternating Directions
play Cm7 |> arp(outside) |> saw // C, Bb, Eb, G (alternate lowest/highest)
play Cm7 |> arp(inside) |> saw // Bb, C, G, Eb (alternate highest/lowest)Converging/Diverging
play Cm7 |> arp(converge) |> saw // C, Bb, Eb, G (pairs from outside in)
play Cm7 |> arp(diverge) |> saw // Eb, G, C, Bb (start middle, expand out)All Arp Modes
| Mode | Pattern (for C E G B) | Description |
|---|---|---|
up | C E G B | Low to high |
down | B G E C | High to low |
random | (varies) | Random each cycle |
updown | C E G B G E | Bounce up then down |
downup | B G E C E G | Bounce down then up |
outside | C B E G | Alternate outer notes |
inside | B C G E | Alternate inner notes |
converge | C B E G | Pairs moving inward |
diverge | E G C B | Start center, expand |
Works on any pattern, not just chords:
play [C4 D4 E4 F4 G4] |> arp(outside) |> saw
play [C4 E4 G4 B4] |> arp(updown) |> fast(2) |> sawEuclidean Rhythms
Generate mathematically distributed rhythms with euclid(hits, steps):
play [C4] |> euclid(3, 8) |> saw // Classic "tresillo" rhythm
play [C4] |> euclid(5, 8) |> saw // Cinquillo pattern
play [C4] |> euclid(7, 16) |> saw // More complex rhythmCommon Euclidean patterns:
| Pattern | Name |
|---|---|
euclid(2, 3) | Basic triplet feel |
euclid(3, 4) | Cumbia |
euclid(3, 8) | Tresillo (Cuban) |
euclid(5, 8) | Cinquillo |
euclid(7, 12) | West African bell |
With rotation:
play [C4] |> euclid(3, 8, 1) |> saw // Rotated by 1 stepSwing
Add swing feel:
[C4 C4 C4 C4] |> swing(0.6) // 60% swingHumanize
Adds random velocity variation for a more human feel:
[C4 E4 G4] |> humanize() // ±10% variation (default)
[C4 E4 G4] |> humanize(0.2) // ±20% variationPolymeters with over()
Sets the pattern's cycle duration for polymeters:
tempo 120
let a = [C4 E4 G4 B4] // 4 beats (default bar)
let b = [C4 E4 G4] |> over(3) // 3 beats
let c = [C4 E4] |> over(2) // 2 beats
play stack(a, b, c) // Patterns drift and realign
// Fractional durations
[C4 E4 G4] |> over(3/4) // 0.75 beats (triplet feel)