Initial release: 8-bit sound synthesizer with LLM/MCP integration

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
This commit is contained in:
Emil
2026-06-21 22:07:05 +03:00
commit c7d6c40683
74 changed files with 13345 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
{
"name": "drone",
"duration": 5.0,
"sample_rate": 44100,
"channels": [
{
"type": "triangle",
"frequency": { "start": 55, "end": 55 },
"envelope": { "attack": 1.0, "decay": 0.0, "sustain": 0.9, "release": 1.0 },
"volume": 0.4,
"pan": -0.3
},
{
"type": "triangle",
"frequency": { "start": 82.5, "end": 82.5 },
"envelope": { "attack": 1.5, "decay": 0.0, "sustain": 0.7, "release": 1.0 },
"volume": 0.3,
"pan": 0.3
},
{
"type": "pulse",
"duty": 12,
"frequency": { "start": 110, "end": 110 },
"envelope": { "attack": 2.0, "decay": 0.0, "sustain": 0.3, "release": 1.0 },
"volume": 0.15,
"pan": 0.0
}
]
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "rain",
"duration": 5.0,
"sample_rate": 44100,
"channels": [
{
"type": "noise",
"mode": "white",
"frequency": 10000,
"filter": {
"type": "highpass",
"cutoff": 2000,
"q": 0.7
},
"envelope": { "attack": 0.5, "decay": 0.0, "sustain": 0.5, "release": 0.5 },
"volume": 0.15,
"pan": 0.0
},
{
"type": "noise",
"mode": "white",
"frequency": 8000,
"filter": {
"type": "lowpass",
"cutoff": 1500,
"q": 0.5
},
"envelope": { "attack": 0.8, "decay": 0.0, "sustain": 0.4, "release": 0.5 },
"volume": 0.1,
"pan": 0.0
}
]
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "wind",
"duration": 5.0,
"sample_rate": 44100,
"channels": [
{
"type": "noise",
"mode": "white",
"frequency": 6000,
"filter": {
"type": "lowpass",
"cutoff": 800,
"q": 0.5
},
"envelope": { "attack": 1.0, "decay": 0.0, "sustain": 0.8, "release": 1.0 },
"volume": 0.3,
"pan": -0.2
},
{
"type": "noise",
"mode": "white",
"frequency": 4000,
"filter": {
"type": "lowpass",
"cutoff": 400,
"q": 0.5
},
"envelope": { "attack": 1.5, "decay": 0.0, "sustain": 0.6, "release": 1.0 },
"volume": 0.25,
"pan": 0.2
}
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "coin",
"duration": 0.15,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 800, "end": 1200, "curve": "exponential" },
"envelope": { "attack": 0.001, "decay": 0.04, "sustain": 0.0, "release": 0.1 },
"volume": 0.5,
"pan": 0.0
}
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"name": "explosion",
"duration": 0.8,
"sample_rate": 44100,
"channels": [
{
"type": "noise",
"mode": "white",
"frequency": 12000,
"filter": {
"type": "lowpass",
"cutoff": 3000,
"cutoff_sweep": { "start": 3000, "end": 100, "curve": "exponential" },
"q": 0.7
},
"envelope": { "attack": 0.002, "decay": 0.6, "sustain": 0.0, "release": 0.18 },
"volume": 0.85,
"pan": 0.0
},
{
"type": "noise",
"mode": "white",
"frequency": 4000,
"filter": {
"type": "lowpass",
"cutoff": 800,
"cutoff_sweep": { "start": 800, "end": 50, "curve": "exponential" },
"q": 1.0
},
"envelope": { "attack": 0.0, "decay": 0.4, "sustain": 0.0, "release": 0.1 },
"volume": 0.5,
"pan": 0.0
}
]
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "hit",
"duration": 0.12,
"sample_rate": 44100,
"channels": [
{
"type": "noise",
"mode": "white",
"frequency": 6000,
"filter": {
"type": "lowpass",
"cutoff": 1500,
"q": 1.5
},
"envelope": { "attack": 0.0, "decay": 0.02, "sustain": 0.0, "release": 0.08 },
"volume": 0.7,
"pan": 0.0
},
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 200, "end": 50, "curve": "exponential" },
"envelope": { "attack": 0.0, "decay": 0.03, "sustain": 0.0, "release": 0.06 },
"volume": 0.4,
"pan": 0.0
}
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "jump",
"duration": 0.25,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 150, "end": 600, "curve": "exponential" },
"envelope": { "attack": 0.005, "decay": 0.08, "sustain": 0.0, "release": 0.15 },
"volume": 0.6,
"pan": 0.0
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"name": "laser",
"duration": 0.3,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 25,
"frequency": { "start": 1200, "end": 80, "curve": "exponential" },
"envelope": { "attack": 0.001, "decay": 0.1, "sustain": 0.0, "release": 0.18 },
"filter": {
"type": "lowpass",
"cutoff": 4000,
"q": 2.0
},
"volume": 0.55,
"pan": 0.0
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "powerup",
"duration": 0.5,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 200, "end": 1600, "curve": "exponential" },
"envelope": { "attack": 0.01, "decay": 0.2, "sustain": 0.3, "release": 0.25 },
"volume": 0.5,
"pan": 0.0
},
{
"type": "triangle",
"frequency": { "start": 100, "end": 800, "curve": "exponential" },
"envelope": { "attack": 0.02, "decay": 0.25, "sustain": 0.2, "release": 0.2 },
"volume": 0.3,
"pan": 0.0
}
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "click",
"duration": 0.05,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 1000, "end": 1000 },
"envelope": { "attack": 0.0, "decay": 0.0, "sustain": 0.0, "release": 0.04 },
"volume": 0.3,
"pan": 0.0
}
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "confirm",
"duration": 0.15,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 600, "end": 900, "curve": "exponential" },
"envelope": { "attack": 0.005, "decay": 0.03, "sustain": 0.0, "release": 0.1 },
"volume": 0.35,
"pan": 0.0
}
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "error",
"duration": 0.2,
"sample_rate": 44100,
"channels": [
{
"type": "pulse",
"duty": 50,
"frequency": { "start": 200, "end": 150 },
"envelope": { "attack": 0.005, "decay": 0.05, "sustain": 0.3, "release": 0.12 },
"volume": 0.4,
"pan": 0.0
}
]
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "hover",
"duration": 0.04,
"sample_rate": 44100,
"channels": [
{
"type": "triangle",
"frequency": { "start": 600, "end": 900 },
"envelope": { "attack": 0.0, "decay": 0.0, "sustain": 0.0, "release": 0.035 },
"volume": 0.2,
"pan": 0.0
}
]
}