Files
Soundgen/crates/soundgen-fmt/examples/play_melody.rs
T
Emil c7d6c40683 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
2026-06-21 22:07:05 +03:00

92 lines
2.9 KiB
Rust

//! Example: render a simple melody (pulse lead + triangle bass + noise hihat) to WAV.
use soundgen_core::{
voice::{DutyCycle, NoiseChannel, NoiseMode, PulseChannel, TriangleChannel},
ChannelRenderer, Envelope, VoiceKind,
};
use soundgen_io::write_wav;
fn note_freq(n: &str) -> f32 {
// Note name to frequency: e.g., "A4" = 440 Hz
let notes = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
let note = &n[..n.len() - 1];
let octave: i32 = n[n.len() - 1..].parse().unwrap();
let semitone = notes.iter().position(|&x| x == note).unwrap() as i32;
let midi = 12 * (octave + 1) + semitone;
440.0 * 2.0f32.powf((midi - 69) as f32 / 12.0)
}
fn main() {
let sr = 44100u32;
let bpm = 120.0;
let beat = 60.0 / bpm; // seconds per beat
let total_beats = 8.0;
let total_samples = (beat * total_beats * sr as f32) as usize;
// Melody: C4 E4 G4 C5 G4 E4 C4 G3
let melody = ["C4", "E4", "G4", "C5", "G4", "E4", "C4", "G3"];
// Bass: C3 C3 G3 G3 A2 A2 F2 F2
let bass = ["C3", "C3", "G3", "G3", "A2", "A2", "F2", "F2"];
let samples_per_note = (beat * sr as f32) as usize;
let mut stereo = Vec::with_capacity(total_samples * 2);
for i in 0..8 {
// Lead: pulse wave
let mut lead = ChannelRenderer::new(
VoiceKind::Pulse(PulseChannel::new(sr as f32, DutyCycle::D50)),
sr as f32,
)
.with_envelope(Envelope::adsr(sr as f32, 0.01, 0.05, 0.6, 0.08))
.with_volume(0.3)
.with_pan(-0.3);
lead.trigger();
lead.set_frequency(note_freq(melody[i]));
// Bass: triangle
let mut bass_ch = ChannelRenderer::new(
VoiceKind::Triangle(TriangleChannel::new(sr as f32)),
sr as f32,
)
.with_envelope(Envelope::adsr(sr as f32, 0.02, 0.1, 0.5, 0.1))
.with_volume(0.35)
.with_pan(0.3);
bass_ch.trigger();
bass_ch.set_frequency(note_freq(bass[i]));
// Hihat: noise on offbeats
let has_hihat = i % 2 == 1;
let mut hihat = ChannelRenderer::new(
VoiceKind::Noise(NoiseChannel::new(sr as f32, NoiseMode::White)),
sr as f32,
)
.with_envelope(Envelope::adsr(sr as f32, 0.0, 0.02, 0.0, 0.03))
.with_volume(0.15)
.with_pan(0.0);
if has_hihat {
hihat.trigger();
hihat.set_frequency(10000.0);
}
for _ in 0..samples_per_note {
let (ll, lr) = lead.tick();
let (bl, br) = bass_ch.tick();
let (hl, hr) = hihat.tick();
let l = (ll + bl + hl).tanh();
let r = (lr + br + hr).tanh();
stereo.push(l);
stereo.push(r);
}
}
let out = std::path::Path::new("play_melody.wav");
write_wav(out, &stereo, sr).expect("failed to write WAV");
eprintln!("Wrote {} ({} notes, {:.1}s)", out.display(), 8, beat * 8.0);
}