9997510765babe947478aa683774380fffe564b6
Replaces verbose multi-section guide with 4-line pipeline: AI generates → you rate → AI sees your top-rated as reference
Soundgen
8-bit sound synthesizer in Rust for game audio assets, with LLM integration via MCP.
Features
- 6 voice types: pulse (NES duty cycles), triangle, noise (LFSR), DPCM samples, wavetable (Game Boy wave), FM (2-operator)
- Effects: ADSR envelope, frequency sweep (linear/exponential), biquad filter (lowpass/highpass), vibrato
- JSON-first: every sound is a
SoundSpecJSON object — LLMs generate JSON, CLI/MCP render to WAV - Presets as data: 13 built-in presets in
presets/{sfx,ui,ambient}/— extend without recompilation - MCP server: LLMs can call
list_presets,generate_sfx,render_soundas tools - Sequencer: pattern-based song playback (JSON format)
- GUI: egui editor with virtual keyboard, preset browser, channel controls, sequencer
- Runtime library: NES-authentic nonlinear DAC + SoundBank for game embedding
- No allocations in audio hot path:
tick() -> f32,&mut self
Quick Start
# List available presets
cargo run --bin soundgen -- list-presets
# Generate a sound from a preset
cargo run --bin soundgen -- gen jump --out assets/jump.wav
# Generate with parameter override
cargo run --bin soundgen -- gen explosion --out assets/explosion.wav --param volume=0.95
# Render from a custom JSON spec
cargo run --bin soundgen -- render presets/sfx/laser.json --out laser.wav
# Render a song (sequencer)
cargo run --bin soundgen -- render-song song.json --out music.wav
# Launch the GUI editor
cargo run -p soundgen-gui
MCP Server (for LLM integration)
Run the MCP server on stdio:
cargo run -p soundgen-mcp -- --presets-dir presets
Configure in Claude Desktop / MCP client:
{
"mcpServers": {
"soundgen": {
"command": "/path/to/soundgen-mcp",
"args": ["--presets-dir", "/path/to/presets"]
}
}
}
LLM workflow:
list_presets→ see available soundsgenerate_sfx { preset: "jump", out_path: "assets/jump.wav" }→ WAV createdrender_sound { spec: {...}, out_path: "assets/custom.wav" }→ custom sound
SoundSpec JSON Format
{
"name": "jump",
"duration": 0.3,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 200, "end": 800, "curve": "exponential" },
"envelope": { "attack": 0.01, "decay": 0.15, "sustain": 0.0, "release": 0.14 },
"volume": 0.7
}
]
}
Channel types: pulse, triangle, noise
Song JSON Format (Sequencer)
{
"bpm": 120,
"rows_per_beat": 4,
"tracks": [
{ "type": "pulse", "duty": 50, "volume": 0.4 }
],
"patterns": [
{ "rows": [ { "notes": [{"frequency": 440}] }, {"notes": [null]} ] }
],
"pattern_order": [0]
}
Architecture
Cargo workspace:
| Crate | Purpose |
|---|---|
soundgen-core |
Synthesis engine (generators, effects, mixer). No I/O. |
soundgen-fmt |
SoundSpec JSON schema + PresetRegistry |
soundgen-io |
WAV writer (hound) + playback (subprocess) |
soundgen-seq |
Sequencer: patterns, songs |
soundgen-cli |
gen, render, render-song, list-presets |
soundgen-mcp |
MCP server for LLM tool-use |
soundgen-gui |
egui GUI editor |
soundgen-runtime |
NES-authentic DAC + SoundBank for game embedding |
Runtime Library (for game integration)
use soundgen_runtime::SoundBank;
// Load all presets at init time
let bank = SoundBank::load_dir(std::path::Path::new("presets"))?;
// Play by name (zero-allocation, returns pre-rendered buffer)
let (samples, sample_rate) = bank.get("jump").unwrap();
// Pitch-shifted variant
let (pitched, sr) = bank.get_pitched("jump", 1.5)?;
Built-in Presets
SFX: jump, explosion, coin, laser, hit, powerup UI: click, hover, confirm, error Ambient: wind, rain, drone
License
MIT
Languages
Rust
100%