1use crate::error::{PulseError, PulseResult};
2use tunes::instruments::Instrument;
3
4type InstrumentConstructor = fn() -> Instrument;
5
6#[derive(Clone, Copy)]
7struct InstrumentPreset {
8 name: &'static str,
9 constructor: InstrumentConstructor,
10}
11
12macro_rules! instrument_presets {
13 ($($name:ident),+ $(,)?) => {
14 const INSTRUMENT_PRESETS: &[InstrumentPreset] = &[
15 $(InstrumentPreset {
16 name: stringify!($name),
17 constructor: Instrument::$name,
18 },)+
19 ];
20
21 const INSTRUMENT_NAMES: &[&str] = &[
22 $(stringify!($name),)+
23 ];
24 };
25}
26
27instrument_presets!(
28 accordion,
29 acid_bass,
30 acid_synth,
31 acoustic_guitar,
32 acoustic_piano,
33 alto_sax,
34 ambient_pad,
35 analog_brass,
36 arp_lead,
37 bagpipes,
38 balalaika,
39 banjo,
40 baritone_sax,
41 bass_808,
42 bass_percussion,
43 bassoon,
44 bells,
45 bitcrush_noise,
46 brass,
47 brass_section,
48 bright_lead,
49 celesta,
50 cello,
51 charango,
52 chiptune,
53 choir_aahs,
54 choir_oohs,
55 chorus_bass,
56 church_organ,
57 circuit_bent,
58 clarinet,
59 clavinet,
60 cosmic_rays,
61 cowbell,
62 cp70,
63 dark_bass,
64 dark_pad,
65 data_stream,
66 deep_bass,
67 detuned_lead,
68 didgeridoo,
69 djembe,
70 double_bass,
71 drone_machine,
72 duduk,
73 dulcimer,
74 dx7_electric_piano,
75 electric_guitar_clean,
76 electric_guitar_distorted,
77 electric_piano,
78 english_horn,
79 erhu,
80 euphonium,
81 fingerstyle_bass,
82 flugelhorn,
83 flute,
84 fm_bass,
85 fm_bells,
86 formant_synth,
87 french_horn,
88 fretless_bass,
89 funk_bass,
90 gaita,
91 glass_harmonica,
92 glitch,
93 glockenspiel,
94 granular_pad,
95 growl_bass,
96 guitar_12_string,
97 guitar_harmonics,
98 guitar_palm_muted,
99 guzheng,
100 hammond_organ,
101 harp,
102 harpsichord,
103 honky_tonk_piano,
104 hoover,
105 impact,
106 juno_pad,
107 kalimba,
108 koto,
109 laser,
110 laser_lead,
111 mallet,
112 marimba,
113 mark_i_rhodes,
114 mbira,
115 metallic_perc,
116 minimoog_bass,
117 music_box,
118 muted_trombone,
119 muted_trumpet,
120 oboe,
121 obx_strings,
122 organ,
123 oud,
124 pan_flute,
125 pianet,
126 piccolo,
127 picked_bass,
128 pizzicato_strings,
129 pluck,
130 pluck_bass,
131 prophet_brass,
132 pwm_bass,
133 reed_organ,
134 reese_bass,
135 ring_mod_bells,
136 riser,
137 saw_lead,
138 scream_lead,
139 sh101_lead,
140 shakuhachi,
141 shamisen,
142 shimmer_pad,
143 sitar,
144 slap_bass,
145 slow_strings,
146 solo_trumpet,
147 soprano_sax,
148 square_lead,
149 stab,
150 stage_73,
151 steel_drums,
152 string_pad,
153 strings,
154 sub_bass,
155 supersaw,
156 synth_bass,
157 synth_lead,
158 synth_voice,
159 tabla,
160 taiko,
161 taiko_drum,
162 tenor_sax,
163 theremin,
164 timpani,
165 toy_piano,
166 trance_synth,
167 tremolo_strings,
168 trombone,
169 tuba,
170 tubular_bells,
171 uilleann_pipes,
172 ukulele,
173 upright_bass,
174 vibraphone,
175 viola,
176 violin,
177 vocal_pad,
178 vocoder,
179 warm_pad,
180 wind_chimes,
181 wobble_bass,
182 wurlitzer,
183 xylophone,
184);
185
186pub fn instrument_names() -> &'static [&'static str] {
188 INSTRUMENT_NAMES
189}
190
191pub fn instrument_by_name(name: &str) -> PulseResult<Instrument> {
193 let normalized = normalize_name(name);
194 INSTRUMENT_PRESETS
195 .iter()
196 .find(|preset| normalize_name(preset.name) == normalized)
197 .map(|preset| (preset.constructor)())
198 .ok_or_else(|| PulseError::InvalidInstrument {
199 value: name.to_string(),
200 })
201}
202
203fn normalize_name(value: &str) -> String {
204 value
205 .trim()
206 .to_ascii_lowercase()
207 .replace(['_', '-', ' '], "")
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn exposes_all_tunes_instrument_presets() {
216 let names = instrument_names();
217 assert!(names.len() >= 150);
218 assert!(names.contains(&"electric_piano"));
219 assert!(names.contains(&"supersaw"));
220 assert!(names.contains(&"bass_808"));
221
222 for name in names {
223 let instrument = instrument_by_name(name).expect("catalog entry should resolve");
224 assert!(!instrument.name.trim().is_empty());
225 }
226 }
227
228 #[test]
229 fn still_accepts_existing_normalized_aliases() {
230 assert!(instrument_by_name("electric piano").is_ok());
231 assert!(instrument_by_name("Stage-73").is_ok());
232 assert!(instrument_by_name("synth_lead").is_ok());
233 }
234
235 #[test]
236 fn rejects_unknown_instrument_alias() {
237 let error = instrument_by_name("laser_harp").expect_err("unknown alias should fail");
238 assert_eq!(error.to_string(), "invalid instrument: laser_harp");
239 }
240}