Skip to main content

acorde_core/model/
gm.rs

1/// Returns the General MIDI Level 1 program name for the given 0-based program number.
2/// Returns `"Unknown"` if `program >= 128`.
3pub fn program_name(program: u8) -> &'static str {
4    GM_NAMES.get(program as usize).copied().unwrap_or("Unknown")
5}
6
7static GM_NAMES: [&str; 128] = [
8    // Piano (0–7)
9    "Acoustic Grand Piano",
10    "Bright Acoustic Piano",
11    "Electric Grand Piano",
12    "Honky-tonk Piano",
13    "Electric Piano 1",
14    "Electric Piano 2",
15    "Harpsichord",
16    "Clavi",
17    // Chromatic Percussion (8–15)
18    "Celesta",
19    "Glockenspiel",
20    "Music Box",
21    "Vibraphone",
22    "Marimba",
23    "Xylophone",
24    "Tubular Bells",
25    "Dulcimer",
26    // Organ (16–23)
27    "Drawbar Organ",
28    "Percussive Organ",
29    "Rock Organ",
30    "Church Organ",
31    "Reed Organ",
32    "Accordion",
33    "Harmonica",
34    "Tango Accordion",
35    // Guitar (24–31)
36    "Acoustic Guitar (nylon)",
37    "Acoustic Guitar (steel)",
38    "Electric Guitar (jazz)",
39    "Electric Guitar (clean)",
40    "Electric Guitar (muted)",
41    "Overdriven Guitar",
42    "Distortion Guitar",
43    "Guitar harmonics",
44    // Bass (32–39)
45    "Acoustic Bass",
46    "Electric Bass (finger)",
47    "Electric Bass (pick)",
48    "Fretless Bass",
49    "Slap Bass 1",
50    "Slap Bass 2",
51    "Synth Bass 1",
52    "Synth Bass 2",
53    // Strings (40–47)
54    "Violin",
55    "Viola",
56    "Cello",
57    "Contrabass",
58    "Tremolo Strings",
59    "Pizzicato Strings",
60    "Orchestral Harp",
61    "Timpani",
62    // Ensemble (48–55)
63    "String Ensemble 1",
64    "String Ensemble 2",
65    "Synth Strings 1",
66    "Synth Strings 2",
67    "Choir Aahs",
68    "Voice Oohs",
69    "Synth Voice",
70    "Orchestra Hit",
71    // Brass (56–63)
72    "Trumpet",
73    "Trombone",
74    "Tuba",
75    "Muted Trumpet",
76    "French Horn",
77    "Brass Section",
78    "Synth Brass 1",
79    "Synth Brass 2",
80    // Reed (64–71)
81    "Soprano Sax",
82    "Alto Sax",
83    "Tenor Sax",
84    "Baritone Sax",
85    "Oboe",
86    "English Horn",
87    "Bassoon",
88    "Clarinet",
89    // Pipe (72–79)
90    "Piccolo",
91    "Flute",
92    "Recorder",
93    "Pan Flute",
94    "Blown Bottle",
95    "Shakuhachi",
96    "Whistle",
97    "Ocarina",
98    // Synth Lead (80–87)
99    "Lead 1 (square)",
100    "Lead 2 (sawtooth)",
101    "Lead 3 (calliope)",
102    "Lead 4 (chiff)",
103    "Lead 5 (charang)",
104    "Lead 6 (voice)",
105    "Lead 7 (fifths)",
106    "Lead 8 (bass + lead)",
107    // Synth Pad (88–95)
108    "Pad 1 (new age)",
109    "Pad 2 (warm)",
110    "Pad 3 (polysynth)",
111    "Pad 4 (choir)",
112    "Pad 5 (bowed)",
113    "Pad 6 (metallic)",
114    "Pad 7 (halo)",
115    "Pad 8 (sweep)",
116    // Synth Effects (96–103)
117    "FX 1 (rain)",
118    "FX 2 (soundtrack)",
119    "FX 3 (crystal)",
120    "FX 4 (atmosphere)",
121    "FX 5 (brightness)",
122    "FX 6 (goblins)",
123    "FX 7 (echoes)",
124    "FX 8 (sci-fi)",
125    // Ethnic (104–111)
126    "Sitar",
127    "Banjo",
128    "Shamisen",
129    "Koto",
130    "Kalimba",
131    "Bag pipe",
132    "Fiddle",
133    "Shanai",
134    // Percussive (112–119)
135    "Tinkle Bell",
136    "Agogo",
137    "Steel Drums",
138    "Woodblock",
139    "Taiko Drum",
140    "Melodic Tom",
141    "Synth Drum",
142    "Reverse Cymbal",
143    // Sound Effects (120–127)
144    "Guitar Fret Noise",
145    "Breath Noise",
146    "Seashore",
147    "Bird Tweet",
148    "Telephone Ring",
149    "Helicopter",
150    "Applause",
151    "Gunshot",
152];
153
154/// Returns the General MIDI percussion instrument name for the given MIDI note number.
155///
156/// Standard GM percussion map spans notes 35–81. Returns `"Unknown Drum"` outside that range.
157pub fn drum_name(note: u8) -> &'static str {
158    const LOW: u8 = 35;
159    const HIGH: u8 = 81;
160    if !(LOW..=HIGH).contains(&note) {
161        return "Unknown Drum";
162    }
163    GM_DRUM_NAMES[(note - LOW) as usize]
164}
165
166static GM_DRUM_NAMES: [&str; 47] = [
167    "Acoustic Bass Drum", // 35
168    "Bass Drum 1",        // 36
169    "Side Stick",         // 37
170    "Acoustic Snare",     // 38
171    "Hand Clap",          // 39
172    "Electric Snare",     // 40
173    "Low Floor Tom",      // 41
174    "Closed Hi-Hat",      // 42
175    "High Floor Tom",     // 43
176    "Pedal Hi-Hat",       // 44
177    "Low Tom",            // 45
178    "Open Hi-Hat",        // 46
179    "Low-Mid Tom",        // 47
180    "Hi-Mid Tom",         // 48
181    "Crash Cymbal 1",     // 49
182    "High Tom",           // 50
183    "Ride Cymbal 1",      // 51
184    "Chinese Cymbal",     // 52
185    "Ride Bell",          // 53
186    "Tambourine",         // 54
187    "Splash Cymbal",      // 55
188    "Cowbell",            // 56
189    "Crash Cymbal 2",     // 57
190    "Vibraslap",          // 58
191    "Ride Cymbal 2",      // 59
192    "Hi Bongo",           // 60
193    "Low Bongo",          // 61
194    "Mute Hi Conga",      // 62
195    "Open Hi Conga",      // 63
196    "Low Conga",          // 64
197    "High Timbale",       // 65
198    "Low Timbale",        // 66
199    "High Agogo",         // 67
200    "Low Agogo",          // 68
201    "Cabasa",             // 69
202    "Maracas",            // 70
203    "Short Whistle",      // 71
204    "Long Whistle",       // 72
205    "Short Guiro",        // 73
206    "Long Guiro",         // 74
207    "Claves",             // 75
208    "Hi Wood Block",      // 76
209    "Low Wood Block",     // 77
210    "Mute Cuica",         // 78
211    "Open Cuica",         // 79
212    "Mute Triangle",      // 80
213    "Open Triangle",      // 81
214];
215
216/// Returns the practical playing range `(min_midi, max_midi)` for a GM program number.
217///
218/// Based on standard orchestral/band instrument ranges. Piano (0) covers 21–108 (A0–C8).
219/// Returns `(0, 127)` for programs without a well-defined acoustic range (e.g. sound effects).
220pub fn instrument_range(midi_program: u8) -> (u8, u8) {
221    GM_RANGES
222        .get(midi_program as usize)
223        .copied()
224        .unwrap_or((0, 127))
225}
226
227// Practical playing ranges per GM program (min_midi, max_midi).
228// Programs 120–127 (sound effects) use (0, 127) — no meaningful pitch restriction.
229static GM_RANGES: [(u8, u8); 128] = [
230    // Piano (0–7): 21(A0)–108(C8) for grands; uprights similar
231    (21, 108),
232    (21, 108),
233    (21, 108),
234    (21, 108),
235    (21, 108),
236    (21, 108),
237    (21, 108),
238    (21, 108),
239    // Chromatic Percussion (8–15)
240    (60, 108), // Celesta: C4–C8
241    (52, 96),  // Glockenspiel: E3–C7
242    (48, 84),  // Music Box
243    (45, 89),  // Vibraphone: A2–F6
244    (36, 96),  // Marimba: C2–C7
245    (43, 96),  // Xylophone: G2–C7
246    (36, 77),  // Tubular Bells: C2–F5
247    (36, 84),  // Dulcimer
248    // Organ (16–23)
249    (24, 108),
250    (24, 108),
251    (24, 108),
252    (24, 108),
253    (24, 108),
254    (24, 108),
255    (24, 108),
256    (24, 108),
257    // Guitar (24–31)
258    (40, 88),
259    (40, 88),
260    (40, 88),
261    (40, 88),
262    (40, 88),
263    (40, 88),
264    (40, 88),
265    (40, 88),
266    // Bass (32–39): low instruments
267    (28, 67),
268    (28, 67),
269    (28, 67),
270    (28, 67),
271    (28, 67),
272    (28, 67),
273    (28, 67),
274    (28, 67),
275    // Strings (40–47)
276    (55, 103), // Violin: G3–B7
277    (48, 91),  // Viola: C3–G6
278    (36, 76),  // Cello: C2–E5
279    (28, 60),  // Contrabass: E1–C4
280    (55, 91),  // Tremolo Strings
281    (55, 91),  // Pizzicato Strings
282    (21, 108), // Orchestral Harp
283    (36, 84),  // Timpani: C2–C6
284    // Ensemble (48–55)
285    (21, 108),
286    (21, 108),
287    (21, 108),
288    (21, 108),
289    (21, 108),
290    (21, 108),
291    (21, 108),
292    (21, 108),
293    // Brass (56–63)
294    (52, 82), // Trumpet: E3–Bb5
295    (36, 67), // Trombone: Bb1–G4
296    (28, 67), // Tuba: Bb0–G4
297    (52, 82), // Muted Trumpet
298    (43, 79), // French Horn: G2–G5
299    (52, 82), // Brass Section
300    (52, 82), // Synth Brass 1
301    (52, 82), // Synth Brass 2
302    // Reed (64–71)
303    (56, 89), // Soprano Sax: Ab3–E6
304    (44, 80), // Alto Sax: Bb2–E5  (concert pitch)
305    (38, 75), // Tenor Sax: Ab1–Bb4
306    (32, 68), // Baritone Sax: Bb0–Eb4
307    (45, 84), // Oboe: A2–C6
308    (36, 77), // English Horn: B1–F5
309    (34, 77), // Bassoon: Bb1–Bb5
310    (52, 96), // Clarinet: E3–C7 (written, concert = -2)
311    // Pipe (72–79)
312    (60, 96), // Piccolo: C4–C7
313    (60, 96), // Flute: C4–C7
314    (55, 91), // Recorder
315    (55, 89), // Pan Flute
316    (48, 84), // Blown Bottle
317    (48, 84), // Shakuhachi
318    (48, 84), // Whistle
319    (48, 84), // Ocarina
320    // Synth Lead (80–87): wide range
321    (0, 127),
322    (0, 127),
323    (0, 127),
324    (0, 127),
325    (0, 127),
326    (0, 127),
327    (0, 127),
328    (0, 127),
329    // Synth Pad (88–95)
330    (0, 127),
331    (0, 127),
332    (0, 127),
333    (0, 127),
334    (0, 127),
335    (0, 127),
336    (0, 127),
337    (0, 127),
338    // Synth Effects (96–103)
339    (0, 127),
340    (0, 127),
341    (0, 127),
342    (0, 127),
343    (0, 127),
344    (0, 127),
345    (0, 127),
346    (0, 127),
347    // Ethnic (104–111)
348    (0, 127),
349    (0, 127),
350    (0, 127),
351    (0, 127),
352    (0, 127),
353    (0, 127),
354    (0, 127),
355    (0, 127),
356    // Percussive (112–119)
357    (0, 127),
358    (0, 127),
359    (0, 127),
360    (0, 127),
361    (0, 127),
362    (0, 127),
363    (0, 127),
364    (0, 127),
365    // Sound Effects (120–127)
366    (0, 127),
367    (0, 127),
368    (0, 127),
369    (0, 127),
370    (0, 127),
371    (0, 127),
372    (0, 127),
373    (0, 127),
374];
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn gm_program_name_piano() {
382        assert_eq!(program_name(0), "Acoustic Grand Piano");
383    }
384
385    #[test]
386    fn gm_program_name_violin() {
387        assert_eq!(program_name(40), "Violin");
388    }
389
390    #[test]
391    fn gm_program_name_last_entry() {
392        assert_eq!(program_name(127), "Gunshot");
393    }
394
395    #[test]
396    fn gm_program_name_out_of_range() {
397        assert_eq!(program_name(128), "Unknown");
398        assert_eq!(program_name(255), "Unknown");
399    }
400
401    #[test]
402    fn drum_name_boundary_low() {
403        assert_eq!(drum_name(35), "Acoustic Bass Drum");
404    }
405
406    #[test]
407    fn drum_name_snare() {
408        assert_eq!(drum_name(38), "Acoustic Snare");
409    }
410
411    #[test]
412    fn drum_name_boundary_high() {
413        assert_eq!(drum_name(81), "Open Triangle");
414    }
415
416    #[test]
417    fn drum_name_out_of_range() {
418        assert_eq!(drum_name(0), "Unknown Drum");
419        assert_eq!(drum_name(34), "Unknown Drum");
420        assert_eq!(drum_name(82), "Unknown Drum");
421        assert_eq!(drum_name(255), "Unknown Drum");
422    }
423
424    #[test]
425    fn instrument_range_piano() {
426        assert_eq!(instrument_range(0), (21, 108));
427    }
428
429    #[test]
430    fn instrument_range_flute() {
431        assert_eq!(instrument_range(73), (60, 96));
432    }
433
434    #[test]
435    fn instrument_range_out_of_gm() {
436        assert_eq!(instrument_range(255), (0, 127));
437    }
438}