Skip to main content

midi_msg/system_exclusive/
tuning.rs

1use crate::parse_error::*;
2use crate::util::*;
3use alloc::vec::Vec;
4
5/// Change the tunings of one or more notes, either real-time or not.
6/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg) and [`UniversalRealTimeMsg`](crate::UniversalRealTimeMsg).
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct TuningNoteChange {
10    /// Which tuning program is targeted, 0-127. See [`Parameter::TuningProgramSelect`](crate::Parameter::TuningProgramSelect).
11    pub tuning_program_num: u8,
12    /// Which tuning bank is targeted, 0-127. See [`Parameter::TuningBankSelect`](crate::Parameter::TuningBankSelect).
13    pub tuning_bank_num: Option<u8>,
14    /// At most 127 `(MIDI note number, Option<Tuning>)`` pairs.
15    /// A `None` value represents "No change".
16    pub tunings: Vec<(u8, Option<Tuning>)>,
17}
18
19impl TuningNoteChange {
20    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
21        // The tuning_bank_num is pushed by the caller if needed
22        push_u7(self.tuning_program_num, v);
23        push_u7(self.tunings.len() as u8, v);
24        for (note, tuning) in self.tunings.iter() {
25            push_u7(*note, v);
26            if let Some(tuning) = tuning {
27                tuning.extend_midi(v);
28            } else {
29                // "No change"
30                v.push(0x7F);
31                v.push(0x7F);
32                v.push(0x7F);
33            }
34        }
35    }
36
37    #[allow(dead_code)]
38    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
39        Err(ParseError::NotImplemented("TuningNoteChange"))
40    }
41}
42
43/// Set the tunings of all 128 notes.
44/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg).
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct KeyBasedTuningDump {
48    /// Which tuning program is targeted, 0-127. See [`Parameter::TuningProgramSelect`](crate::Parameter::TuningProgramSelect).
49    pub tuning_program_num: u8,
50    /// Which tuning bank is targeted, 0-127. See [`Parameter::TuningBankSelect`](crate::Parameter::TuningBankSelect).
51    pub tuning_bank_num: Option<u8>,
52    /// An exactly 16 character name
53    pub name: [u8; 16],
54    /// Should be exactly 128 Tunings with the index of each value = the MIDI note number being tuned.
55    /// Excess values will be ignored. If fewer than 128 values are supplied, equal temperament
56    /// will be applied to the remaining notes.
57    /// A `None` value represents "No change".
58    pub tunings: Vec<Option<Tuning>>,
59}
60
61impl KeyBasedTuningDump {
62    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
63        if let Some(bank_num) = self.tuning_bank_num {
64            v.push(to_u7(bank_num))
65        }
66        push_u7(self.tuning_program_num, v);
67        for ch in self.name.iter() {
68            v.push(*ch);
69        }
70        let mut i = 0;
71        loop {
72            if i >= 128 {
73                break;
74            }
75            if let Some(tuning) = self.tunings.get(i) {
76                if let Some(tuning) = tuning {
77                    tuning.extend_midi(v);
78                } else {
79                    // "No change"
80                    v.push(0x7F);
81                    v.push(0x7F);
82                    v.push(0x7F);
83                }
84            } else {
85                // The equivalent of equal temperament tuning
86                push_u7(i as u8, v);
87                v.push(0);
88                v.push(0);
89            }
90            i += 1;
91        }
92        v.push(0); // Checksum <- Will be written over by `SystemExclusiveMsg.extend_midi`
93    }
94
95    #[allow(dead_code)]
96    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
97        Err(ParseError::NotImplemented("KeyBasedTuningDump"))
98    }
99}
100
101/// Used to represent a tuning by [`TuningNoteChange`] and [`KeyBasedTuningDump`].
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[derive(Debug, Copy, Clone, PartialEq, Eq)]
104pub struct Tuning {
105    /// The semitone corresponding with the same MIDI note number, 0-127
106    pub semitone: u8,
107    /// Fraction of semitones above the `semitone`, in .0061-cent units.
108    /// 0-16383
109    pub fraction: u16,
110}
111
112impl Tuning {
113    pub fn from_freq(freq: f32) -> Self {
114        if freq < 8.17358 {
115            Self {
116                semitone: 0,
117                fraction: 0,
118            }
119        } else if freq > 13289.73 {
120            Self {
121                semitone: 127,
122                fraction: 16383,
123            }
124        } else {
125            let (semitone, c) = freq_to_midi_note_cents(freq);
126            Self {
127                semitone,
128                fraction: cents_to_u14(c).min(0x3FFE),
129            }
130        }
131    }
132
133    fn extend_midi(&self, v: &mut Vec<u8>) {
134        push_u7(self.semitone, v);
135        let [msb, lsb] = to_u14(self.fraction);
136        v.push(msb); // For some reason this is the opposite order of everything else???
137        v.push(lsb);
138    }
139}
140
141/// Set the tuning of all octaves for a tuning program/bank.
142/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg).
143///
144/// As defined in MIDI Tuning Updated Specification (CA-020/CA-021/RP-020)
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146#[derive(Debug, Copy, Clone, PartialEq, Eq)]
147pub struct ScaleTuningDump1Byte {
148    /// Which tuning program is targeted, 0-127. See [`Parameter::TuningProgramSelect`](crate::Parameter::TuningProgramSelect).
149    pub tuning_program_num: u8,
150    /// Which tuning bank is targeted, 0-127. See [`Parameter::TuningBankSelect`](crate::Parameter::TuningBankSelect).
151    pub tuning_bank_num: u8,
152    /// An exactly 16 character name.
153    pub name: [u8; 16],
154    /// 12 semitones of tuning adjustments repeated over all octaves, starting with C
155    /// Each value represents that number of cents plus the equal temperament tuning,
156    /// from -64 to 63 cents
157    pub tuning: [i8; 12],
158}
159
160impl ScaleTuningDump1Byte {
161    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
162        push_u7(self.tuning_bank_num, v);
163        push_u7(self.tuning_program_num, v);
164        for ch in self.name.iter() {
165            v.push(*ch);
166        }
167
168        for t in self.tuning.iter() {
169            v.push(i_to_u7(*t));
170        }
171
172        v.push(0); // Checksum <- Will be written over by `SystemExclusiveMsg.extend_midi`
173    }
174
175    #[allow(dead_code)]
176    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
177        Err(ParseError::NotImplemented("ScaleTuningDump1Byte"))
178    }
179}
180
181/// Set the high-res tuning of all octaves for a tuning program/bank.
182/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg).
183///
184/// As defined in MIDI Tuning Updated Specification (CA-020/CA-021/RP-020)
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186#[derive(Debug, Copy, Clone, PartialEq, Eq)]
187pub struct ScaleTuningDump2Byte {
188    /// Which tuning program is targeted, 0-127. See [`Parameter::TuningProgramSelect`](crate::Parameter::TuningProgramSelect).
189    pub tuning_program_num: u8,
190    /// Which tuning bank is targeted, 0-127. See [`Parameter::TuningBankSelect`](crate::Parameter::TuningBankSelect).
191    pub tuning_bank_num: u8,
192    /// An exactly 16 character name.
193    pub name: [u8; 16],
194    /// 12 semitones of tuning adjustments repeated over all octaves, starting with C
195    /// Each value represents that fractional number of cents plus the equal temperament tuning,
196    /// from -8192 to 8192 (steps of .012207 cents)
197    pub tuning: [i16; 12],
198}
199
200impl ScaleTuningDump2Byte {
201    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
202        push_u7(self.tuning_bank_num, v);
203        push_u7(self.tuning_program_num, v);
204        for ch in self.name.iter() {
205            v.push(*ch);
206        }
207
208        for t in self.tuning.iter() {
209            let [msb, lsb] = i_to_u14(*t);
210            v.push(lsb);
211            v.push(msb);
212        }
213
214        v.push(0); // Checksum <- Will be written over by `SystemExclusiveMsg.extend_midi`
215    }
216
217    #[allow(dead_code)]
218    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
219        Err(ParseError::NotImplemented("ScaleTuningDump2Byte"))
220    }
221}
222
223/// Set the tuning of all octaves for a set of channels.
224/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg) and [`UniversalRealTimeMsg`](crate::UniversalRealTimeMsg).
225///
226/// As defined in MIDI Tuning Updated Specification (CA-020/CA-021/RP-020)
227#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
228#[derive(Debug, Copy, Clone, PartialEq, Eq)]
229pub struct ScaleTuning1Byte {
230    pub channels: ChannelBitMap,
231    /// 12 semitones of tuning adjustments repeated over all octaves, starting with C
232    /// Each value represents that number of cents plus the equal temperament tuning,
233    /// from -64 to 63 cents
234    pub tuning: [i8; 12],
235}
236
237impl ScaleTuning1Byte {
238    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
239        self.channels.extend_midi(v);
240        for t in self.tuning.iter() {
241            v.push(i_to_u7(*t));
242        }
243    }
244
245    #[allow(dead_code)]
246    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
247        Err(ParseError::NotImplemented("ScaleTuning1Byte"))
248    }
249}
250
251/// Set the high-res tuning of all octaves for a set of channels.
252/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg) and [`UniversalRealTimeMsg`](crate::UniversalRealTimeMsg).
253///
254/// As defined in MIDI Tuning Updated Specification (CA-020/CA-021/RP-020)
255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256#[derive(Debug, Copy, Clone, PartialEq, Eq)]
257pub struct ScaleTuning2Byte {
258    pub channels: ChannelBitMap,
259    /// 12 semitones of tuning adjustments repeated over all octaves, starting with C
260    /// Each value represents that fractional number of cents plus the equal temperament tuning,
261    /// from -8192 to 8192 (steps of .012207 cents)
262    pub tuning: [i16; 12],
263}
264
265impl ScaleTuning2Byte {
266    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
267        self.channels.extend_midi(v);
268        for t in self.tuning.iter() {
269            let [msb, lsb] = i_to_u14(*t);
270            v.push(lsb);
271            v.push(msb);
272        }
273    }
274
275    #[allow(dead_code)]
276    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
277        Err(ParseError::NotImplemented("ScaleTuning2Byte"))
278    }
279}
280
281/// The set of channels to apply this tuning message to. Used by [`ScaleTuning1Byte`] and [`ScaleTuning2Byte`].
282#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
283#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
284pub struct ChannelBitMap {
285    pub channel_1: bool,
286    pub channel_2: bool,
287    pub channel_3: bool,
288    pub channel_4: bool,
289    pub channel_5: bool,
290    pub channel_6: bool,
291    pub channel_7: bool,
292    pub channel_8: bool,
293    pub channel_9: bool,
294    pub channel_10: bool,
295    pub channel_11: bool,
296    pub channel_12: bool,
297    pub channel_13: bool,
298    pub channel_14: bool,
299    pub channel_15: bool,
300    pub channel_16: bool,
301}
302
303impl ChannelBitMap {
304    /// All channels set
305    pub fn all() -> Self {
306        Self {
307            channel_1: true,
308            channel_2: true,
309            channel_3: true,
310            channel_4: true,
311            channel_5: true,
312            channel_6: true,
313            channel_7: true,
314            channel_8: true,
315            channel_9: true,
316            channel_10: true,
317            channel_11: true,
318            channel_12: true,
319            channel_13: true,
320            channel_14: true,
321            channel_15: true,
322            channel_16: true,
323        }
324    }
325
326    /// No channels set
327    pub fn none() -> Self {
328        Self::default()
329    }
330
331    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
332        let mut byte1: u8 = 0;
333        if self.channel_16 {
334            byte1 += 1 << 1;
335        }
336        if self.channel_15 {
337            byte1 += 1 << 0;
338        }
339        v.push(byte1);
340
341        let mut byte2: u8 = 0;
342        if self.channel_14 {
343            byte2 += 1 << 6;
344        }
345        if self.channel_13 {
346            byte2 += 1 << 5;
347        }
348        if self.channel_12 {
349            byte2 += 1 << 4;
350        }
351        if self.channel_11 {
352            byte2 += 1 << 3;
353        }
354        if self.channel_10 {
355            byte2 += 1 << 2;
356        }
357        if self.channel_9 {
358            byte2 += 1 << 1;
359        }
360        if self.channel_8 {
361            byte2 += 1 << 0;
362        }
363        v.push(byte2);
364
365        let mut byte3: u8 = 0;
366        if self.channel_7 {
367            byte3 += 1 << 6;
368        }
369        if self.channel_6 {
370            byte3 += 1 << 5;
371        }
372        if self.channel_5 {
373            byte3 += 1 << 4;
374        }
375        if self.channel_4 {
376            byte3 += 1 << 3;
377        }
378        if self.channel_3 {
379            byte3 += 1 << 2;
380        }
381        if self.channel_2 {
382            byte3 += 1 << 1;
383        }
384        if self.channel_1 {
385            byte3 += 1 << 0;
386        }
387        v.push(byte3);
388    }
389
390    #[allow(dead_code)]
391    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
392        Err(ParseError::NotImplemented("ChannelBitMap"))
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use crate::*;
399    use alloc::vec;
400    use bstr::B;
401    use core::convert::TryInto;
402
403    #[test]
404    fn serialize_tuning_note_change() {
405        assert_eq!(
406            MidiMsg::SystemExclusive {
407                msg: SystemExclusiveMsg::UniversalRealTime {
408                    device: DeviceID::AllCall,
409                    msg: UniversalRealTimeMsg::TuningNoteChange(TuningNoteChange {
410                        tuning_program_num: 5,
411                        tuning_bank_num: None,
412                        tunings: vec![
413                            (
414                                1,
415                                Some(Tuning {
416                                    semitone: 1,
417                                    fraction: 255,
418                                }),
419                            ),
420                            (
421                                0x33,
422                                Some(Tuning {
423                                    semitone: 0x33,
424                                    fraction: 511,
425                                }),
426                            ),
427                            (0x45, None),
428                            (0x78, Some(Tuning::from_freq(8_372.063)))
429                        ],
430                    }),
431                },
432            }
433            .to_midi(),
434            &[
435                0xF0, 0x7F, 0x7F, 0x08, 0x02, 0x05, 4, // Number of changes
436                0x01, 0x01, 0x01, 0x7f, // Tuning 1
437                0x33, 0x33, 0x03, 0x7f, // Tuning 2
438                0x45, 0x7f, 0x7f, 0x7f, // Tuning 3 (no change)
439                // 0x78, 0x78, 0x00, 0x01, // Tuning 4, exact
440                0x78, 0x78, 0x00, 0x02, // Tuning 4, micromath approximation
441                0xF7,
442            ]
443        );
444    }
445
446    #[test]
447    fn serialize_tuning_bulk_dump_reply() {
448        let packet_msg = MidiMsg::SystemExclusive {
449            msg: SystemExclusiveMsg::UniversalNonRealTime {
450                device: DeviceID::AllCall,
451                msg: UniversalNonRealTimeMsg::KeyBasedTuningDump(KeyBasedTuningDump {
452                    tuning_program_num: 5,
453                    tuning_bank_num: None,
454                    name: B("A tuning program").try_into().unwrap(), // B creates a &[u8], try_into converts it into an array
455                    tunings: vec![Some(Tuning {
456                        semitone: 1,
457                        fraction: 255,
458                    })],
459                }),
460            },
461        }
462        .to_midi();
463
464        assert_eq!(packet_msg.len(), 408);
465        assert_eq!(
466            &packet_msg[0..7],
467            &[0xF0, 0x7E, 0x7F, 0x08, 0x01, 0x05, b"A"[0]]
468        );
469
470        assert_eq!(
471            &packet_msg[22..31],
472            &[
473                0x01, 0x01, 0x7f, // Provided tuning
474                0x01, 0x00, 0x00, // Default tuning
475                0x02, 0x00, 0x00 // Default tuning
476            ]
477        );
478    }
479}