Soundgen is a Rust workspace for generating 8-bit/chiptune sound effects, UI sounds, and ambient textures for game audio assets. It features JSON-first sound specs, an MCP server for LLM tool-use, an egui GUI editor, and a runtime library with NES-authentic DAC emulation. Features: - 6 voice types: pulse, triangle, noise, DPCM, wavetable, FM - Effects: ADSR envelope, frequency sweep, biquad filter, vibrato - 13 built-in presets (SFX/UI/ambient) as JSON data files - MCP server: list_presets, generate_sfx, render_sound tools - Pattern-based sequencer (JSON song format) - egui GUI: virtual keyboard, preset browser, channel editor, undo/redo - Runtime: NES nonlinear DAC + SoundBank for game embedding - 90 tests, 0 warnings Crates: - soundgen-core: synthesis engine (no I/O) - soundgen-fmt: SoundSpec JSON schema + PresetRegistry - soundgen-io: WAV writer + audio playback - soundgen-seq: sequencer (patterns, songs) - soundgen-cli: gen/render/render-song/list-presets - soundgen-mcp: MCP server for LLM integration - soundgen-gui: egui editor - soundgen-runtime: NES DAC + SoundBank
141 lines
3.7 KiB
Markdown
141 lines
3.7 KiB
Markdown
# 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 `SoundSpec` JSON 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_sound` as 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
cargo run -p soundgen-mcp -- --presets-dir presets
|
|
```
|
|
|
|
Configure in Claude Desktop / MCP client:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"soundgen": {
|
|
"command": "/path/to/soundgen-mcp",
|
|
"args": ["--presets-dir", "/path/to/presets"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
LLM workflow:
|
|
1. `list_presets` → see available sounds
|
|
2. `generate_sfx { preset: "jump", out_path: "assets/jump.wav" }` → WAV created
|
|
3. `render_sound { spec: {...}, out_path: "assets/custom.wav" }` → custom sound
|
|
|
|
## SoundSpec JSON Format
|
|
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```rust
|
|
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
|