mlua-pulse 0.1.0

Lua-friendly music composition and audio export bindings built on tunes and mlua
Documentation
use crate::error::{PulseError, PulseResult};
use tunes::instruments::Instrument;

type InstrumentConstructor = fn() -> Instrument;

#[derive(Clone, Copy)]
struct InstrumentPreset {
    name: &'static str,
    constructor: InstrumentConstructor,
}

macro_rules! instrument_presets {
    ($($name:ident),+ $(,)?) => {
        const INSTRUMENT_PRESETS: &[InstrumentPreset] = &[
            $(InstrumentPreset {
                name: stringify!($name),
                constructor: Instrument::$name,
            },)+
        ];

        const INSTRUMENT_NAMES: &[&str] = &[
            $(stringify!($name),)+
        ];
    };
}

instrument_presets!(
    accordion,
    acid_bass,
    acid_synth,
    acoustic_guitar,
    acoustic_piano,
    alto_sax,
    ambient_pad,
    analog_brass,
    arp_lead,
    bagpipes,
    balalaika,
    banjo,
    baritone_sax,
    bass_808,
    bass_percussion,
    bassoon,
    bells,
    bitcrush_noise,
    brass,
    brass_section,
    bright_lead,
    celesta,
    cello,
    charango,
    chiptune,
    choir_aahs,
    choir_oohs,
    chorus_bass,
    church_organ,
    circuit_bent,
    clarinet,
    clavinet,
    cosmic_rays,
    cowbell,
    cp70,
    dark_bass,
    dark_pad,
    data_stream,
    deep_bass,
    detuned_lead,
    didgeridoo,
    djembe,
    double_bass,
    drone_machine,
    duduk,
    dulcimer,
    dx7_electric_piano,
    electric_guitar_clean,
    electric_guitar_distorted,
    electric_piano,
    english_horn,
    erhu,
    euphonium,
    fingerstyle_bass,
    flugelhorn,
    flute,
    fm_bass,
    fm_bells,
    formant_synth,
    french_horn,
    fretless_bass,
    funk_bass,
    gaita,
    glass_harmonica,
    glitch,
    glockenspiel,
    granular_pad,
    growl_bass,
    guitar_12_string,
    guitar_harmonics,
    guitar_palm_muted,
    guzheng,
    hammond_organ,
    harp,
    harpsichord,
    honky_tonk_piano,
    hoover,
    impact,
    juno_pad,
    kalimba,
    koto,
    laser,
    laser_lead,
    mallet,
    marimba,
    mark_i_rhodes,
    mbira,
    metallic_perc,
    minimoog_bass,
    music_box,
    muted_trombone,
    muted_trumpet,
    oboe,
    obx_strings,
    organ,
    oud,
    pan_flute,
    pianet,
    piccolo,
    picked_bass,
    pizzicato_strings,
    pluck,
    pluck_bass,
    prophet_brass,
    pwm_bass,
    reed_organ,
    reese_bass,
    ring_mod_bells,
    riser,
    saw_lead,
    scream_lead,
    sh101_lead,
    shakuhachi,
    shamisen,
    shimmer_pad,
    sitar,
    slap_bass,
    slow_strings,
    solo_trumpet,
    soprano_sax,
    square_lead,
    stab,
    stage_73,
    steel_drums,
    string_pad,
    strings,
    sub_bass,
    supersaw,
    synth_bass,
    synth_lead,
    synth_voice,
    tabla,
    taiko,
    taiko_drum,
    tenor_sax,
    theremin,
    timpani,
    toy_piano,
    trance_synth,
    tremolo_strings,
    trombone,
    tuba,
    tubular_bells,
    uilleann_pipes,
    ukulele,
    upright_bass,
    vibraphone,
    viola,
    violin,
    vocal_pad,
    vocoder,
    warm_pad,
    wind_chimes,
    wobble_bass,
    wurlitzer,
    xylophone,
);

/// Returns the stable Lua-facing instrument aliases.
pub fn instrument_names() -> &'static [&'static str] {
    INSTRUMENT_NAMES
}

/// Resolves stable Lua-facing aliases to `tunes` instrument presets.
pub fn instrument_by_name(name: &str) -> PulseResult<Instrument> {
    let normalized = normalize_name(name);
    INSTRUMENT_PRESETS
        .iter()
        .find(|preset| normalize_name(preset.name) == normalized)
        .map(|preset| (preset.constructor)())
        .ok_or_else(|| PulseError::InvalidInstrument {
            value: name.to_string(),
        })
}

fn normalize_name(value: &str) -> String {
    value
        .trim()
        .to_ascii_lowercase()
        .replace(['_', '-', ' '], "")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn exposes_all_tunes_instrument_presets() {
        let names = instrument_names();
        assert!(names.len() >= 150);
        assert!(names.contains(&"electric_piano"));
        assert!(names.contains(&"supersaw"));
        assert!(names.contains(&"bass_808"));

        for name in names {
            let instrument = instrument_by_name(name).expect("catalog entry should resolve");
            assert!(!instrument.name.trim().is_empty());
        }
    }

    #[test]
    fn still_accepts_existing_normalized_aliases() {
        assert!(instrument_by_name("electric piano").is_ok());
        assert!(instrument_by_name("Stage-73").is_ok());
        assert!(instrument_by_name("synth_lead").is_ok());
    }

    #[test]
    fn rejects_unknown_instrument_alias() {
        let error = instrument_by_name("laser_harp").expect_err("unknown alias should fail");
        assert_eq!(error.to_string(), "invalid instrument: laser_harp");
    }
}