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
238 lines
10 KiB
Markdown
238 lines
10 KiB
Markdown
# Soundgen — 8-bit синтезатор на Rust + LLM-интеграция
|
||
|
||
## Статус: Фазы 1-4 завершены
|
||
|
||
| Параметр | Значение |
|
||
|---|---|
|
||
| Язык | Rust |
|
||
| Игра | Rust + Vulkan (custom engine) |
|
||
| Генераторы | Свои, с нуля |
|
||
| GUI | egui (фаза 3) |
|
||
| MCP | Да, через `rmcp` (официальный Rust MCP SDK) |
|
||
| Звуки | SFX, UI sounds, Ambient |
|
||
| Генерация | Build time → WAV-ассеты |
|
||
| Лицензия | MIT |
|
||
|
||
## Архитектура Cargo workspace
|
||
|
||
```
|
||
soundgen/
|
||
├── Cargo.toml # [workspace], shared deps
|
||
├── LICENSE # MIT
|
||
├── README.md
|
||
│
|
||
├── crates/
|
||
│ ├── soundgen-core/ # чистый движок синтеза, без I/O
|
||
│ │ └── src/
|
||
│ │ ├── lib.rs # реэкспорты
|
||
│ │ ├── generator.rs # trait Generator, Voice, Param
|
||
│ │ ├── voice/
|
||
│ │ │ ├── pulse.rs # PulseChannel (duty 12.5/25/50/75%)
|
||
│ │ │ ├── triangle.rs # TriangleChannel (32-step)
|
||
│ │ │ ├── noise.rs # NoiseChannel (LFSR white/periodic)
|
||
│ │ │ ├── dpcm.rs # (фаза 2)
|
||
│ │ │ ├── wavetable.rs # (фаза 2, GB wave-style)
|
||
│ │ │ └── fm.rs # (фаза 2, 4-op FM)
|
||
│ │ ├── effect/
|
||
│ │ │ ├── envelope.rs # ADSR
|
||
│ │ │ ├── sweep.rs # freq sweep (linear/exponential)
|
||
│ │ │ ├── vibrato.rs # LFO (фаза 2)
|
||
│ │ │ └── filter.rs # biquad lowpass/highpass (свой)
|
||
│ │ ├── automation.rs # FrequencyAutomation, Arpeggiator
|
||
│ │ └── mixer.rs # Mixer: N голосов → стерео
|
||
│ │
|
||
│ ├── soundgen-fmt/ # декларативный формат + пресеты
|
||
│ │ └── src/
|
||
│ │ ├── lib.rs
|
||
│ │ ├── spec.rs # SoundSpec (serde JSON)
|
||
│ │ └── preset.rs # PresetRegistry, built-in presets
|
||
│ │
|
||
│ ├── soundgen-io/ # I/O слой
|
||
│ │ └── src/
|
||
│ │ ├── lib.rs
|
||
│ │ ├── wav.rs # hound: WAV writer (16/24-bit)
|
||
│ │ └── realtime.rs # cpal (фаза 3)
|
||
│ │
|
||
│ ├── soundgen-seq/ # секвенсер (фаза 2)
|
||
│ │ └── src/
|
||
│ │ ├── pattern.rs # Pattern, Row, Note
|
||
│ │ ├── song.rs # Song, Track
|
||
│ │ └── sequencer.rs # Sequencer
|
||
│ │
|
||
│ ├── soundgen-cli/ # CLI binary
|
||
│ │ └── src/
|
||
│ │ ├── main.rs
|
||
│ │ └── commands/
|
||
│ │ ├── gen.rs # gen --preset jump --out ...
|
||
│ │ ├── render.rs # render song.json --out ...
|
||
│ │ └── list.rs # list-presets
|
||
│ │
|
||
│ ├── soundgen-mcp/ # MCP server для LLM (фаза 2)
|
||
│ │ └── src/
|
||
│ │ ├── lib.rs
|
||
│ │ ├── server.rs # rmcp ServerHandler impl
|
||
│ │ └── tools.rs # generate_sfx, list_presets, render
|
||
│ │
|
||
│ └── soundgen-gui/ # egui GUI (фаза 3)
|
||
│ └── src/
|
||
│ ├── main.rs
|
||
│ ├── keyboard.rs # виртуальная клавиатура
|
||
│ ├── channel_panel.rs # регуляторы каналов
|
||
│ └── preset_browser.rs # браузер пресетов
|
||
│
|
||
├── presets/ # встроенная библиотека (JSON)
|
||
│ ├── sfx/
|
||
│ │ ├── jump.json
|
||
│ │ ├── explosion.json
|
||
│ │ ├── coin.json
|
||
│ │ ├── laser.json
|
||
│ │ ├── hit.json
|
||
│ │ └── powerup.json
|
||
│ ├── ui/
|
||
│ │ ├── click.json
|
||
│ │ ├── hover.json
|
||
│ │ ├── confirm.json
|
||
│ │ └── error.json
|
||
│ └── ambient/ # (фаза 2)
|
||
│ ├── wind.json
|
||
│ ├── rain.json
|
||
│ └── drone.json
|
||
│
|
||
├── examples/
|
||
│ ├── play_melody.rs # фаза 1: pulse+triangle+noise → WAV
|
||
│ ├── generate_sfx.rs # фаза 1: SFX из кода
|
||
│ └── render_song.rs # фаза 2: song.json → WAV
|
||
│
|
||
└── tests/
|
||
└── integration.rs # рендер WAV, проверка RMS/длительности
|
||
```
|
||
|
||
## Декларативный формат (JSON) — LLM-friendly
|
||
|
||
Пример `presets/sfx/jump.json`:
|
||
|
||
```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
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Пример `presets/sfx/explosion.json`:
|
||
|
||
```json
|
||
{
|
||
"name": "explosion",
|
||
"duration": 0.8,
|
||
"channels": [
|
||
{
|
||
"type": "noise",
|
||
"mode": "white",
|
||
"filter": { "type": "lowpass", "cutoff": { "start": 2000, "end": 200, "curve": "exponential" } },
|
||
"envelope": { "attack": 0.005, "decay": 0.7, "sustain": 0.0, "release": 0.095 },
|
||
"volume": 0.9
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
LLM генерирует такой JSON → CLI/MCP рендерит → WAV-ассет готов.
|
||
|
||
## MCP server — tools для LLM
|
||
|
||
Сервер на `rmcp` (stdio transport), expose 3 tool'а:
|
||
|
||
| Tool | Параметры | Возвращает |
|
||
|---|---|---|
|
||
| `list_presets` | `category?: "sfx"\|"ui"\|"ambient"` | JSON-список пресетов с описаниями |
|
||
| `generate_sfx` | `preset: string`, `params?: object`, `out_path: string` | Путь к WAV + метаданные (длительность, размер) |
|
||
| `render_sound` | `spec: object` (полный SoundSpec JSON), `out_path: string` | Путь к WAV + метаданные |
|
||
|
||
Workflow LLM при разработке игры:
|
||
|
||
1. LLM вызывает `list_presets` → видит доступные звуки
|
||
2. LLM вызывает `generate_sfx { preset: "jump", out_path: "assets/sfx/jump.wav" }` → WAV создан
|
||
3. Для кастомного звука: LLM пишет JSON-spec и вызывает `render_sound { spec: {...}, out_path: "assets/sfx/laser.wav" }`
|
||
4. LLM пишет Rust-код игры, загружающий WAV через `hound`/`rodio`
|
||
|
||
## Зависимости
|
||
|
||
```toml
|
||
# workspace
|
||
[workspace.dependencies]
|
||
hound = "0.5" # WAV I/O
|
||
serde = { version = "1", features = ["derive"] }
|
||
serde_json = "1"
|
||
|
||
# soundgen-io (фаза 3)
|
||
cpal = "0.15"
|
||
|
||
# soundgen-mcp (фаза 2)
|
||
rmcp = "1.7"
|
||
tokio = { version = "1", features = ["full"] }
|
||
|
||
# soundgen-cli
|
||
clap = { version = "4", features = ["derive"] }
|
||
|
||
# soundgen-gui (фаза 3)
|
||
eframe = "0.27"
|
||
egui = "0.27"
|
||
```
|
||
|
||
## Фазы разработки
|
||
|
||
### Фаза 1 — Core + CLI + Пресеты (MVP)
|
||
|
||
Цель: работающая библиотека, CLI, и набор готовых пресетов для SFX/UI.
|
||
|
||
- **soundgen-core**: `Generator`/`Voice`/`Param` трейты, `PulseChannel`, `TriangleChannel`, `NoiseChannel`, `Envelope` (ADSR), `Sweep` (linear/exponential), `FrequencyAutomation`, `Mixer`, простые biquad filters (lowpass/highpass — свой код)
|
||
- **soundgen-fmt**: `SoundSpec` (serde), `PresetRegistry` (загрузка JSON из `presets/`)
|
||
- **soundgen-io**: `wav::write(path, samples, sr, bit_depth)` через `hound`
|
||
- **soundgen-cli**: `gen --preset <name> [--param k=v]... --out <path>`, `gen --from <spec.json> --out <path>`, `list-presets [--category sfx|ui|ambient]`
|
||
- **Пресеты**: jump, explosion, coin, laser, hit, powerup (SFX); click, hover, confirm, error (UI)
|
||
- **Examples**: `play_melody`, `generate_sfx`
|
||
- **Тесты**: unit на duty cycle/частоту/envelope; интеграционный — рендер WAV, проверка длительности и RMS
|
||
|
||
### Фаза 2 — Extended synthesis + Sequencer + MCP
|
||
|
||
Цель: расширенный синтез, секвенсер для мелодий, MCP-сервер для LLM.
|
||
|
||
- **soundgen-core**: `DpcmChannel`, `WavetableChannel`, `FmChannel`, `Vibrato` (LFO)
|
||
- **soundgen-seq**: `Pattern`, `Song`, `Sequencer` (tempo, rows, note triggers)
|
||
- **soundgen-mcp**: rmcp-сервер, tools: `list_presets`, `generate_sfx`, `render_sound`
|
||
- **Ambient пресеты**: wind (filtered noise + slow LFO), rain (noise + highpass), drone (low freq sustained)
|
||
- **CLI**: `render song.json --out music.wav`
|
||
- **Example**: `render_song`
|
||
|
||
### Фаза 3 — GUI (egui)
|
||
|
||
Цель: интерактивный редактор для человека.
|
||
|
||
- egui app: виртуальная клавиатура (мышью/клавишами), панель каналов (duty, freq, envelope), браузер пресетов, pattern editor
|
||
- cpal realtime playback (ring buffer)
|
||
- Сохранение/загрузка проектов (JSON)
|
||
|
||
### Фаза 4 — Advanced (опционально)
|
||
|
||
- NES-authentic: нелинейный DAC, DPCM corruption, hardware-accurate mixing
|
||
- VST плагин через `nih-plug`
|
||
- Runtime library для прямой интеграции в Rust+Vulkan игру
|
||
|
||
## Ключевые принципы
|
||
|
||
- **Чистый core без I/O** — рендеринг в `Vec<f32>` в тестах без звуковой карты
|
||
- **Sample-accurate tick** — `Voice::tick() -> f32`, `Mixer` собирает буфер
|
||
- **Без allocations в hot path** — генераторы работают с `&mut self`, без `Vec` в audio thread
|
||
- **JSON-first для LLM** — каждый звук описывается JSON, LLM генерирует JSON естественно
|
||
- **Пресеты как data, не code** — JSON-файлы в `presets/`, расширяемые без перекомпиляции
|