Skip to main content

midi_msg/system_exclusive/
global_parameter.rs

1use crate::parse_error::*;
2use crate::util::*;
3use alloc::vec;
4use alloc::vec::Vec;
5#[allow(unused_imports)]
6use micromath::F32Ext;
7
8/// Global Parameter Control, to control parameters on a device that affect all sound.
9/// E.g. a global reverb.
10/// Used by [`UniversalRealTimeMsg::GlobalParameterControl`](crate::UniversalRealTimeMsg::GlobalParameterControl).
11///
12/// As defined in CA-024.
13///
14/// This C/A is much more permissive than most, and thus has a pretty awkward interface.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct GlobalParameterControl {
17    /// Between 0 and 127 `SlotPath`s, with each successive path representing a child
18    /// of the preceding value. No paths refers to the "top level"
19    /// (except if the first value refers to the top level ¯\_(ツ)_/¯)
20    pub slot_paths: Vec<SlotPath>,
21    /// The number of bytes present in the `id`s of `params`, must be greater than 0
22    /// Must line up with the values provided in `params` or output will be massaged
23    pub param_id_width: u8,
24    /// The number of bytes present in the `value`s of `params, must be greater than 0
25    /// Must line up with the values provided in `params` or output will be massaged
26    pub value_width: u8,
27    /// _Any number_ of `GlobalParameter`s
28    pub params: Vec<GlobalParameter>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32/// The type of reverb, used by [`GlobalParameterControl::reverb`].
33pub enum ReverbType {
34    SmallRoom = 0,
35    MediumRoom = 1,
36    LargeRoom = 2,
37    MediumHall = 3,
38    LargeHall = 4,
39    Plate = 8,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43/// The type of chorus, used by [`GlobalParameterControl::chorus`].
44pub enum ChorusType {
45    Chorus1 = 0,
46    Chorus2 = 1,
47    Chorus3 = 2,
48    Chorus4 = 3,
49    FBChorus = 4,
50    Flanger = 5,
51}
52
53impl GlobalParameterControl {
54    /// Constructor for a `GlobalParameterControl` directed at a GM2 Reverb slot type.
55    ///
56    /// `reverb_time` is the time in seconds (0.36 - 9.0) for which the low frequency
57    /// portion of the original sound declines by 60dB
58    pub fn reverb(reverb_type: Option<ReverbType>, reverb_time: Option<f32>) -> Self {
59        let mut params = vec![];
60
61        if let Some(reverb_type) = reverb_type {
62            params.push(GlobalParameter {
63                id: vec![0],
64                value: vec![reverb_type as u8],
65            });
66        }
67        if let Some(reverb_time) = reverb_time {
68            params.push(GlobalParameter {
69                id: vec![1],
70                value: vec![to_u7((F32Ext::ln(reverb_time) / 0.025 + 40.0) as u8)],
71            });
72        }
73        Self {
74            slot_paths: vec![SlotPath::Reverb],
75            param_id_width: 1,
76            value_width: 1,
77            params,
78        }
79    }
80
81    /// Constructor for a `GlobalParameterControl` directed at a GM2 Chorus slot type.
82    ///
83    /// `mod_rate` is the modulation frequency in Hz (0.0-15.5).
84    ///
85    /// `mod_depth` is the peak-to-peak swing of the modulation in ms (0.3-40.0).
86    ///
87    /// `feedback` is the amount of feedback from Chorus output in percent (0.0-97.0).
88    ///
89    /// `send_to_reverb` is the send level from Chorus to Reverb in percent (0.0-100.0).
90    pub fn chorus(
91        chorus_type: Option<ChorusType>,
92        mod_rate: Option<f32>,
93        mod_depth: Option<f32>,
94        feedback: Option<f32>,
95        send_to_reverb: Option<f32>,
96    ) -> Self {
97        let mut params = vec![];
98
99        if let Some(chorus_type) = chorus_type {
100            params.push(GlobalParameter {
101                id: vec![0],
102                value: vec![chorus_type as u8],
103            });
104        }
105
106        if let Some(mod_rate) = mod_rate {
107            params.push(GlobalParameter {
108                id: vec![1],
109                value: vec![to_u7((mod_rate / 0.122) as u8)],
110            });
111        }
112
113        if let Some(mod_depth) = mod_depth {
114            params.push(GlobalParameter {
115                id: vec![2],
116                value: vec![to_u7(((mod_depth * 3.2) - 1.0) as u8)],
117            });
118        }
119
120        if let Some(feedback) = feedback {
121            params.push(GlobalParameter {
122                id: vec![3],
123                value: vec![to_u7((feedback / 0.763) as u8)],
124            });
125        }
126
127        if let Some(send_to_reverb) = send_to_reverb {
128            params.push(GlobalParameter {
129                id: vec![4],
130                value: vec![to_u7((send_to_reverb / 0.787) as u8)],
131            });
132        }
133
134        Self {
135            slot_paths: vec![SlotPath::Chorus],
136            param_id_width: 1,
137            value_width: 1,
138            params,
139        }
140    }
141
142    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
143        v.push(self.slot_paths.len().min(127) as u8);
144        push_u7(self.param_id_width, v);
145        push_u7(self.value_width, v);
146        for (i, sp) in self.slot_paths.iter().enumerate() {
147            if i > 127 {
148                break;
149            }
150            sp.extend_midi(v);
151        }
152        for p in self.params.iter() {
153            p.extend_midi_with_limits(v, self.param_id_width.max(1), self.value_width.max(1));
154        }
155    }
156
157    #[allow(dead_code)]
158    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
159        Err(ParseError::NotImplemented("GlobalParameterControl"))
160    }
161}
162
163/// The "slot" of the device being referred to by [`GlobalParameterControl`].
164/// Values other than `Unregistered` come from the General MIDI 2 spec.
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum SlotPath {
167    Reverb,
168    Chorus,
169    /// For use in paths not described by the GM2 spec
170    Unregistered(u8, u8),
171}
172
173impl SlotPath {
174    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
175        match self {
176            Self::Reverb => {
177                v.push(1);
178                v.push(1);
179            }
180            Self::Chorus => {
181                v.push(1);
182                v.push(2);
183            }
184            Self::Unregistered(a, b) => {
185                push_u7(*a, v); // MSB first ¯\_(ツ)_/¯
186                push_u7(*b, v);
187            }
188        }
189    }
190
191    #[allow(dead_code)]
192    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
193        Err(ParseError::NotImplemented("SlotPath"))
194    }
195}
196
197/// An `id`:`value` pair that must line up with the [`GlobalParameterControl`] that it is placed in.
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub struct GlobalParameter {
200    pub id: Vec<u8>,
201    pub value: Vec<u8>,
202}
203
204impl GlobalParameter {
205    pub(crate) fn extend_midi_with_limits(
206        &self,
207        v: &mut Vec<u8>,
208        param_id_width: u8,
209        value_width: u8,
210    ) {
211        for i in 0..param_id_width {
212            // MSB first
213            if let Some(x) = self.id.get(i as usize) {
214                push_u7(*x, v);
215            } else {
216                v.push(0);
217            }
218        }
219        for i in (0..value_width).rev() {
220            // LSB first
221            if let Some(x) = self.value.get(i as usize) {
222                push_u7(*x, v);
223            } else {
224                v.push(0);
225            }
226        }
227    }
228
229    #[allow(dead_code)]
230    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
231        Err(ParseError::NotImplemented("GlobalParameter"))
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use crate::*;
238    use alloc::vec;
239
240    #[test]
241    fn serialize_global_parameter() {
242        assert_eq!(
243            MidiMsg::SystemExclusive {
244                msg: SystemExclusiveMsg::UniversalRealTime {
245                    device: DeviceID::AllCall,
246                    msg: UniversalRealTimeMsg::GlobalParameterControl(GlobalParameterControl {
247                        slot_paths: vec![
248                            SlotPath::Unregistered(1, 0x47),
249                            SlotPath::Unregistered(2, 3)
250                        ],
251                        param_id_width: 1,
252                        value_width: 2,
253                        params: vec![
254                            GlobalParameter {
255                                id: vec![4],
256                                value: vec![5, 6, 7] // One byte will be ignored
257                            },
258                            GlobalParameter {
259                                id: vec![4],
260                                value: vec![1] // Only the MSB of two bytes
261                            }
262                        ]
263                    }),
264                },
265            }
266            .to_midi(),
267            vec![
268                0xF0, 0x7F, 0x7F, // Receiver device
269                0x4, 0x5, 0x2,  // Slot path length
270                1,    // Param ID width
271                2,    // Value width
272                1,    // Slot path 1 MSB
273                0x47, // Slot path 1 LSB
274                2,    // Slot path 2 MSB
275                3,    // Slot path 2 LSB
276                4,    // Param number 1
277                6,    // Param value 1 LSB
278                5,    // Param value 1 MSB
279                4,    // Param number 2
280                0,    // Param value 2 LSB
281                1,    // Param value 2 MSB
282                0xF7
283            ]
284        );
285
286        assert_eq!(
287            MidiMsg::SystemExclusive {
288                msg: SystemExclusiveMsg::UniversalRealTime {
289                    device: DeviceID::AllCall,
290                    msg: UniversalRealTimeMsg::GlobalParameterControl(
291                        GlobalParameterControl::chorus(
292                            Some(ChorusType::Flanger),
293                            Some(1.1),
294                            None,
295                            None,
296                            Some(100.0)
297                        )
298                    ),
299                },
300            }
301            .to_midi(),
302            vec![
303                0xF0, 0x7F, 0x7F, // Receiver device
304                0x4, 0x5, 0x1,  // Slot path length
305                0x1,  // Param ID width
306                0x1,  // Value width
307                0x1,  // Slot path 1 MSB
308                0x2,  // Slot path 1 LSB
309                0x0,  // Param number 1: chorus type
310                0x5,  // Param value 1
311                0x1,  // Param number 2: mod rate
312                0x9,  // Param value 2
313                0x4,  // Param number 3: send to reverb
314                0x7F, // Param value 3
315                0xF7
316            ]
317        );
318    }
319}