Parser for Strudel/Tidal-style mini-notation, used in live coding and algorithmic music generation.
UzuParser converts text-based pattern notation into an Abstract Syntax Tree (AST). It's designed for live coding environments and algorithmic music systems, providing a simple yet expressive syntax for creating rhythmic and melodic patterns.
Note: UzuParser handles parsing only. For event generation and pattern transformations, see UzuPattern.
Add uzu_parser to your dependencies in mix.exs:
def deps do
[
{:uzu_parser, "~> 0.6.0"}
]
end# Parse a pattern - returns AST
{:ok, ast} = UzuParser.parse("bd sd hh")
# => {:ok, {:sequence, [
# %{type: :atom, value: "bd", source_start: 0, source_end: 2},
# %{type: :atom, value: "sd", source_start: 3, source_end: 5},
# %{type: :atom, value: "hh", source_start: 6, source_end: 8}
# ]}}
# For timed events, use UzuPattern:
pattern = UzuPattern.parse("bd sd hh")
haps = UzuPattern.query(pattern, 0)
# => [%Hap{value: %{sound: "bd"}, part: %{begin: Ratio.new(0,1), ...}}, ...]Space-separated sounds create a sequence:
UzuParser.parse("bd sd hh sd") # 4 elements in sequenceUse ~ for silence:
UzuParser.parse("bd ~ sd ~") # kick and snare on alternating beatsBrackets create subdivisions within a step:
UzuParser.parse("bd [sd sd] hh") # snare plays twice as fast
UzuParser.parse("bd [sd hh cp]") # three sounds in the time of one stepAsterisk multiplies elements:
UzuParser.parse("bd*4") # equivalent to "bd bd bd bd"
UzuParser.parse("[bd sd]*2") # repeat the subdivisionExclamation mark replicates with weight:
UzuParser.parse("bd!4") # four bds, each with weight 1
UzuParser.parse("[bd!3 sd]") # three bds then one sd in subdivisionSlash spreads pattern across cycles:
UzuParser.parse("[bd sd]/2") # pattern takes 2 cycles to completeColon selects sample variants:
UzuParser.parse("bd:0 bd:1 bd:2") # different kick drum samplesComma within brackets plays sounds simultaneously:
UzuParser.parse("[bd,sd]") # kick and snare together
UzuParser.parse("[c3,e3,g3]") # C major chordQuestion mark adds probability:
UzuParser.parse("bd?") # 50% chance to play
UzuParser.parse("bd?0.25") # 25% chance to playAt sign specifies relative duration:
UzuParser.parse("bd@2 sd") # kick twice as long as snare
UzuParser.parse("bd@3 sd@1") # kick takes 3/4, snare takes 1/4Underscore extends the previous sound:
UzuParser.parse("bd _ sd") # kick held for 2/3, snare for 1/3
UzuParser.parse("bd _ _ sd") # kick held for 3/4, snare for 1/4Angle brackets cycle through options:
UzuParser.parse("<bd sd hh>") # bd on cycle 0, sd on cycle 1, hh on cycle 2Pipe randomly selects one option:
UzuParser.parse("bd|sd|hh") # pick one randomly per cycleCurly braces create independent timing:
UzuParser.parse("{bd sd, hh hh hh}") # 2-against-3 polyrhythm
UzuParser.parse("{bd sd hh}%8") # fit pattern into 8 stepsParentheses create Euclidean patterns:
UzuParser.parse("bd(3,8)") # 3 hits distributed over 8 steps
UzuParser.parse("bd(3,8,1)") # with rotation offsetPipe with key:value sets sound parameters:
UzuParser.parse("bd|gain:0.8|speed:2")
UzuParser.parse("bd|lpf:2000|room:0.5")Period works like space but creates visual grouping:
UzuParser.parse("bd sd . hh cp") # same as "bd sd hh cp"The parser returns an AST with node types:
:sequence- Sequential elements:stack- Polyphonic (simultaneous) elements:subdivision- Bracketed group with optional modifiers:alternation- Angle bracket alternation:polymetric- Curly brace polymetric group:atom- Sound/note with optional modifiers:rest- Silence (~):elongation- Underscore continuation (_)
Each atom node includes:
value- Sound namesample- Sample number (from:n)repeat- Repetition count (from*n)replicate- Replication count (from!n)euclidean-{k, n, offset}tuple (from(k,n)or(k,n,o))probability- Float 0-1 (from?or?n)weight- Float (from@n)params- Map of parameters (from|key:value)source_start,source_end- Position in source string
All AST nodes include source positions for editor integration:
{:ok, {:sequence, nodes}} = UzuParser.parse("bd sd")
[bd, sd] = nodes
bd.source_start # => 0
bd.source_end # => 2
sd.source_start # => 3
sd.source_end # => 5This enables features like syntax highlighting, error reporting, and click-to-edit in live coding environments.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ UzuParser │────▶│ UzuPattern │────▶│ Waveform │
│ (parsing) │ │ (interpretation │ │ (audio) │
│ │ │ & transforms) │ │ │
│ • parse/1 │ │ • Interpreter │ │ • OSC │
│ • mini-notation │ │ • Pattern struct│ │ • SuperDirt │
│ • AST output │ │ • fast/slow/rev │ │ • Web Audio │
│ │ │ • query/2 │ │ • scheduling │
└─────────────────┘ └─────────────────┘ └─────────────────┘
- UzuParser: Parses mini-notation strings into AST
- UzuPattern: Interprets AST into patterns, applies transformations, queries events
- Waveform: Handles audio output via OSC/SuperDirt/Web Audio
# Successful parse
{:ok, ast} = UzuParser.parse("bd sd")
# Parse error
{:error, message} = UzuParser.parse("[bd sd")
# => {:error, "missing terminator: ]"}# Run tests
mix test
# Generate documentation
mix docs
# Format code
mix formatMIT License - See LICENSE for details
Inspired by the pattern mini-notation from TidalCycles and Strudel.