open-sound-module 0.1.0

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

use crate::osc::write_osc_string;
use byteorder::{BigEndian, ByteOrder};
use std::fmt;
use std::fmt::Display;
use std::io::{Error, ErrorKind, Read};
use std::str::FromStr;

static ADDR_A: &'static str = "/osm/a/cv";
static ADDR_B: &'static str = "/osm/b/cv";
static TYPE_TAG: &'static str = ",f";

/// cv::CvAddress represents all valid Open Sound Module
/// cv addresses.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CvAddress {
    A,
    B,
}

impl FromStr for CvAddress {
    type Err = failure::Error;

    // Create a cv::CvAddress from a string.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "/osm/a/cv" {
            return Ok(CvAddress::A);
        } else if s == "/osm/b/cv" {
            return Ok(CvAddress::B);
        }
        return Err(failure::format_err!("{} is not a known address", s));
    }
}

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

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

/// An Open Sound Module cv message
/// ```rust
/// use std::io::Read;
/// use open_sound_module::{CvMessage, CvAddress};
///  
/// fn main() -> Result<(), failure::Error> {
///     let mut msg = CvMessage::new(CvAddress::A, -0.3);
///     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 CvMessage {
    pub addr: CvAddress,
    pub arg: f32,
}

impl CvMessage {
    /// Create a new CvMessage from the following args:
    /// addr (OSC address pattern)
    /// * CvAddress::A
    /// * CvAddress::B
    /// arg: (float32)
    /// * 0.0 to 1.0 for unipolar signal from 0 to 5v
    /// * -1.0 to 1.0 for bipolar signal from -5 to 5v
    pub fn new(addr: CvAddress, arg: f32) -> CvMessage {
        return CvMessage {
            addr: addr,
            arg: arg,
        };
    }

    /// Return a vector of u8 bytes represending the
    /// CvMessage 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_f32(&mut be_bytes, self.arg);

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

        return buf;
    }
}

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

impl Read for CvMessage {
    /// Read Trait for CvMessage
    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_cv_addr() -> Result<(), failure::Error> {
        let test_fixture_a = "/osm/a/cv";
        let test_fixture_b = "/osm/a/cv";

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

        let addr_b = CvAddress::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_cv_msg_to_vec() -> Result<(), failure::Error> {
        let test_val = 0.1;
        let msg = CvMessage::new(CvAddress::A, test_val);
        let bytes = msg.to_vec();
        assert!(bytes.len() == MSG_LEN);

        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_f32(&mut comp_bytes, test_val);
        let arg_bytes = &bytes[ARG_START..ARG_END];
        assert!(comp_bytes == arg_bytes);

        Ok(())
    }

    #[test]
    fn test_cv_msg_read() -> Result<(), failure::Error> {
        let test_val = -0.5;

        let mut bytes: [u8; 1024] = [0; 1024];
        let mut msg = CvMessage::new(CvAddress::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_f32(&mut comp_bytes, test_val);
        let arg_bytes = &bytes[ARG_START..ARG_END];
        assert!(comp_bytes == arg_bytes);

        Ok(())
    }
}