open-sound-module 0.1.0

A crate providing a client for the Rebel Tech Open Sound Module
//! The **trigger** module provides types for working with Open Sound Module
//! trigger messages.

use crate::osc::write_osc_string;
use byteorder::{BigEndian, ByteOrder};

use std::fmt;
use std::fmt::Display;
use std::io::{Error, ErrorKind, Read};
static TYPE_TAG: &'static str = ",i";
pub static ADDR_A: &'static str = "/osm/a/tr";
pub static ADDR_B: &'static str = "/osm/b/tr";

/// TriggerAddress represents all possible Open Sound Module
/// trigger addresses
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TriggerAddress {
    A,
    B,
}

impl Display for TriggerAddress {
    /// Display Trait for tr::TriggerAddress
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            &TriggerAddress::A => ADDR_A.to_string(),
            &TriggerAddress::B => ADDR_B.to_string(),
        };

        write!(f, "{}", s)
    }
}

/// An Open Sound Module trigger message
/// ```rust
/// use std::io::Read;
/// use open_sound_module::{TriggerMessage,TriggerAddress};
///  
/// fn main() -> Result<(), failure::Error> {
///     let mut msg = TriggerMessage::new(TriggerAddress::B, 1);
///     let bytes = msg.to_vec();
///     let mut buf: [u8; 1024] = [0; 1024];
///     let n = msg.read(&mut buf)?;
///     assert!(n == 20);
///     Ok(())  
/// }
/// ```
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TriggerMessage {
    addr: TriggerAddress,
    arg: i32,
}

impl TriggerMessage {
    /// Create a new TriggerMessage from the following args:
    /// addr (OSC address pattern)
    /// * TriggerAddress::A
    /// * TriggerAddress::B
    /// arg: (float32)
    /// * 0 for off / low
    /// * 1 for on / high
    pub fn new(addr: TriggerAddress, arg: i32) -> TriggerMessage {
        return TriggerMessage {
            addr: addr,
            arg: arg,
        };
    }

    /// Return a vector of u8 bytes represending the
    /// TriggerMessage in Open Sound Control protocol
    pub fn to_vec(&self) -> Vec<u8> {
        let mut buf: Vec<u8> = Vec::new();

        write_osc_string(&mut buf, self.addr.to_string());
        write_osc_string(&mut buf, TYPE_TAG.to_string());

        let mut be_bytes = [0; 4];
        BigEndian::write_i32(&mut be_bytes, self.arg);

        for byte in be_bytes.iter() {
            buf.push(*byte);
        }

        return buf;
    }
}

impl Display for TriggerMessage {
    /// Display trait for TriggerMessage
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {}))", self.addr, self.arg)
    }
}

impl Read for TriggerMessage {
    /// Read trait for TriggerMessage
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
        let v = self.to_vec();

        if buf.len() < v.len() {
            let e = Error::new(ErrorKind::Other, "supplied buffer too small");
            return Err(e);
        }

        buf[..v.len()].clone_from_slice(&v);
        Ok(v.len())
    }
}

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

    const ADDR_START: usize = 0;
    const ADDR_END: usize = 9;
    const TAG_START: usize = 12;
    const TAG_END: usize = 14;
    const ARG_START: usize = 16;
    const ARG_END: usize = 20;
    const ARG_SIZE: usize = 4;
    const MSG_LEN: usize = 20;

    #[test]
    fn test_trigger_addr() -> Result<(), failure::Error> {
        let test_fixture_a = "/osm/a/tr";
        let test_fixture_b = "/osm/a/tr";

        let addr_a = TriggerAddress::A;
        assert!(addr_a.to_string() == test_fixture_a);

        let addr_b = TriggerAddress::B;
        assert!(addr_a.to_string() == test_fixture_b);

        assert!(addr_a != addr_b);

        let addr_a2 = addr_a.clone();
        assert!(addr_a == addr_a2);

        Ok(())
    }

    #[test]
    fn test_trigger_msg_new() -> Result<(), failure::Error> {
        let test_val = 1;
        let msg = TriggerMessage::new(TriggerAddress::A, test_val);
        println!("{}", msg);
        Ok(())
    }

    #[test]
    fn test_trigger_message_to_vec() -> Result<(), failure::Error> {
        let test_val = 1;
        let msg = TriggerMessage::new(TriggerAddress::A, test_val);
        let bytes = msg.to_vec();

        let comp_bytes = ADDR_A.as_bytes();
        let addr_bytes = &bytes[ADDR_START..ADDR_END];
        assert!(comp_bytes == addr_bytes);

        let comp_bytes = TYPE_TAG.as_bytes();
        let flag_bytes = &bytes[TAG_START..TAG_END];
        assert!(comp_bytes == flag_bytes);

        let mut comp_bytes = [0; ARG_SIZE];
        BigEndian::write_i32(&mut comp_bytes, test_val);
        let arg_bytes = &bytes[ARG_START..ARG_END];
        assert!(comp_bytes == arg_bytes);

        Ok(())
    }

    #[test]
    fn test_trigger_message_read() -> Result<(), failure::Error> {
        let test_val = 0;
        let mut bytes: [u8; 1024] = [0; 1024];
        let mut msg = TriggerMessage::new(TriggerAddress::B, test_val);
        let n = msg.read(&mut bytes).unwrap();
        assert!(bytes[..n].len() == MSG_LEN);

        let comp_bytes = ADDR_B.as_bytes();
        let addr_bytes = &bytes[ADDR_START..ADDR_END];
        assert!(comp_bytes == addr_bytes);

        let comp_bytes = TYPE_TAG.as_bytes();
        let flag_bytes = &bytes[TAG_START..TAG_END];
        assert!(comp_bytes == flag_bytes);

        let mut comp_bytes = [0; ARG_SIZE];
        BigEndian::write_i32(&mut comp_bytes, test_val);
        let arg_bytes = &bytes[ARG_START..ARG_END];
        assert!(comp_bytes == arg_bytes);

        Ok(())
    }
}