# Waveform
A lightweight OSC transport layer for communicating with SuperCollider from Elixir.
Waveform provides low-level OSC messaging, node/group management, and a simple API for triggering synths. Perfect for live coding, algorithmic composition, and building custom audio applications on top of SuperCollider.
## Prerequisites
**⚠️ SuperCollider must be installed on your system before using Waveform.**
### Installing SuperCollider
**macOS:**
```bash
brew install supercollider
```
**Linux:**
```bash
# Debian/Ubuntu
sudo apt-get install supercollider
# Arch
sudo pacman -S supercollider
```
**Windows:**
Download from [supercollider.github.io](https://supercollider.github.io/)
### Installing SuperDirt (Optional, for Pattern-Based Live Coding)
If you want to use Waveform's pattern scheduler with SuperDirt (TidalCycles-style live coding):
1. **Install the SuperDirt Quark** (one-time setup):
Open SuperCollider IDE and run:
```supercollider
Quarks.install("SuperDirt");
thisProcess.recompile;
```
2. **Install Dirt-Samples** (optional but recommended):
Dirt-Samples provides 217 sample banks (1800+ audio files) including drum machines,
percussion, synths, and instruments. Waveform includes an automated installer:
```bash
mix waveform.install_samples
```
This will:
- Download ~200MB of samples
- Install them to the correct location for your OS
- Verify the installation
- Provide next steps
**Note:** Waveform is pre-configured with optimal buffer settings (4096 buffers)
to support all Dirt-Samples. No additional configuration needed!
3. **Start SuperDirt** (automatic):
Waveform automatically starts SuperDirt with the correct configuration when you use
`Helpers.ensure_superdirt_ready()`. The samples are loaded automatically from the
installed Dirt-Samples directory.
```elixir
alias Waveform.Helpers
Helpers.ensure_superdirt_ready()
```
4. **Install sc3-plugins** (optional, for additional synths):
The sc3-plugins provide additional synthesizers like `superpiano`, `supermandolin`,
`supergong`, and more. These are physical modeling synths that extend SuperDirt's
capabilities beyond sample playback.
**Installation:**
```bash
# macOS
brew install sc3-plugins
# Linux (Debian/Ubuntu)
sudo apt-get install sc3-plugins
# Arch Linux
sudo pacman -S sc3-plugins
# Windows
# Download from https://supercollider.github.io/sc3-plugins/
```
After installation, restart SuperCollider and Waveform to use these synths.
**Note:** The demos in this repository use samples from Dirt-Samples by default
and don't require sc3-plugins. If you want to experiment with synths like
`superpiano`, install sc3-plugins and see `test_superpiano.exs` for an example.
### Custom Installation Path
If SuperCollider is installed in a non-standard location, set the `SCLANG_PATH` environment variable:
```bash
export SCLANG_PATH=/path/to/sclang
```
### Verify Installation
After installing SuperCollider (and optionally SuperDirt), run:
```bash
mix waveform.doctor
```
This will verify that your system is properly configured, including checking for SuperDirt if you plan to use pattern-based features.
## Features
- **OSC Transport**: Send and receive OSC messages to/from SuperCollider
- **Process Management**: Automatically manages the `sclang` process
- **Node & Group Management**: Track synth nodes and organize them into groups
- **Simple API**: Minimal, focused API for triggering synths
- **SuperDirt Integration**: TidalCycles-compatible sample playback and effects
- **Pattern Scheduler**: High-precision continuous pattern playback with cycle-based timing
- **Hot-Swappable Patterns**: Change patterns while they're playing without stopping
## Installation
Add `waveform` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:waveform, "~> 0.2.0"}
]
end
```
Then run:
```bash
mix deps.get
mix waveform.doctor # Verify SuperCollider is installed
```
## Quick Start
```elixir
# Start your application (Waveform starts automatically)
# The sclang process and SuperCollider server will boot
# Trigger a synth (assumes you have a synth named "default" loaded)
alias Waveform.Synth
Synth.trigger("default", note: 60, amp: 0.5)
```
## Usage
### Defining Synths
Waveform does not include any built-in synth definitions. You need to define synths in SuperCollider first.
You can define synths in several ways:
**Option 1: Define in SuperCollider directly**
```elixir
alias Waveform.Lang
Lang.send_command("""
SynthDef(\\saw, { |freq=440, amp=0.1, out=0|
Out.ar(out, Saw.ar(freq, amp))
}).add;
""")
```
**Option 2: Load from a file**
```elixir
# Place your .scsyndef files in a directory
OSC.load_synthdef_dir("/path/to/synthdefs")
```
**Option 3: Use SuperDirt**
For TidalCycles-style live coding, load SuperDirt which includes many synths and samples. See the [SuperDirt integration](#integrating-with-superdirt) section below.
### Triggering Synths
Once you have synths defined, trigger them with:
```elixir
alias Waveform.Synth
# Basic synth trigger with parameters
Synth.trigger("saw", note: 60, amp: 0.5, cutoff: 1000)
# Specify node and group IDs manually
Synth.trigger("kick", [amp: 0.8], node_id: 1001, group_id: 1)
# Use the convenience play/2 function
Synth.play(60, synth: "piano", amp: 0.6)
```
### Low-Level OSC API
For more control, use the OSC module directly:
```elixir
alias Waveform.OSC
# Send raw OSC commands
OSC.new_synth("my-synth", node_id, :head, group_id, [:freq, 440, :amp, 0.5])
# Create a new group
OSC.new_group(group_id, :tail, parent_group_id)
# Delete a group and all its nodes
OSC.delete_group(group_id)
# Load synth definitions from a directory (if you have custom synthdefs)
OSC.load_synthdef_dir("/path/to/your/synthdefs")
```
### Node and Group Management
```elixir
alias Waveform.OSC.Node
alias Waveform.OSC.Group
# Get the next available node ID
%{id: node_id} = Node.next_synth_node()
# Get a process-specific group
%{id: group_id} = Group.synth_group(self())
# Create a named group
group = Group.chord_group("my-chord")
```
### Sending Commands to SuperCollider
You can send arbitrary SuperCollider code to the `sclang` interpreter:
```elixir
alias Waveform.Lang
Lang.send_command("""
SynthDef(\\simple, { |freq=440, amp=0.1|
Out.ar(0, SinOsc.ar(freq, 0, amp))
}).add;
""")
```
## Architecture
Waveform starts a supervision tree with these processes:
- **Waveform.Lang** - Manages the `sclang` process
- **Waveform.OSC** - Handles OSC message transport
- **Waveform.OSC.Node.ID** - Allocates unique node IDs (Agent)
- **Waveform.OSC.Node** - Tracks node lifecycle
- **Waveform.OSC.Group** - Manages groups
## Use Cases
### Live Coding in Livebook
```elixir
Mix.install([
{:waveform, "~> 0.2.0"}
])
alias Waveform.Synth
# Define a pattern
notes = [60, 64, 67, 72]
# Play the pattern
Task.async(fn ->
Stream.cycle(notes)
|> Enum.each(fn note ->
Synth.play(note, synth: "default", amp: 0.5)
Process.sleep(250)
end)
end)
```
### Building a Pattern Engine
Waveform is designed to be a foundation for higher-level pattern languages (like TidalCycles or Strudel):
```elixir
defmodule MyPatternEngine do
alias Waveform.Synth
def schedule_event(%Event{time: time, synth: synth, params: params}) do
Process.send_after(self(), {:trigger, synth, params}, time)
end
def handle_info({:trigger, synth, params}, state) do
Synth.trigger(synth, params)
{:noreply, state}
end
end
```
### Pattern-Based Live Coding with SuperDirt
Waveform includes built-in SuperDirt integration and a high-precision pattern scheduler for TidalCycles-style live coding.
**Prerequisites:** Make sure SuperDirt is installed and loaded (see [Prerequisites](#prerequisites)).
**Basic SuperDirt playback:**
```elixir
alias Waveform.SuperDirt
# Start SuperDirt in SuperCollider
Waveform.Lang.send_command("SuperDirt.start;")
# Trigger individual samples
SuperDirt.play(s: "bd") # Bass drum
SuperDirt.play(s: "sn", n: 2, gain: 0.8) # Snare variant 2
SuperDirt.play(s: "cp", room: 0.5, size: 0.8) # Clap with reverb
```
**Continuous pattern playback:**
```elixir
alias Waveform.PatternScheduler
# Set tempo (0.5625 = 135 BPM)
PatternScheduler.set_cps(0.5625)
# Define a drum pattern (events at cycle positions 0.0 to 1.0)
drums = [
{0.0, [s: "bd"]}, # Kick on the 1
{0.25, [s: "cp"]}, # Clap on the 2
{0.5, [s: "sn"]}, # Snare on the 3
{0.75, [s: "cp"]} # Clap on the 4
]
# Start the pattern looping
PatternScheduler.schedule_pattern(:drums, drums)
# Add a hi-hat pattern
hats = [
{0.0, [s: "hh", n: 0]},
{0.125, [s: "hh", n: 1]},
{0.25, [s: "hh", n: 0]},
{0.375, [s: "hh", n: 1]},
{0.5, [s: "hh", n: 0]},
{0.625, [s: "hh", n: 1]},
{0.75, [s: "hh", n: 0]},
{0.875, [s: "hh", n: 1]}
]
PatternScheduler.schedule_pattern(:hats, hats)
# Hot-swap the drum pattern while it's playing
new_drums = [
{0.0, [s: "bd", n: 1]},
{0.5, [s: "bd", n: 2]}
]
PatternScheduler.update_pattern(:drums, new_drums)
# Change tempo on the fly
PatternScheduler.set_cps(0.75) # Speed up to 180 BPM
# Stop a specific pattern
PatternScheduler.stop_pattern(:hats)
# Emergency stop all patterns
PatternScheduler.hush()
```
**For more advanced pattern languages**, see [KinoSpaetzle](https://github.com/rpmessner/kino_spaetzle) - a TidalCycles-inspired live coding environment for Livebook that adds mini-notation parsing on top of Waveform's scheduler.
## Development
```bash
# Clone the repository
git clone https://github.com/rpmessner/waveform.git
cd waveform
# Install dependencies
mix deps.get
# Compile
mix compile
# Run tests
mix test
# Check code coverage
MIX_ENV=test mix coveralls
# Generate documentation
mix docs
```
### Development Session Documentation
Development sessions are documented in `docs/sessions/` to maintain context across sessions and help contributors understand recent changes:
- **Session history**: See [docs/sessions/README.md](docs/sessions/README.md)
- **Latest session**: Check the most recent file in `docs/sessions/`
- **Project changelog**: See [CHANGELOG.md](CHANGELOG.md)
When working on Waveform (especially with AI assistants), consult the session documentation for context on recent architectural decisions and ongoing work.
## Roadmap
- [x] SuperDirt integration (✅ Complete - v0.3.0)
- [x] Pattern scheduling utilities (✅ Complete - v0.3.0)
- [ ] MIDI support
- [ ] More examples and guides
- [ ] Buffer management for custom samples
## Troubleshooting
### SuperDirt / Dirt-Samples Issues
#### Only hearing kick drum / Some samples don't play
**Cause:** SuperCollider's buffer limit is too low for Dirt-Samples (1817 audio files).
**Solution:** Waveform is pre-configured with `numBuffers = 4096` to support all samples.
If you're still experiencing issues:
1. Verify Dirt-Samples is installed:
```bash
mix waveform.install_samples
```
2. Restart your application completely (not just reload):
```elixir
# In IEx
:init.restart()
```
3. Check the installation:
```bash
ls ~/Library/Application\ Support/SuperCollider/downloaded-quarks/Dirt-Samples/
# Should show 217+ directories (bd, sn, hh, cp, etc.)
```
#### ERROR: No more buffer numbers
**Cause:** The buffer limit has been exceeded.
**Solution:** This is handled automatically by Waveform's server configuration. If you see this error:
1. Make sure you're using the latest version of Waveform
2. Restart your application
3. If the issue persists, you may have custom server options overriding Waveform's settings
#### Samples installed but not loading
**Cause:** Sample path not configured correctly.
**Solution:** Waveform automatically detects the correct sample path for your OS. If samples
still don't load:
1. Verify the path exists:
- **macOS**: `~/Library/Application Support/SuperCollider/downloaded-quarks/Dirt-Samples`
- **Linux**: `~/.local/share/SuperCollider/downloaded-quarks/Dirt-Samples`
- **Windows**: `~/AppData/Local/SuperCollider/downloaded-quarks/Dirt-Samples`
2. Check the sample count:
```bash
find ~/Library/Application\ Support/SuperCollider/downloaded-quarks/Dirt-Samples/ -name "*.wav" | wc -l
# Should show ~1800 files
```
3. If files are missing, reinstall:
```bash
mix waveform.install_samples
```
### General SuperCollider Issues
#### SuperCollider not found
Run `mix waveform.doctor` to diagnose installation issues.
If SuperCollider is installed in a custom location:
```bash
export SCLANG_PATH=/path/to/sclang
```
#### Server won't start
1. Check if another SuperCollider instance is running
2. Verify audio device permissions (macOS/Linux)
3. Check SuperCollider logs for errors
4. Try running SuperCollider IDE directly to diagnose
### Getting Help
1. Run diagnostics: `mix waveform.doctor`
2. Test SuperDirt: `mix waveform.check`
3. Report issues: https://github.com/rpmessner/waveform/issues
## Related Projects
- [KinoSpaetzle](https://github.com/rpmessner/kino_spaetzle) - TidalCycles-like live coding for Livebook (uses Waveform)
- [Harmony](https://github.com/rpmessner/harmony) - Music theory library for Elixir (useful for higher-level integrations)
- [SuperCollider](https://supercollider.github.io/) - The audio synthesis platform
## Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
When contributing significant changes:
1. Run `mix test` to ensure all tests pass
2. Check `MIX_ENV=test mix coveralls` to verify coverage
3. Update or create session documentation in `docs/sessions/` for major features
4. Update [CHANGELOG.md](CHANGELOG.md) with your changes
See [docs/sessions/README.md](docs/sessions/README.md) for context on recent development work.
## License
MIT License - see [LICENSE](LICENSE) for details.
## Acknowledgments
Built for live coding music in Elixir and Livebook. Inspired by TidalCycles and the SuperCollider community.