# UzuPattern
Pattern orchestration library for Strudel.js-style transformations in Elixir.
UzuPattern provides pattern manipulation functions (`fast`, `slow`, `rev`, `stack`, `cat`, `every`, `jux`, etc.) that work with events from [UzuParser](https://github.com/rpmessner/uzu_parser). It enables TidalCycles/Strudel.js-style live coding patterns with method chaining and cycle-aware transformations.
## Installation
Add `uzu_pattern` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:uzu_pattern, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
alias UzuPattern.Pattern
# Create a pattern from mini-notation
pattern = Pattern.new("bd sd hh cp")
# Apply transformations
pattern
|> Pattern.fast(2) # Double speed
|> Pattern.rev() # Reverse
|> Pattern.every(4, &Pattern.slow(&1, 2)) # Slow every 4th cycle
# Get events for a specific cycle
events = Pattern.query(pattern, 0)
```
## Architecture
```
┌─────────────────┐ ┌─────────────────┐
│ UzuParser │────▶│ UzuPattern │
│ (parsing) │ │ (transforms) │
│ │ │ ◀── HERE │
│ • parse/1 │ │ • fast/slow/rev │
│ • mini-notation │ │ • stack/cat │
│ • [%Event{}] │ │ • every/when │
└─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Waveform │
│ (audio) │
└─────────────────┘
```
## Features
### Time Modifiers
```elixir
Pattern.fast(pattern, 2) # Speed up by factor
Pattern.slow(pattern, 2) # Slow down by factor
Pattern.rev(pattern) # Reverse pattern
Pattern.early(pattern, 0.25) # Shift earlier (wraps)
Pattern.late(pattern, 0.25) # Shift later (wraps)
Pattern.ply(pattern, 3) # Repeat each event 3 times
Pattern.compress(pattern, 0.25, 0.75) # Fit into time segment
Pattern.zoom(pattern, 0.25, 0.75) # Extract and expand segment
Pattern.linger(pattern, 0.5) # Repeat first half to fill cycle
```
### Combinators
```elixir
Pattern.stack([p1, p2, p3]) # Play simultaneously
Pattern.cat([p1, p2, p3]) # Play sequentially
Pattern.palindrome(pattern) # Forward then backward
```
### Conditional (Cycle-Aware)
```elixir
Pattern.every(pattern, 4, &Pattern.rev/1) # Reverse every 4th cycle
Pattern.sometimes(pattern, &Pattern.fast(&1, 2)) # 50% chance each cycle
Pattern.often(pattern, fun) # 75% probability
Pattern.rarely(pattern, fun) # 25% probability
Pattern.iter(pattern, 4) # Rotate start position each cycle
Pattern.iter_back(pattern, 4) # Rotate backwards each cycle
```
### Degradation
```elixir
Pattern.degrade(pattern) # Remove ~50% of events
Pattern.degrade_by(pattern, 0.3) # Remove ~30% of events
```
### Stereo
```elixir
Pattern.jux(pattern, &Pattern.rev/1) # Left: original, Right: reversed
```
## Cycle-Aware Transformations
Some transformations (like `every`) depend on which cycle is being played. UzuPattern handles this through the `query/2` function:
```elixir
pattern = Pattern.new("bd sd") |> Pattern.every(2, &Pattern.rev/1)
Pattern.query(pattern, 0) # Cycle 0: reversed (0 mod 2 == 0)
Pattern.query(pattern, 1) # Cycle 1: normal
Pattern.query(pattern, 2) # Cycle 2: reversed
Pattern.query(pattern, 3) # Cycle 3: normal
```
## Integration with Waveform
UzuPattern provides a query function that Waveform can use for scheduling:
```elixir
# Create pattern with transforms
pattern =
"bd sd hh cp"
|> Pattern.new()
|> Pattern.fast(2)
|> Pattern.every(4, &Pattern.rev/1)
# Create query function for Waveform
query_fn = fn cycle -> UzuPattern.query(pattern, cycle) end
# Pass to Waveform's PatternScheduler
Waveform.PatternScheduler.schedule_pattern(:drums, query_fn)
```
## Examples
### Basic Drum Pattern
```elixir
"bd sd [hh hh] cp"
|> Pattern.new()
|> Pattern.fast(2)
|> Pattern.query(0)
```
### Layered Patterns
```elixir
kicks = Pattern.new("bd ~ bd ~")
snares = Pattern.new("~ sd ~ sd")
hats = Pattern.new("[hh hh hh hh]")
Pattern.stack([kicks, snares, hats])
```
### Evolving Pattern
```elixir
"bd sd hh cp"
|> Pattern.new()
|> Pattern.every(4, &Pattern.rev/1)
|> Pattern.every(8, &Pattern.fast(&1, 2))
|> Pattern.sometimes(&Pattern.degrade/1)
```
### Stereo Spread
```elixir
"arpy:0 arpy:1 arpy:2 arpy:3"
|> Pattern.new()
|> Pattern.jux(&Pattern.rev/1)
```
### Rhythmic Variations (Phase 2)
```elixir
# Drum roll effect with ply
"bd sd"
|> Pattern.new()
|> Pattern.ply(4)
# Compress pattern into middle of cycle
"bd sd hh cp"
|> Pattern.new()
|> Pattern.compress(0.25, 0.75)
# Zoom into second half
"bd sd hh cp"
|> Pattern.new()
|> Pattern.zoom(0.5, 1.0)
# Rotating pattern (evolves each cycle)
"bd sd hh cp"
|> Pattern.new()
|> Pattern.iter(4)
# Repeat first quarter
"bd sd hh cp"
|> Pattern.new()
|> Pattern.linger(0.25)
```
## Documentation
- **[ROADMAP.md](ROADMAP.md)** - Feature roadmap and Strudel.js parity tracking
- **[HANDOFF.md](HANDOFF.md)** - Architecture and integration guide
## Related Projects
- [UzuParser](https://github.com/rpmessner/uzu_parser) - Mini-notation parser
- [Waveform](https://github.com/rpmessner/waveform) - Audio playback via SuperDirt/MIDI
## License
MIT License - see [LICENSE](LICENSE) for details.