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