synth_utils/
mono_midi_receiver.rs

1//! Monophonic MIDI Receiver
2//!
3//! Monophonic means that only one note is active at a time.
4//!
5//! This MIDI receiver can be used to control music synthesizers or for other control purposes (lights, motors, etc).
6//!
7//! A MIDI receiver is fed MIDI data in the form of sequential bytes following the MIDI protocol and converts this MIDI
8//! data into various common synthesizer control signals.
9//!
10//! Not every part of the MIDI protocol is handled.
11
12use heapless::Vec;
13
14use midi_convert::{
15    midi_types::{MidiMessage, Value7},
16    MidiByteStreamParser,
17};
18
19/// A Monophonic MIDI receiver is represented here.
20pub struct MonoMidiReceiver {
21    parser: MidiByteStreamParser,
22
23    // the MIDI channel to listen to in `[0..15]`
24    channel: u8,
25
26    // in `[0..127]`
27    note_num: u8,
28
29    // in `[0.0, 1.0]`
30    velocity: f32,
31
32    // in `[-1.0, 1.0]`
33    pitch_bend: f32,
34
35    // in `[0.0, 1.0]`
36    mod_wheel: f32,
37
38    // in `[0.0, 1.0]`
39    volume: f32,
40
41    // in `[0.0, 1.0]`
42    vcf_cutoff: f32,
43
44    // in `[0.0, 1.0]`
45    vcf_resonance: f32,
46
47    // in `[0.0, 1.0]`
48    portamento_time: f32,
49
50    portamento_enabled: bool,
51    sustain_enabled: bool,
52
53    gate: bool,
54    rising_gate: bool,
55    falling_gate: bool,
56
57    retrigger_mode: RetriggerMode,
58    note_priority: NotePriority,
59
60    // the notes currently being held down, we choose which note is active based on the note-priority-mode
61    held_down_notes: Vec<u8, HELD_DOWN_NOTE_BUFFER_LEN>,
62}
63
64impl MonoMidiReceiver {
65    /// `MonoMidiReceiver::new(c)` is a new Monophonic MIDI receiver which accepts messages on MIDI channel `c`
66    ///
67    /// # Arguments
68    ///
69    /// * `channel` - The zero-based MIDI channel to listen to in `[0..15]`. All other MIDI channels are ignored.
70    ///
71    /// The channel is clamped to `[0..15]`
72    pub fn new(channel: u8) -> Self {
73        Self {
74            parser: MidiByteStreamParser::new(),
75
76            channel: channel.min(15),
77
78            note_num: 0,
79
80            pitch_bend: 0.0_f32,
81
82            velocity: 0.0_f32,
83            mod_wheel: 0.0_f32,
84            volume: 0.0_f32,
85            vcf_cutoff: 0.0_f32,
86            vcf_resonance: 0.0_f32,
87            portamento_time: 0.0_f32,
88
89            portamento_enabled: true,
90            sustain_enabled: true,
91
92            gate: false,
93            rising_gate: false,
94            falling_gate: false,
95
96            retrigger_mode: RetriggerMode::NoRetrigger,
97            note_priority: NotePriority::Last,
98
99            held_down_notes: Vec::new(),
100        }
101    }
102
103    /// `mr.parse(b)` parses incoming MIDI data in the form of sequential bytes `b` and updates its internal state
104    ///
105    /// It is expected to call this function every time a new MIDI byte is received.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use synth_utils::mono_midi_receiver::MonoMidiReceiver;
111    ///
112    /// let mut mr = MonoMidiReceiver::new(1);
113    /// mr.parse(0x91); // note-on on channel 1
114    /// mr.parse(42); // note number 42
115    /// mr.parse(127); // max velocity
116    ///
117    /// assert_eq!(mr.note_num(), 42);
118    /// assert_eq!(mr.velocity(), 1.0);
119    /// ```
120    pub fn parse(&mut self, byte: u8) {
121        match self.parser.parse(byte) {
122            Some(MidiMessage::NoteOn(ch, note, vel)) if u8::from(ch) == self.channel => {
123                // note-on with velocity of zero is interpreted as note-off
124                if 0 == u8::from(vel) {
125                    self.handle_note_off(note.into());
126                } else {
127                    self.handle_note_on(note.into(), vel);
128                };
129            }
130            Some(MidiMessage::NoteOff(ch, note, _)) if u8::from(ch) == self.channel => {
131                self.handle_note_off(note.into());
132            }
133            Some(MidiMessage::PitchBendChange(ch, val_u14)) if u8::from(ch) == self.channel => {
134                self.pitch_bend = f32::from(val_u14);
135            }
136            Some(MidiMessage::ControlChange(ch, cc, val7)) if u8::from(ch) == self.channel => {
137                match u8::from(cc) {
138                    CC_MOD_WHEEL => self.mod_wheel = value7_to_f32(val7),
139                    CC_VOLUME => self.volume = value7_to_f32(val7),
140                    CC_VCF_CUTOFF => self.vcf_cutoff = value7_to_f32(val7),
141                    CC_VCF_RESONANCE => self.vcf_resonance = value7_to_f32(val7),
142                    CC_PORTAMENTO_TIME => self.portamento_time = value7_to_f32(val7),
143                    CC_PORTAMENTO_SWITCH => {
144                        self.portamento_enabled = U7_HALF_SCALE <= u8::from(val7)
145                    }
146                    CC_SUSTAIN_SWITCH => self.sustain_enabled = U7_HALF_SCALE <= u8::from(val7),
147                    CC_ALL_CONTROLLERS_OFF => self.reset_controllers(),
148                    CC_ALL_NOTES_OFF => {
149                        self.held_down_notes.clear();
150                        self.gate = false;
151                        self.rising_gate = false;
152                        self.falling_gate = false;
153                    }
154                    _ => (), // ignore all other MIDI CC messages
155                }
156            }
157            _ => (), // ignore all other MIDI messages
158        }
159    }
160
161    /// `mr.handle_note_on(n, v)` updates the internal state after receiving a note-on message
162    fn handle_note_on(&mut self, note: u8, velocity: Value7) {
163        self.velocity = value7_to_f32(velocity);
164
165        self.held_down_notes.push(note).ok();
166
167        self.note_num = self.choose_next_note();
168
169        self.gate = true;
170        self.falling_gate = false;
171
172        if (self.retrigger_mode == RetriggerMode::AllowRetrigger)
173            | (self.held_down_notes.len() == 1)
174        {
175            self.rising_gate = true;
176        }
177    }
178
179    /// `mr.handle_note_off()` updates the internal state after receiving a note-off message
180    fn handle_note_off(&mut self, note: u8) {
181        // delete the note from the list of notes which are held down
182        self.held_down_notes.retain(|n| *n != note);
183
184        if self.held_down_notes.is_empty() {
185            self.gate = false;
186            self.rising_gate = false;
187            self.falling_gate = true;
188        } else {
189            // we know that there is at least one element in the vec
190            self.note_num = self.choose_next_note();
191        }
192    }
193
194    /// `mr.choose_next_note()` is the next MIDI note to use based on the notes currently held down and note priority
195    ///
196    /// If no notes have been played yet returns note zero
197    fn choose_next_note(&self) -> u8 {
198        match self.note_priority {
199            NotePriority::Last => *self.held_down_notes.last().unwrap_or(&0),
200            NotePriority::High => *self.held_down_notes.iter().max().unwrap_or(&0),
201            NotePriority::Low => *self.held_down_notes.iter().min().unwrap_or(&0),
202        }
203    }
204
205    /// `mr.note_num()` is the current MIDI note number held by the MIDI receiver
206    pub fn note_num(&self) -> u8 {
207        self.note_num
208    }
209
210    /// `mr.pitch_bend()` is the current MIDI pitch-bend value held by the MIDI receiver, in `[-1.0, 1.0]`
211    ///
212    /// Typically a value of -1 means "bend 2 semitones down", 0 means "don't bend at all", and +1 means "bend 2
213    /// semitones up", but this behavior can be tweaked by the end user.
214    pub fn pitch_bend(&self) -> f32 {
215        self.pitch_bend
216    }
217
218    /// `mr.velocity()` is the current MIDI velocity value held by the MIDI receiver, in `[0.0, 1.0]`
219    pub fn velocity(&self) -> f32 {
220        self.velocity
221    }
222
223    /// `mr.mod_wheel()` is the current MIDI mod-wheel value held by the MIDI receiver, in `[0.0, 1.0]`
224    pub fn mod_wheel(&self) -> f32 {
225        self.mod_wheel
226    }
227
228    /// `mr.volume()` is the current MIDI volume value held by the MIDI receiver, in `[0.0, 1.0]`
229    pub fn volume(&self) -> f32 {
230        self.volume
231    }
232
233    /// `mr.vcf_cutoff()` is the current MIDI VCF-cutoff value held by the MIDI receiver, in `[0.0, 1.0]`
234    pub fn vcf_cutoff(&self) -> f32 {
235        self.vcf_cutoff
236    }
237
238    /// `mr.vcf_resonance()` is the current MIDI VCF-resonance value held by the MIDI receiver, in `[0.0, 1.0]`
239    pub fn vcf_resonance(&self) -> f32 {
240        self.vcf_resonance
241    }
242
243    /// `mr.portamento_time()` is the current MIDI portamento-time value held by the MIDI receiver, in `[0.0, 1.0]`
244    pub fn portamento_time(&self) -> f32 {
245        self.portamento_time
246    }
247
248    /// `mr.portamento_enabled()` is true if MIDI portamento is currently enabled
249    pub fn portamento_enabled(&self) -> bool {
250        self.portamento_enabled
251    }
252
253    /// `mr.sustain_enabled()` is true if MIDI sustain is currently enabled
254    pub fn sustain_enabled(&self) -> bool {
255        self.sustain_enabled
256    }
257
258    /// `mr.gate()` is true if any MIDI notes are currently being played
259    pub fn gate(&self) -> bool {
260        self.gate
261    }
262
263    /// `mr.rising_gate()` is true if a new note has been triggered. Self clearing.
264    ///
265    /// When retrigger is not allowed a rising gate is only triggered when a new note is played after all other notes
266    /// have been lifted.
267    ///
268    /// When retrigger is allowed a rising gate is triggered any time a new note-on message is received.
269    pub fn rising_gate(&mut self) -> bool {
270        if self.rising_gate {
271            self.rising_gate = false;
272            true
273        } else {
274            false
275        }
276    }
277
278    /// `mr.falling_gate()` is true if all notes have been released after at least one note was played. Self clearing.
279    pub fn falling_gate(&mut self) -> bool {
280        if self.falling_gate {
281            self.falling_gate = false;
282            true
283        } else {
284            false
285        }
286    }
287
288    /// `mr.set_retrigger_mode(m)` sets the retrigger mode to the given mode `m`
289    pub fn set_retrigger_mode(&mut self, mode: RetriggerMode) {
290        self.retrigger_mode = mode;
291    }
292
293    /// `mr.set_note_priority(p)` sets the note priority to `p`
294    pub fn set_note_priority(&mut self, priority: NotePriority) {
295        self.note_priority = priority;
296    }
297
298    /// `mr.reset_controllers()` resets all implemented MIDI controllers to their default values
299    fn reset_controllers(&mut self) {
300        self.pitch_bend = 0.0_f32;
301        self.mod_wheel = 0.0_f32;
302        self.volume = 0.0_f32;
303        self.vcf_cutoff = 0.0_f32;
304        self.vcf_resonance = 0.0_f32;
305        self.portamento_time = 0.0_f32;
306        self.portamento_enabled = true;
307        self.sustain_enabled = true;
308    }
309}
310
311/// Retrigger mode is represented here
312///
313/// Retriggering means that if the user plays a new MIDI note before releasing the last one, a new rising gate will
314/// be triggered.
315///
316/// When retriggering is disabled this is sometimes called "legato" mode, as overlapping notes blend together.
317///
318/// Classic instruments have used both variations. The MiniMoog does not allow retriggering, while the Arp Odyssey does.
319#[derive(PartialEq, Eq)]
320pub enum RetriggerMode {
321    AllowRetrigger,
322    NoRetrigger,
323}
324
325/// Note priority is represented here
326///
327/// When more than one note is played at a time on a monophonic instrument, we need to decide which note takes priority.
328///
329/// - `Last` priority means that whichever note was played most recently wins
330///
331/// - `High` priority means that whichever note is highest in pitch wins
332///
333/// - `Low` priority means that whichever note is lowest in pitch wins
334pub enum NotePriority {
335    Last,
336    High,
337    Low,
338}
339
340///`value7_to_f32(v)` is the Value7 converted to f32 in `[0.0, 1.0]`
341fn value7_to_f32(val7: Value7) -> f32 {
342    u8::from(val7) as f32 / 127.0_f32
343}
344
345// Common MIDI CC names
346const CC_MOD_WHEEL: u8 = 0x01;
347const CC_VOLUME: u8 = 0x07;
348const CC_VCF_CUTOFF: u8 = 0x47;
349const CC_VCF_RESONANCE: u8 = 0x4A;
350const CC_SUSTAIN_SWITCH: u8 = 0x40;
351const CC_PORTAMENTO_SWITCH: u8 = 0x41;
352const CC_PORTAMENTO_TIME: u8 = 0x05;
353const CC_ALL_CONTROLLERS_OFF: u8 = 0x79;
354const CC_ALL_NOTES_OFF: u8 = 0x7B;
355
356// for MIDI CC used as switches values below half scale are considered false and values at-least half scale are true
357const U7_HALF_SCALE: u8 = 1 << 6;
358
359/// The maximum number of held down MIDI notes we can remember
360///
361/// If the user mashes dowm more notes than this, some information may be lost
362const HELD_DOWN_NOTE_BUFFER_LEN: usize = 32;
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    #[test]
369    fn should_listen_on_correct_channel() {
370        let mut mr = MonoMidiReceiver::new(1);
371        mr.parse(0x91); // note-on on channel 1
372        mr.parse(42); // note number 42
373        mr.parse(127);
374
375        assert_eq!(mr.note_num(), 42);
376    }
377
378    #[test]
379    fn should_not_list_on_wrong_channel() {
380        let mut mr = MonoMidiReceiver::new(1);
381        assert_eq!(mr.note_num(), 0);
382
383        mr.parse(0x92); // wrong channel
384        mr.parse(43); // try to change the note
385        mr.parse(127);
386
387        // note stays the same
388        assert_eq!(mr.note_num(), 0);
389    }
390
391    #[test]
392    fn should_handle_running_status() {
393        let mut mr = MonoMidiReceiver::new(1);
394        mr.parse(0x91);
395        mr.parse(42);
396        mr.parse(127);
397        // change the note with running status
398        mr.parse(43);
399        mr.parse(127);
400
401        // note updates without a new note-on byte
402        assert_eq!(mr.note_num(), 43);
403    }
404
405    #[test]
406    fn gate_goes_on_with_note_on() {
407        let mut mr = MonoMidiReceiver::new(1);
408        mr.parse(0x91);
409        mr.parse(42);
410        mr.parse(127);
411        assert!(mr.gate());
412    }
413
414    #[test]
415    fn gate_goes_off_with_note_off() {
416        let mut mr = MonoMidiReceiver::new(1);
417        mr.parse(0x91);
418        mr.parse(42);
419        mr.parse(127);
420
421        mr.parse(0x81); // note off
422        mr.parse(42);
423        mr.parse(0);
424        assert!(!mr.gate());
425    }
426
427    #[test]
428    fn gate_stays_on_if_any_notes_left_on() {
429        let mut mr = MonoMidiReceiver::new(1);
430        mr.parse(0x91);
431        mr.parse(42);
432        mr.parse(127);
433        mr.parse(43);
434        mr.parse(127);
435        mr.parse(44);
436        mr.parse(127);
437
438        mr.parse(0x81);
439        mr.parse(42);
440        mr.parse(0);
441        mr.parse(44);
442        mr.parse(0);
443
444        // note 43 is still on
445        assert!(mr.gate());
446    }
447
448    #[test]
449    fn gate_turns_off_when_all_notes_are_off() {
450        let mut mr = MonoMidiReceiver::new(1);
451        mr.parse(0x91);
452        mr.parse(42);
453        mr.parse(127);
454        mr.parse(43);
455        mr.parse(127);
456        mr.parse(44);
457        mr.parse(127);
458
459        mr.parse(0x81);
460        mr.parse(42);
461        mr.parse(0);
462        mr.parse(43);
463        mr.parse(0);
464        mr.parse(44);
465        mr.parse(0);
466
467        assert!(!mr.gate());
468    }
469
470    #[test]
471    fn channel_clamps_if_too_big() {
472        let mut mr = MonoMidiReceiver::new(200); // 200 is way too big
473
474        mr.parse(0x9F); // note on on channel 15
475        mr.parse(11);
476        mr.parse(127);
477
478        assert_eq!(mr.note_num(), 11);
479    }
480
481    #[test]
482    fn velocity_of_0_is_treated_as_note_off() {
483        let mut mr = MonoMidiReceiver::new(1);
484        mr.parse(0x91);
485        mr.parse(42);
486        mr.parse(0); // velocity is zero
487        assert!(!mr.gate());
488    }
489
490    #[test]
491    fn velocity_of_0_turns_existing_note_off() {
492        let mut mr = MonoMidiReceiver::new(1);
493        mr.parse(0x91);
494        mr.parse(42);
495        mr.parse(5); // velocity greated than zero
496        assert!(mr.gate());
497
498        mr.parse(42);
499        mr.parse(0); // velocity is zero
500        assert!(!mr.gate());
501    }
502
503    #[test]
504    fn rising_gate_is_self_clearing() {
505        let mut mr = MonoMidiReceiver::new(1);
506        mr.parse(0x91);
507        mr.parse(42);
508        mr.parse(1); // velocity is greater than zero
509        assert!(mr.rising_gate());
510        // if we check the rising gate twice it will be cleared
511        assert!(!mr.rising_gate());
512    }
513
514    #[test]
515    fn can_retrigger_when_retrigger_mode_is_on() {
516        let mut mr = MonoMidiReceiver::new(1);
517
518        mr.set_retrigger_mode(RetriggerMode::AllowRetrigger);
519
520        mr.parse(0x91);
521        mr.parse(42);
522        mr.parse(1);
523        assert!(mr.rising_gate());
524
525        mr.parse(43); // new running status note-on
526        mr.parse(1);
527        assert!(mr.rising_gate());
528    }
529
530    #[test]
531    fn can_not_retrigger_when_retrigger_mode_is_off() {
532        let mut mr = MonoMidiReceiver::new(1);
533
534        mr.set_retrigger_mode(RetriggerMode::NoRetrigger);
535
536        mr.parse(0x91);
537        mr.parse(42);
538        mr.parse(1);
539        assert!(mr.rising_gate());
540
541        mr.parse(43); // new running status note-on
542        mr.parse(1);
543        // we didn't let go of all notes first, so no new retrigger
544        assert!(!mr.rising_gate());
545    }
546
547    #[test]
548    fn note_priority_last_gets_the_last_note() {
549        let mut mr = MonoMidiReceiver::new(1);
550
551        mr.set_note_priority(NotePriority::Last);
552
553        mr.parse(0x91);
554        mr.parse(42);
555        mr.parse(1);
556        mr.parse(43);
557        mr.parse(1);
558        mr.parse(44);
559        mr.parse(1);
560        assert_eq!(mr.note_num(), 44);
561    }
562
563    #[test]
564    fn note_priority_high_gets_the_highest_note() {
565        let mut mr = MonoMidiReceiver::new(1);
566
567        mr.set_note_priority(NotePriority::High);
568
569        mr.parse(0x91);
570        mr.parse(42);
571        mr.parse(1);
572        mr.parse(43);
573        mr.parse(1);
574        mr.parse(44);
575        mr.parse(1);
576        mr.parse(66); // this one is the highest note
577        mr.parse(1);
578        mr.parse(10);
579        mr.parse(1);
580        assert_eq!(mr.note_num(), 66);
581    }
582
583    #[test]
584    fn note_priority_low_gets_the_lowest_note() {
585        let mut mr = MonoMidiReceiver::new(1);
586
587        mr.set_note_priority(NotePriority::Low);
588
589        mr.parse(0x91);
590        mr.parse(42);
591        mr.parse(1);
592        mr.parse(5); // this one is the lowest note
593        mr.parse(1);
594        mr.parse(44);
595        mr.parse(1);
596        mr.parse(66);
597        mr.parse(1);
598        mr.parse(10);
599        mr.parse(1);
600        assert_eq!(mr.note_num(), 5);
601    }
602
603    #[test]
604    fn note_off_keeps_the_last_note() {
605        let mut mr = MonoMidiReceiver::new(1);
606        mr.parse(0x91);
607        mr.parse(42);
608        mr.parse(1);
609
610        mr.parse(0x81); // turn the note off
611        mr.parse(42);
612        mr.parse(0);
613
614        // but it's still retained as the last valid note
615        assert_eq!(mr.note_num(), 42);
616    }
617}