midi-control 0.2.0

Communicate with MIDI controllers
Documentation
//
// (c) 2020 Hubert Figuière
//
// License: LGPL-3.0-or-later

//! System Exclusive specific

use crate::consts::system_event;
use crate::message::{SysExEvent, SysExType};
use crate::MidiMessage;

/// System Exclusive Manufacturer Id
/// Originally a single u8.
/// If it is zero, two more u8.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ManufacturerId {
    Id(u8),
    ExtId(u8, u8),
}

impl ManufacturerId {
    /// Creates a ManufacturerId from raw data.
    /// ## Error
    /// Return None if the raw data is too short.
    pub fn from_raw(raw: &[u8]) -> Option<ManufacturerId> {
        if raw[0] != 0 {
            Some(ManufacturerId::Id(raw[0]))
        } else if raw.len() < 3 {
            None
        } else {
            Some(ManufacturerId::ExtId(raw[1], raw[2]))
        }
    }

    /// The byte length this take in raw data.
    pub fn raw_len(self) -> usize {
        match self {
            ManufacturerId::Id(_) => 1,
            ManufacturerId::ExtId(_, _) => 3,
        }
    }

    /// Push manufacturer id to raw data vector
    /// Will push exactly raw_len() bytes.
    pub fn push_to(self, vec: &mut Vec<u8>) {
        match self {
            ManufacturerId::Id(m) => vec.push(m),
            ManufacturerId::ExtId(m1, m2) => {
                vec.push(system_event::sysex::ID_EXTENSION);
                vec.push(m1);
                vec.push(m2);
            }
        }
    }
}

/// Decoder to parse the a SysEx message
/// The decoder is not meant to live longer than the message.
pub struct SysExDecoder<'a>(&'a SysExEvent);

impl SysExDecoder<'_> {
    /// Return a decoder for the message. Return None if the message isn't SysEx
    ///
    /// ## Error
    /// Return None if the message isn't a SysEx messsage.
    pub fn decode(msg: &MidiMessage) -> Option<SysExDecoder> {
        if let MidiMessage::SysEx(ref e) = msg {
            Some(SysExDecoder(e))
        } else {
            None
        }
    }

    /// Message is Universal SysEx
    pub fn is_universal_sysex(&self) -> bool {
        match self.0.get_type() {
            SysExType::NonRealTime(_, _) | SysExType::RealTime(_, _) => true,
            _ => false,
        }
    }
}

/// Universal SysEx decoder.
pub struct USysExDecoder<'a>(&'a SysExEvent);

impl USysExDecoder<'_> {
    /// Creates a new decoder for the message.
    /// ## Error
    /// Returns None if the message isn't a Universal System Exclusive
    pub fn decode(msg: &MidiMessage) -> Option<USysExDecoder> {
        // TODO check the size of the data (at least 6 bytes)
        if let MidiMessage::SysEx(ref e) = msg {
            match e.get_type() {
                SysExType::NonRealTime(_, _) | SysExType::RealTime(_, _) => {
                    return Some(USysExDecoder(e))
                }
                _ => {}
            }
        }
        None
    }

    /// Tell if message is non realtime
    pub fn is_non_realtime(&self) -> bool {
        if let SysExType::NonRealTime(_, _) = self.0.get_type() {
            true
        } else {
            false
        }
    }

    /// Returns the target device
    /// ## Panic
    /// If the message isn't a Universal System Exclusive it will panic.
    /// The decode() function is supposed to check for it.
    pub fn target_device(&self) -> u8 {
        match self.0.get_type() {
            SysExType::NonRealTime(d, _) | SysExType::RealTime(d, _) => *d,
            _ => unreachable!(),
        }
    }

    /// Returns the subid for the Universel System Exclusive message.
    /// ## Panic
    /// If the message isn't a Universal System Exclusive it will panic.
    /// The decode() function is supposed to check for it.
    pub fn subid(&self) -> [u8; 2] {
        match self.0.get_type() {
            SysExType::NonRealTime(_, subid) | SysExType::RealTime(_, subid) => *subid,
            _ => unreachable!(),
        }
    }

    /// Return the manufacturer ID from the General Info reply
    /// Check that the SysEx message is the right one.
    /// ## Error
    /// Returns None if the message isn't the right one.
    pub fn general_info_reply_manufacturer_id(&self) -> Option<ManufacturerId> {
        if self.is_non_realtime() && self.subid() == [6, 2] {
            let data = self.0.get_data();
            if data.len() >= 8 {
                return ManufacturerId::from_raw(data);
            }
        }
        None
    }

    /// Return the device family from the General Info reply
    /// Check that the SysEx message is the right one.
    /// ## Error
    /// Returns None if the message isn't the right one.
    pub fn general_info_reply_family(&self) -> Option<([u8; 2], [u8; 2])> {
        if self.is_non_realtime() && self.subid() == [6, 2] {
            let data = self.0.get_data();
            if data.len() >= 8 {
                return Some(([data[3], data[4]], [data[5], data[6]]));
            }
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_sysex_decoder() {
        // SysEx
        let msg = MidiMessage::SysEx(SysExEvent::new_non_realtime(
            0,
            [6, 2],
            &[0, 32, 107, 2, 0, 4, 2, 67, 7, 0, 1, 247],
        ));
        let decoder = SysExDecoder::decode(&msg);
        assert!(decoder.is_some());
        let decoder = decoder.unwrap();
        assert!(decoder.is_universal_sysex());

        // Universal SysEx
        let decoder = USysExDecoder::decode(&msg);
        assert!(decoder.is_some());
        let decoder = decoder.unwrap();
        assert!(decoder.is_non_realtime());
        assert_eq!(decoder.target_device(), 0);
        assert_eq!(decoder.subid(), [6, 2]);
        assert_eq!(
            decoder.general_info_reply_manufacturer_id(),
            Some(ManufacturerId::ExtId(32, 107))
        );
        assert_eq!(decoder.general_info_reply_family(), Some(([2, 0], [4, 2])));
    }
}