Skip to main content

midi_msg/
general_midi.rs

1use core::convert::TryFrom;
2
3#[cfg(feature = "std")]
4use strum::{Display, EnumIter, EnumString};
5
6/// Used to turn General MIDI level 1 or 2 on, or turn them off.
7///
8/// Used in [`UniversalNonRealTimeMsg::GeneralMidi`](crate::UniversalNonRealTimeMsg::GeneralMidi)
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum GeneralMidi {
12    GM1 = 1,
13    GM2 = 3,
14    Off = 2,
15}
16
17/// The instrument that should be played when applying a [`ChannelVoiceMsg::ProgramChange`](crate::ChannelVoiceMsg::ProgramChange).
18///
19/// Use `GMSoundSet::Sound as u8` to use as the program number. For example:
20///
21/// ```
22/// # use midi_msg::*;
23/// MidiMsg::ChannelVoice {
24///     channel: Channel::Ch1,
25///     msg: ChannelVoiceMsg::ProgramChange {
26///         program: GMSoundSet::Vibraphone as u8
27///     }
28/// };
29/// ```
30///
31/// Should not be used when targeting channel 10.
32///
33/// As defined in General MIDI System Level 1 (MMA0007 / RP003).
34#[cfg_attr(feature = "std", derive(EnumIter, Display, EnumString))]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[repr(u8)]
38pub enum GMSoundSet {
39    AcousticGrandPiano = 0,
40    BrightAcousticPiano = 1,
41    ElectricGrandPiano = 2,
42    HonkytonkPiano = 3,
43    ElectricPiano1 = 4,
44    ElectricPiano2 = 5,
45    Harpsichord = 6,
46    Clavi = 7,
47    Celesta = 8,
48    Glockenspiel = 9,
49    MusicBox = 10,
50    Vibraphone = 11,
51    Marimba = 12,
52    Xylophone = 13,
53    TubularBells = 14,
54    Dulcimer = 15,
55    DrawbarOrgan = 16,
56    PercussiveOrgan = 17,
57    RockOrgan = 18,
58    ChurchOrgan = 19,
59    ReedOrgan = 20,
60    Accordion = 21,
61    Harmonica = 22,
62    TangoAccordion = 23,
63    AcousticGuitarNylon = 24,
64    AcousticGuitarSteel = 25,
65    ElectricGuitarJazz = 26,
66    ElectricGuitarClean = 27,
67    ElectricGuitarMuted = 28,
68    OverdrivenGuitar = 29,
69    DistortionGuitar = 30,
70    GuitarHarmonics = 31,
71    AcousticBass = 32,
72    ElectricBassFinger = 33,
73    ElectricBassPick = 34,
74    FretlessBass = 35,
75    SlapBass1 = 36,
76    SlapBass2 = 37,
77    SynthBass1 = 38,
78    SynthBass2 = 39,
79    Violin = 40,
80    Viola = 41,
81    Cello = 42,
82    Contrabass = 43,
83    TremoloStrings = 44,
84    PizzicatoStrings = 45,
85    OrchestralHarp = 46,
86    Timpani = 47,
87    StringEnsemble1 = 48,
88    StringEnsemble2 = 49,
89    SynthStrings1 = 50,
90    SynthStrings2 = 51,
91    ChoirAahs = 52,
92    VoiceOohs = 53,
93    SynthVoice = 54,
94    OrchestraHit = 55,
95    Trumpet = 56,
96    Trombone = 57,
97    Tuba = 58,
98    MutedTrumpet = 59,
99    FrenchHorn = 60,
100    BrassSection = 61,
101    SynthBrass1 = 62,
102    SynthBrass2 = 63,
103    SopranoSax = 64,
104    AltoSax = 65,
105    TenorSax = 66,
106    BaritoneSax = 67,
107    Oboe = 68,
108    EnglishHorn = 69,
109    Bassoon = 70,
110    Clarinet = 71,
111    Piccolo = 72,
112    Flute = 73,
113    Recorder = 74,
114    PanFlute = 75,
115    BlownBottle = 76,
116    Shakuhachi = 77,
117    Whistle = 78,
118    Ocarina = 79,
119    Lead1 = 80,
120    Lead2 = 81,
121    Lead3 = 82,
122    Lead4 = 83,
123    Lead5 = 84,
124    Lead6 = 85,
125    Lead7 = 86,
126    Lead8 = 87,
127    Pad1 = 88,
128    Pad2 = 89,
129    Pad3 = 90,
130    Pad4 = 91,
131    Pad5 = 92,
132    Pad6 = 93,
133    Pad7 = 94,
134    Pad8 = 95,
135    FX1 = 96,
136    FX2 = 97,
137    FX3 = 98,
138    FX4 = 99,
139    FX5 = 100,
140    FX6 = 101,
141    FX7 = 102,
142    FX8 = 103,
143    Sitar = 104,
144    Banjo = 105,
145    Shamisen = 106,
146    Koto = 107,
147    Kalimba = 108,
148    Bagpipe = 109,
149    Fiddle = 110,
150    Shanai = 111,
151    TinkleBell = 112,
152    Agogo = 113,
153    SteelDrums = 114,
154    Woodblock = 115,
155    TaikoDrum = 116,
156    MelodicTom = 117,
157    SynthDrum = 118,
158    ReverseCymbal = 119,
159    GuitarFretNoise = 120,
160    BreathNoise = 121,
161    Seashore = 122,
162    BirdTweet = 123,
163    TelephoneRing = 124,
164    Helicopter = 125,
165    Applause = 126,
166    Gunshot = 127,
167}
168
169impl TryFrom<u8> for GMSoundSet {
170    type Error = &'static str;
171
172    fn try_from(value: u8) -> Result<Self, Self::Error> {
173        if value > 127 {
174            return Err("Invalid value for GMSoundSet");
175        }
176        Ok(unsafe { core::mem::transmute::<u8, GMSoundSet>(value) })
177    }
178}
179
180/// The General MIDI percussion sound to play for a given note number when targeting
181/// Channel 10.
182///
183/// For example:
184///
185/// ```
186/// # use midi_msg::*;
187/// MidiMsg::ChannelVoice {
188///     channel: Channel::Ch10,
189///     msg: ChannelVoiceMsg::NoteOn {
190///         note: GMPercussionMap::Vibraslap as u8,
191///         velocity: 127
192///     }
193/// };
194/// ```
195///
196/// As defined in General MIDI System Level 1 (MMA0007 / RP003).
197#[cfg_attr(feature = "std", derive(EnumIter, Display, EnumString))]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200#[repr(u8)]
201pub enum GMPercussionMap {
202    AcousticBassDrum = 35,
203    BassDrum1 = 36,
204    SideStick = 37,
205    AcousticSnare = 38,
206    HandClap = 39,
207    ElectricSnare = 40,
208    LowFloorTom = 41,
209    ClosedHiHat = 42,
210    HighFloorTom = 43,
211    PedalHiHat = 44,
212    LowTom = 45,
213    OpenHiHat = 46,
214    LowMidTom = 47,
215    HiMidTom = 48,
216    CrashCymbal1 = 49,
217    HighTom = 50,
218    RideCymbal1 = 51,
219    ChineseCymbal = 52,
220    RideBell = 53,
221    Tambourine = 54,
222    SplashCymbal = 55,
223    Cowbell = 56,
224    CrashCymbal2 = 57,
225    Vibraslap = 58,
226    RideCymbal2 = 59,
227    HiBongo = 60,
228    LowBongo = 61,
229    MuteHiConga = 62,
230    OpenHiConga = 63,
231    LowConga = 64,
232    HighTimbale = 65,
233    LowTimbale = 66,
234    HighAgogo = 67,
235    LowAgogo = 68,
236    Cabasa = 69,
237    Maracas = 70,
238    ShortWhistle = 71,
239    LongWhistle = 72,
240    ShortGuiro = 73,
241    LongGuiro = 74,
242    Claves = 75,
243    HiWoodBlock = 76,
244    LowWoodBlock = 77,
245    MuteCuica = 78,
246    OpenCuica = 79,
247    MuteTriangle = 80,
248    OpenTriangle = 81,
249}
250
251impl TryFrom<u8> for GMPercussionMap {
252    type Error = &'static str;
253
254    fn try_from(value: u8) -> Result<Self, Self::Error> {
255        if !(35..=81).contains(&value) {
256            return Err("Invalid value for GMPercussionMap");
257        }
258        Ok(unsafe { core::mem::transmute::<u8, GMPercussionMap>(value) })
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[cfg(feature = "std")]
267    use std::str::FromStr;
268    #[cfg(feature = "std")]
269    use strum::IntoEnumIterator;
270
271    #[cfg(feature = "std")]
272    #[test]
273    fn gm_iter() {
274        for (i, inst) in GMSoundSet::iter().enumerate() {
275            //println!("{:?} {}",inst, inst as u8);
276            assert_eq!(inst as u8, i as u8);
277        }
278    }
279
280    #[cfg(feature = "std")]
281    #[test]
282    fn gm_from_string() {
283        assert_eq!(
284            GMSoundSet::TenorSax,
285            GMSoundSet::from_str("TenorSax").unwrap()
286        );
287    }
288
289    #[cfg(feature = "std")]
290    #[test]
291    fn gm_display() {
292        assert_eq!("TenorSax", format!("{}", GMSoundSet::TenorSax));
293    }
294
295    #[cfg(feature = "std")]
296    #[test]
297    fn gm_tostring() {
298        assert_eq!("TenorSax", GMSoundSet::TenorSax.to_string());
299    }
300
301    #[test]
302    fn gm_as_u8() {
303        assert_eq!(0, GMSoundSet::AcousticGrandPiano as u8);
304
305        assert_eq!(127, GMSoundSet::Gunshot as u8);
306    }
307
308    #[test]
309    fn gm_from_u8() {
310        assert_eq!(
311            GMSoundSet::AcousticGrandPiano,
312            GMSoundSet::try_from(0).unwrap()
313        );
314        assert_eq!(GMSoundSet::Gunshot, GMSoundSet::try_from(127).unwrap());
315    }
316
317    #[test]
318    fn gm_from_u8_invalid() {
319        assert!(GMSoundSet::try_from(128).is_err());
320    }
321
322    #[test]
323    fn gm_percussion_as_u8() {
324        assert_eq!(35, GMPercussionMap::AcousticBassDrum as u8);
325        assert_eq!(81, GMPercussionMap::OpenTriangle as u8);
326    }
327
328    #[test]
329    fn gm_percussion_from_u8() {
330        assert_eq!(
331            GMPercussionMap::AcousticBassDrum,
332            GMPercussionMap::try_from(35).unwrap()
333        );
334        assert_eq!(
335            GMPercussionMap::OpenTriangle,
336            GMPercussionMap::try_from(81).unwrap()
337        );
338    }
339
340    #[test]
341    fn gm_percussion_from_u8_invalid() {
342        assert!(GMPercussionMap::try_from(34).is_err());
343        assert!(GMPercussionMap::try_from(82).is_err());
344    }
345
346    #[cfg(feature = "std")]
347    #[test]
348    fn percussion_iter() {
349        for (i, perc) in GMPercussionMap::iter().enumerate() {
350            //println!("{:?} {}",inst, inst as u8);
351            assert_eq!(perc as u8, (i + 35) as u8);
352        }
353    }
354}