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,
);
pub fn instrument_names() -> &'static [&'static str] {
INSTRUMENT_NAMES
}
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");
}
}