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// Playing ranges per GM program (min_midi, max_midi), sounding pitch. Orchestral, band and guitar
228// entries follow professional ranges (extended-range guitars and basses, string quartet high
229// positions, altissimo) so authored repertoire does not fail validation; the table is only ever
230// widened, never narrowed.
231// Programs 120–127 (sound effects) use (0, 127) — no meaningful pitch restriction.
232static GM_RANGES: [(u8, u8); 128] = [
233    // Piano (0–7): 21(A0)–108(C8) for grands; uprights similar
234    (21, 108),
235    (21, 108),
236    (21, 108),
237    (21, 108),
238    (21, 108),
239    (21, 108),
240    (21, 108),
241    (21, 108),
242    // Chromatic Percussion (8–15)
243    (60, 108), // Celesta: C4–C8
244    (52, 96),  // Glockenspiel: E3–C7
245    (48, 84),  // Music Box
246    (45, 89),  // Vibraphone: A2–F6
247    (36, 96),  // Marimba: C2–C7
248    (43, 96),  // Xylophone: G2–C7
249    (36, 77),  // Tubular Bells: C2–F5
250    (36, 84),  // Dulcimer
251    // Organ (16–23)
252    (24, 108),
253    (24, 108),
254    (24, 108),
255    (24, 108),
256    (24, 108),
257    (24, 108),
258    (24, 108),
259    (24, 108),
260    // Guitar (24–31)
261    (30, 91),
262    (30, 91),
263    (30, 91),
264    (30, 91),
265    (30, 91),
266    (30, 91),
267    (30, 91),
268    (30, 91),
269    // Bass (32–39): low instruments
270    (23, 72),
271    (23, 72),
272    (23, 72),
273    (23, 72),
274    (23, 72),
275    (23, 72),
276    (23, 72),
277    (23, 72),
278    // Strings (40–47)
279    (55, 105), // Violin: G3–A7
280    (48, 93),  // Viola: C3–A6
281    (36, 93),  // Cello: C2–A6
282    (28, 67),  // Contrabass: E1–G4
283    (55, 91),  // Tremolo Strings
284    (55, 91),  // Pizzicato Strings
285    (21, 108), // Orchestral Harp
286    (36, 84),  // Timpani: C2–C6
287    // Ensemble (48–55)
288    (21, 108),
289    (21, 108),
290    (21, 108),
291    (21, 108),
292    (21, 108),
293    (21, 108),
294    (21, 108),
295    (21, 108),
296    // Brass (56–63)
297    (52, 86), // Trumpet: E3–D6
298    (34, 77), // Trombone: Bb1–F5
299    (26, 67), // Tuba: Bb0–G4
300    (52, 86), // Muted Trumpet
301    (34, 79), // French Horn: Bb1–G5
302    (52, 82), // Brass Section
303    (52, 82), // Synth Brass 1
304    (52, 82), // Synth Brass 2
305    // Reed (64–71)
306    (56, 91), // Soprano Sax: Ab3–E6
307    (44, 87), // Alto Sax: Bb2–E5  (concert pitch)
308    (38, 81), // Tenor Sax: Ab1–Bb4
309    (32, 72), // Baritone Sax: Bb0–Eb4
310    (45, 93), // Oboe: A2–C6
311    (36, 84), // English Horn: B1–F5
312    (34, 77), // Bassoon: Bb1–Bb5
313    (50, 96), // Clarinet: D3–C7
314    // Pipe (72–79)
315    (60, 108), // Piccolo: C4–C8
316    (59, 98),  // Flute: B3–D7
317    (55, 91),  // Recorder
318    (55, 89),  // Pan Flute
319    (48, 84),  // Blown Bottle
320    (48, 84),  // Shakuhachi
321    (48, 84),  // Whistle
322    (48, 84),  // Ocarina
323    // Synth Lead (80–87): wide range
324    (0, 127),
325    (0, 127),
326    (0, 127),
327    (0, 127),
328    (0, 127),
329    (0, 127),
330    (0, 127),
331    (0, 127),
332    // Synth Pad (88–95)
333    (0, 127),
334    (0, 127),
335    (0, 127),
336    (0, 127),
337    (0, 127),
338    (0, 127),
339    (0, 127),
340    (0, 127),
341    // Synth Effects (96–103)
342    (0, 127),
343    (0, 127),
344    (0, 127),
345    (0, 127),
346    (0, 127),
347    (0, 127),
348    (0, 127),
349    (0, 127),
350    // Ethnic (104–111)
351    (0, 127),
352    (0, 127),
353    (0, 127),
354    (0, 127),
355    (0, 127),
356    (0, 127),
357    (0, 127),
358    (0, 127),
359    // Percussive (112–119)
360    (0, 127),
361    (0, 127),
362    (0, 127),
363    (0, 127),
364    (0, 127),
365    (0, 127),
366    (0, 127),
367    (0, 127),
368    // Sound Effects (120–127)
369    (0, 127),
370    (0, 127),
371    (0, 127),
372    (0, 127),
373    (0, 127),
374    (0, 127),
375    (0, 127),
376    (0, 127),
377];
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn gm_program_name_piano() {
385        assert_eq!(program_name(0), "Acoustic Grand Piano");
386    }
387
388    #[test]
389    fn gm_program_name_violin() {
390        assert_eq!(program_name(40), "Violin");
391    }
392
393    #[test]
394    fn gm_program_name_last_entry() {
395        assert_eq!(program_name(127), "Gunshot");
396    }
397
398    #[test]
399    fn gm_program_name_out_of_range() {
400        assert_eq!(program_name(128), "Unknown");
401        assert_eq!(program_name(255), "Unknown");
402    }
403
404    #[test]
405    fn drum_name_boundary_low() {
406        assert_eq!(drum_name(35), "Acoustic Bass Drum");
407    }
408
409    #[test]
410    fn drum_name_snare() {
411        assert_eq!(drum_name(38), "Acoustic Snare");
412    }
413
414    #[test]
415    fn drum_name_boundary_high() {
416        assert_eq!(drum_name(81), "Open Triangle");
417    }
418
419    #[test]
420    fn drum_name_out_of_range() {
421        assert_eq!(drum_name(0), "Unknown Drum");
422        assert_eq!(drum_name(34), "Unknown Drum");
423        assert_eq!(drum_name(82), "Unknown Drum");
424        assert_eq!(drum_name(255), "Unknown Drum");
425    }
426
427    #[test]
428    fn instrument_range_piano() {
429        assert_eq!(instrument_range(0), (21, 108));
430    }
431
432    #[test]
433    fn instrument_range_flute() {
434        assert_eq!(instrument_range(73), (59, 98));
435    }
436
437    #[test]
438    fn instrument_range_out_of_gm() {
439        assert_eq!(instrument_range(255), (0, 127));
440    }
441}