1//! The timed voice: what a [`LineTiming`] and a [`TimedAudio`] may and may not be, and what an adapter's 2//! `speak_timed` must hand back. The first is a law of the port's own types (no adapter can break it, so a 3//! failing one means the type changed); the second holds an adapter to it. 4 5use ::log::info; 6use whiskers_ports::{Audio, AudioMs, LineTiming, SpeechLine, TimedAudio, TimingError, Voice, run_ready}; 7 8fn spans(pairs: &[(u32, u32)]) -> Vec<(AudioMs, AudioMs)> { 9 pairs.iter().map(|&(s, e)| (AudioMs::new(s), AudioMs::new(e))).collect() 10} 11 12/// Runs every law of the types. 13pub fn timing_types() { 14 info!("conformance: timing types"); 15 a_timing_has_one_span_per_character(); 16 a_span_never_ends_before_it_starts(); 17 speech_never_runs_backwards(); 18 a_timed_line_is_of_the_line_spoken(); 19} 20 21pub fn a_timing_has_one_span_per_character() { 22 // Characters are Unicode scalar values (the unit SpeechLine::chars counts in), not bytes or UTF-16 units. 23 let text = "h\u{e9}\u{1F408}"; 24 assert_eq!(text.chars().count(), 3); 25 assert!(LineTiming::new(text.into(), spans(&[(0, 1), (1, 2), (2, 3)])).is_ok()); 26 for wrong in [spans(&[(0, 1), (1, 2)]), spans(&[(0, 1), (1, 2), (2, 3), (3, 4)]), vec![]] { 27 let n = wrong.len(); 28 assert_eq!(LineTiming::new(text.into(), wrong), Err(TimingError::Count { characters: 3, spans: n })); 29 } 30 assert_eq!(LineTiming::new(String::new(), vec![]), Err(TimingError::Empty), "there is nothing to time in no text"); 31} 32 33pub fn a_span_never_ends_before_it_starts() { 34 assert!(LineTiming::new("a".into(), spans(&[(5, 5)])).is_ok(), "an instant is a span"); 35 assert_eq!(LineTiming::new("ab".into(), spans(&[(0, 9), (20, 10)])), Err(TimingError::Reversed { index: 1 })); 36} 37 38pub fn speech_never_runs_backwards() { 39 assert!(LineTiming::new("ab".into(), spans(&[(0, 10), (10, 20)])).is_ok(), "touching is fine"); 40 assert!(LineTiming::new("ab".into(), spans(&[(0, 10), (4, 20)])).is_ok(), "so is a little overlap"); 41 assert_eq!(LineTiming::new("ab".into(), spans(&[(10, 20), (9, 30)])), Err(TimingError::OutOfOrder { index: 1 }), "a start going back"); 42 assert_eq!(LineTiming::new("ab".into(), spans(&[(10, 20), (12, 19)])), Err(TimingError::OutOfOrder { index: 1 }), "an end going back"); 43} 44 45pub fn a_timed_line_is_of_the_line_spoken() { 46 let line = SpeechLine::new("Hi".into()).unwrap(); 47 let timing = LineTiming::new("Hi".into(), spans(&[(0, 50), (50, 90)])).unwrap(); 48 let timed = TimedAudio::new(Audio(vec![1, 2]), timing.clone(), &line).unwrap(); 49 assert_eq!((timed.audio().0.as_slice(), timed.timing()), (&[1u8, 2][..], &timing)); 50 let other = SpeechLine::new("Ha".into()).unwrap(); 51 assert_eq!(TimedAudio::new(Audio(vec![1, 2]), timing, &other), Err(TimingError::NotThisLine), "timing of another line is not this line's"); 52} 53 54/// A voice that can speak answers `speak_timed` with its audio and a timing of exactly the line asked, one 55/// span per character. (Needs a voice that works, so an adapter that spends money on a call runs it only 56/// when its operator has agreed to the spend.) 57pub fn speaks_timed<V: Voice>(v: &V) { 58 info!("conformance: timed voice"); 59 let line = SpeechLine::new("Hi Paula, it is a sunny day.".into()).unwrap(); 60 let timed = run_ready(v.speak_timed(&line)).expect("a configured voice speaks"); 61 assert!(!timed.audio().0.is_empty(), "there is sound"); 62 assert_eq!(timed.timing().text(), line.as_str(), "the timing is of the line that was asked for"); 63 assert_eq!(timed.timing().spans().len() as u32, line.chars(), "a span for every character, counted as the allowance counts them"); 64}