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.get(midi_program as usize).copied().unwrap_or((0, 127))
222}
223
224// Practical playing ranges per GM program (min_midi, max_midi).
225// Programs 120–127 (sound effects) use (0, 127) — no meaningful pitch restriction.
226static GM_RANGES: [(u8, u8); 128] = [
227    // Piano (0–7): 21(A0)–108(C8) for grands; uprights similar
228    (21, 108), (21, 108), (21, 108), (21, 108),
229    (21, 108), (21, 108), (21, 108), (21, 108),
230    // Chromatic Percussion (8–15)
231    (60, 108), // Celesta: C4–C8
232    (52, 96),  // Glockenspiel: E3–C7
233    (48, 84),  // Music Box
234    (45, 89),  // Vibraphone: A2–F6
235    (36, 96),  // Marimba: C2–C7
236    (43, 96),  // Xylophone: G2–C7
237    (36, 77),  // Tubular Bells: C2–F5
238    (36, 84),  // Dulcimer
239    // Organ (16–23)
240    (24, 108), (24, 108), (24, 108), (24, 108),
241    (24, 108), (24, 108), (24, 108), (24, 108),
242    // Guitar (24–31)
243    (40, 88), (40, 88), (40, 88), (40, 88),
244    (40, 88), (40, 88), (40, 88), (40, 88),
245    // Bass (32–39): low instruments
246    (28, 67), (28, 67), (28, 67), (28, 67),
247    (28, 67), (28, 67), (28, 67), (28, 67),
248    // Strings (40–47)
249    (55, 103), // Violin: G3–B7
250    (48, 91),  // Viola: C3–G6
251    (36, 76),  // Cello: C2–E5
252    (28, 60),  // Contrabass: E1–C4
253    (55, 91),  // Tremolo Strings
254    (55, 91),  // Pizzicato Strings
255    (21, 108), // Orchestral Harp
256    (36, 84),  // Timpani: C2–C6
257    // Ensemble (48–55)
258    (21, 108), (21, 108), (21, 108), (21, 108),
259    (21, 108), (21, 108), (21, 108), (21, 108),
260    // Brass (56–63)
261    (52, 82),  // Trumpet: E3–Bb5
262    (36, 67),  // Trombone: Bb1–G4
263    (28, 67),  // Tuba: Bb0–G4
264    (52, 82),  // Muted Trumpet
265    (43, 79),  // French Horn: G2–G5
266    (52, 82),  // Brass Section
267    (52, 82),  // Synth Brass 1
268    (52, 82),  // Synth Brass 2
269    // Reed (64–71)
270    (56, 89),  // Soprano Sax: Ab3–E6
271    (44, 80),  // Alto Sax: Bb2–E5  (concert pitch)
272    (38, 75),  // Tenor Sax: Ab1–Bb4
273    (32, 68),  // Baritone Sax: Bb0–Eb4
274    (45, 84),  // Oboe: A2–C6
275    (36, 77),  // English Horn: B1–F5
276    (34, 77),  // Bassoon: Bb1–Bb5
277    (52, 96),  // Clarinet: E3–C7 (written, concert = -2)
278    // Pipe (72–79)
279    (60, 96),  // Piccolo: C4–C7
280    (60, 96),  // Flute: C4–C7
281    (55, 91),  // Recorder
282    (55, 89),  // Pan Flute
283    (48, 84),  // Blown Bottle
284    (48, 84),  // Shakuhachi
285    (48, 84),  // Whistle
286    (48, 84),  // Ocarina
287    // Synth Lead (80–87): wide range
288    (0, 127), (0, 127), (0, 127), (0, 127),
289    (0, 127), (0, 127), (0, 127), (0, 127),
290    // Synth Pad (88–95)
291    (0, 127), (0, 127), (0, 127), (0, 127),
292    (0, 127), (0, 127), (0, 127), (0, 127),
293    // Synth Effects (96–103)
294    (0, 127), (0, 127), (0, 127), (0, 127),
295    (0, 127), (0, 127), (0, 127), (0, 127),
296    // Ethnic (104–111)
297    (0, 127), (0, 127), (0, 127), (0, 127),
298    (0, 127), (0, 127), (0, 127), (0, 127),
299    // Percussive (112–119)
300    (0, 127), (0, 127), (0, 127), (0, 127),
301    (0, 127), (0, 127), (0, 127), (0, 127),
302    // Sound Effects (120–127)
303    (0, 127), (0, 127), (0, 127), (0, 127),
304    (0, 127), (0, 127), (0, 127), (0, 127),
305];
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn gm_program_name_piano() {
313        assert_eq!(program_name(0), "Acoustic Grand Piano");
314    }
315
316    #[test]
317    fn gm_program_name_violin() {
318        assert_eq!(program_name(40), "Violin");
319    }
320
321    #[test]
322    fn gm_program_name_last_entry() {
323        assert_eq!(program_name(127), "Gunshot");
324    }
325
326    #[test]
327    fn gm_program_name_out_of_range() {
328        assert_eq!(program_name(128), "Unknown");
329        assert_eq!(program_name(255), "Unknown");
330    }
331
332    #[test]
333    fn drum_name_boundary_low() {
334        assert_eq!(drum_name(35), "Acoustic Bass Drum");
335    }
336
337    #[test]
338    fn drum_name_snare() {
339        assert_eq!(drum_name(38), "Acoustic Snare");
340    }
341
342    #[test]
343    fn drum_name_boundary_high() {
344        assert_eq!(drum_name(81), "Open Triangle");
345    }
346
347    #[test]
348    fn drum_name_out_of_range() {
349        assert_eq!(drum_name(0), "Unknown Drum");
350        assert_eq!(drum_name(34), "Unknown Drum");
351        assert_eq!(drum_name(82), "Unknown Drum");
352        assert_eq!(drum_name(255), "Unknown Drum");
353    }
354
355    #[test]
356    fn instrument_range_piano() {
357        assert_eq!(instrument_range(0), (21, 108));
358    }
359
360    #[test]
361    fn instrument_range_flute() {
362        assert_eq!(instrument_range(73), (60, 96));
363    }
364
365    #[test]
366    fn instrument_range_out_of_gm() {
367        assert_eq!(instrument_range(255), (0, 127));
368    }
369}