灵符lingfu
L

ling-daw

↓ 0 downloads

Ling DAW engine: synth, physical-modeling drums, effects, mixer, sequencer, project I/O

lingfu install ling-daw
LicenseApache-2.0 OR MIT
AuthorsLing Lin <taellinglin@gmail.com>, Sanny Lin <SannyLing53@gmail.com>
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (91)

src/dsp/mod.rs
//! lsx-dsp — instruments and effects for LingStationX.
pub mod drums;
pub mod guitar;
pub mod pluck;
pub mod scriptdsp;
pub mod vocoder;
pub mod fx;
pub mod vox;
pub mod hypergrowl;
pub mod growl;
pub mod prism;
pub mod sampler;
pub mod sf2;
pub mod synth;
pub mod voice;

pub const TAU: f32 = std::f32::consts::TAU;

/// Anything that renders stereo audio and takes MIDI-ish note events.
pub trait Instrument: Send {
    fn note_on(&mut self, key: u8, vel: f32);
    /// Note with per-note pan (-1..1) and cutoff/reso modifiers (0..1, 0.5 neutral).
    fn note_on_ex(&mut self, key: u8, vel: f32, _pan: f32, _cutoff: f32, _reso: f32) {
        self.note_on(key, vel);
    }
    /// Note with its lyric (`~` tie marker + IPA syllables joined by `-`, see lyrics.rs) and its length in samples.
    /// Only sung instruments use the lyric; everything else plays the note.
    fn note_on_lyric(&mut self, key: u8, vel: f32, pan: f32, cutoff: f32, reso: f32, _lyric: &str, _len_samples: f32) {
        self.note_on_ex(key, vel, pan, cutoff, reso);
    }
    fn note_off(&mut self, key: u8);
    fn all_off(&mut self);
    /// The project tempo, handed over before every render: instruments with a tempo-synced part (the Ling Voice arpeggiator) follow it.
    fn set_tempo(&mut self, _bpm: f64) {}
    /// Render and ADD into `out` (interleaved stereo).
    fn render(&mut self, out: &mut [f32]);
    /// Named parameters, 0..1 normalised.
    fn params(&self) -> Vec<(&'static str, f32)>;
    fn set_param(&mut self, name: &str, v: f32);
    fn name(&self) -> &'static str;
    /// Sub-instruments the params apply to (e.g. drum pieces). Empty = none.
    fn parts(&self) -> Vec<&'static str> {
        vec![]
    }
    fn part(&self) -> usize {
        0
    }
    fn select_part(&mut self, _i: usize) {}
    fn presets(&self) -> &'static [&'static str] {
        &[]
    }
    /// File the instrument was built from (soundfont path), empty for built-in instruments.
    fn source_path(&self) -> String {
        String::new()
    }
    /// Dynamic preset list (soundfont presets) and the selected index.
    fn preset_names(&self) -> Vec<String> {
        vec![]
    }
    fn preset_index(&self) -> usize {
        0
    }
    fn select_preset_index(&mut self, _i: usize) {}
    /// Choose a sound by MIDI bank / program; false when the instrument has no such sound.
    fn midi_select(&mut self, _bank: u16, _program: u16, _drums: bool) -> bool {
        false
    }
    fn load_preset(&mut self, _name: &str) {}
    /// The category of each built-in preset: (preset name, category), empty when the instrument keeps its presets in one list.
    fn preset_categories(&self) -> Vec<(String, String)> {
        vec![]
    }
    /// Instrument-specific edit commands (the Sampler's zones): `cmd` with numeric `args` and some `text`; the reply is a list of numbers.
    fn command(&mut self, _cmd: &str, _args: &[f64], _text: &str) -> Vec<f64> {
        vec![]
    }
    /// Instrument-specific text: "names", "files", "config", "error" for the Sampler.
    fn info(&self, _what: &str) -> String {
        String::new()
    }
    /// One parameter by name (0..1), `None` when the instrument has no such parameter.
    fn get_param(&self, name: &str) -> Option<f32> {
        self.params().into_iter().find(|(n, _)| *n == name).map(|(_, v)| v)
    }
    /// Curves for the panel: "wave" (one cycle of the oscillator), "filter" (response, 0..1 = -24..+24 dB). `n` points.
    fn plot(&self, _what: &str, _n: usize) -> Vec<f32> {
        vec![]
    }
    /// Full serialisable state (name, value); drums include every part.
    fn state(&self) -> Vec<(String, f32)> {
        self.params().into_iter().map(|(n, v)| (n.to_string(), v)).collect()
    }
    fn load_state(&mut self, st: &[(String, f32)]) {
        for (n, v) in st {
            self.set_param(n, *v);
        }
    }
    /// A new, random but musical sound (same seed, same sound); false when the instrument cannot do that.
    fn randomize(&mut self, _seed: u32) -> bool {
        false
    }
}

#[inline]
pub fn midi_hz(k: f32) -> f32 {
    440.0 * 2f32.powf((k - 69.0) / 12.0)
}

/// Small xorshift RNG (no allocation, realtime safe).
#[derive(Clone)]
pub struct Rng(u32);
impl Rng {
    pub fn new(seed: u32) -> Self {
        Rng(seed.max(1))
    }
    #[inline]
    pub fn next(&mut self) -> f32 {
        let mut x = self.0;
        x ^= x << 13;
        x ^= x >> 17;
        x ^= x << 5;
        self.0 = x;
        (x as f32 / u32::MAX as f32) * 2.0 - 1.0
    }
}

/// Instrument factory: "synth" (Ling Synth X) or "drums" (Ling Membrane).
/// Like `make_instrument`, for kinds that need a file ("sf2" = SoundFont).
pub fn make_instrument_file(kind: &str, sr: f32, path: &str) -> Option<Box<dyn Instrument>> {
    match kind {
        "sf2" => match sf2::Sf2Player::new(sr, path) {
            Ok(p) => Some(Box::new(p)),
            Err(e) => {
                eprintln!("soundfont: {e}");
                None
            }
        },
        other => make_instrument(other, sr),
    }
}

pub fn make_instrument(kind: &str, sr: f32) -> Option<Box<dyn Instrument>> {
    match kind {
        "synth" => Some(Box::new(synth::LingSynth::new(sr))),
        "drums" => Some(Box::new(drums::DrumSynth::new(sr))),
        "growl" => Some(Box::new(growl::Growl::new(sr))),
        "voice" => Some(Box::new(voice::Voice::new(sr))),
        "prism" => Some(Box::new(prism::Prism::new(sr))),
        "sampler" => Some(Box::new(sampler::Sampler::new(sr))),
        "pluck" => Some(Box::new(pluck::Pluck::new(sr))),
        "script" => Some(Box::new(scriptdsp::ScriptSynth::new(sr))),
        _ => None,
    }
}

← Back to index