rocoder 0.4.0

A live-codeable phase vocoder
Documentation
use std::time::Duration;

use anyhow::{bail, Result};

pub fn parse_duration(duration_str: &str) -> Result<Duration> {
    let parts: Vec<&str> = duration_str.rsplit(":").collect();
    if parts.len() > 3 || parts.len() < 1 {
        bail!("Invalid duration specification".to_string());
    }
    let seconds_str = parts.get(0).unwrap();
    let maybe_minutes_str = parts.get(1);
    let maybe_hours_str = parts.get(2);

    let seconds_val: f32 = seconds_str.parse()?;
    let milliseconds_val = (seconds_val * 1000.0) as u64;

    let mut dur = Duration::from_millis(milliseconds_val);
    if let Some(minutes_str) = maybe_minutes_str {
        dur += Duration::from_secs(minutes_str.parse::<u64>()? * 60);
    }
    if let Some(hours_str) = maybe_hours_str {
        dur += Duration::from_secs(hours_str.parse::<u64>()? * 60 * 60);
    }
    Ok(dur)
}

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

    #[test_case("adkjfn", None ; "nonsense fails")]
    #[test_case("1", Some(Duration::from_secs(1)) ; "only seconds val")]
    #[test_case("1:1", Some(Duration::from_secs(61)) ; "seconds and minutes")]
    #[test_case("1:1:1", Some(Duration::from_secs(3661)) ; "all fields")]
    #[test_case("1:1:1.234", Some(Duration::from_secs(3661) + Duration::from_millis(234)) ; "float second")]
    #[test_case("1:2:3:4", None ; "too many fields fails")]
    #[test_case("1:2.9:4", None ; "float minute fails")]
    #[test_case("1.9:2:4", None ; "float hour fails")]
    fn test_parse_duration(duration_str: &str, expected_result_as_opt: Option<Duration>) {
        assert_eq!(parse_duration(duration_str).ok(), expected_result_as_opt);
    }
}