The timed voice: what a [LineTiming] and a [TimedAudio] may and may not be, and what an adapter's
speak_timed must hand back. The first is a law of the port's own types (no adapter can break it, so a
failing one means the type changed); the second holds an adapter to it.
Runs every law of the types.
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}
A voice that can speak answers speak_timed with its audio and a timing of exactly the line asked, one
span per character. (Needs a voice that works, so an adapter that spends money on a call runs it only
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}