rust-synth 0.23.0

Terminal modular ambient synthesizer — FunDSP + Ratatui. Long cinematic pads, Euclidean drum sequencer, per-track LFO, Valhalla-Supermassive-style reverb, genetic evolution coupled to Conway's Game of Life, TOML presets, FLAC recording.
Documentation
//! Offline WAV render — deterministic, no audio device.

use anyhow::{Context, Result};
use fundsp::hacker::*;
use hound::{SampleFormat, WavSpec, WavWriter};
use rust_synth::audio::preset::{master_bus, GlobalParams, Preset, PresetKind};
use rust_synth::audio::track::TrackParams;
use std::path::PathBuf;

const SAMPLE_RATE: u32 = 48_000;

fn main() -> Result<()> {
    let args = parse_args();

    let mut graph = build_demo_graph();
    graph.set_sample_rate(SAMPLE_RATE as f64);

    let spec = WavSpec {
        channels: 2,
        sample_rate: SAMPLE_RATE,
        bits_per_sample: 32,
        sample_format: SampleFormat::Float,
    };

    std::fs::create_dir_all(args.out.parent().unwrap_or(&PathBuf::from(".")))
        .context("create out dir")?;
    let mut writer = WavWriter::create(&args.out, spec).context("open wav writer")?;

    let total_samples = (args.duration * SAMPLE_RATE as f32) as u64;
    for _ in 0..total_samples {
        let (l, r) = graph.get_stereo();
        writer.write_sample(l)?;
        writer.write_sample(r)?;
    }
    writer.finalize()?;
    eprintln!("rendered {:.1}s → {}", args.duration, args.out.display());
    Ok(())
}

fn build_demo_graph() -> Net {
    let g = GlobalParams::default();

    let pad = TrackParams::default_for(55.0);
    let bass = TrackParams::default_for(55.0);
    bass.gain.set_value(0.55);
    bass.cutoff.set_value(600.0);
    let heart = TrackParams::default_for(55.0);
    heart.gain.set_value(0.7);
    let drone = TrackParams::default_for(34.0);
    drone.gain.set_value(0.32);
    drone.reverb_mix.set_value(0.7);

    let a = Preset::build(PresetKind::PadZimmer, &pad, &g);
    let b = Preset::build(PresetKind::BassPulse, &bass, &g);
    let c = Preset::build(PresetKind::Heartbeat, &heart, &g);
    let d = Preset::build(PresetKind::DroneSub, &drone, &g);
    ((a + b + c + d) * 0.5) >> master_bus(g.brightness.clone())
}

struct Args {
    duration: f32,
    out: PathBuf,
}

fn parse_args() -> Args {
    let mut duration = 10.0_f32;
    let mut out = PathBuf::from("out/render.wav");
    let mut args = std::env::args().skip(1);
    while let Some(a) = args.next() {
        match a.as_str() {
            "--duration" => {
                duration = args
                    .next()
                    .and_then(|s| s.parse().ok())
                    .unwrap_or(duration);
            }
            "--out" => {
                if let Some(v) = args.next() {
                    out = PathBuf::from(v);
                }
            }
            _ => {}
        }
    }
    Args { duration, out }
}