sim_lib_stream_clock/
cookbook.rs1use sim_kernel::{Expr, NumberLiteral, Symbol};
4
5use crate::{Clock, ClockChart, Instant, TempoMap, TempoSegment};
6
7pub fn tempo_chart_demo() -> Expr {
9 let tempo = TempoMap::new(vec![
10 TempoSegment::new(0, 500_000).expect("valid initial tempo"),
11 TempoSegment::new(1_920, 400_000).expect("valid second tempo"),
12 ])
13 .expect("valid tempo map");
14 let clock = Clock::midi(
15 Symbol::qualified("stream/clock", "cookbook-tempo"),
16 480,
17 tempo,
18 )
19 .expect("valid cookbook MIDI clock");
20 let beat = clock
21 .index_for_instant(Instant::seconds(1))
22 .expect("one second maps onto the MIDI clock");
23 Expr::Map(vec![
24 (field("kind"), sym("stream-clock", "tempo-chart")),
25 (field("clock"), Expr::Symbol(clock.id().clone())),
26 (field("domain"), Expr::Symbol(clock.domain().symbol())),
27 (
28 field("tpq"),
29 match clock.chart() {
30 ClockChart::Midi { tpq, .. } => number(*tpq),
31 ClockChart::Frames { .. } => number(0),
32 },
33 ),
34 (field("index-at-one-second"), number(beat.index().value())),
35 (
36 field("segments"),
37 Expr::Vector(match clock.chart() {
38 ClockChart::Midi { tempo_map, .. } => tempo_map
39 .segments()
40 .iter()
41 .map(|segment| {
42 Expr::Map(vec![
43 (field("start-tick"), number(segment.start_tick)),
44 (field("us-per-quarter"), number(segment.us_per_quarter)),
45 ])
46 })
47 .collect(),
48 ClockChart::Frames { .. } => Vec::new(),
49 }),
50 ),
51 ])
52}
53
54fn field(name: &str) -> Expr {
55 Expr::Symbol(Symbol::qualified("stream-clock", name))
56}
57
58fn sym(namespace: &str, name: &str) -> Expr {
59 Expr::Symbol(Symbol::qualified(namespace, name))
60}
61
62fn number(value: impl ToString) -> Expr {
63 Expr::Number(NumberLiteral {
64 domain: Symbol::qualified("numbers", "i64"),
65 canonical: value.to_string(),
66 })
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn tempo_chart_demo_contains_valid_segments() {
75 let Expr::Map(entries) = tempo_chart_demo() else {
76 panic!("tempo chart demo is a map")
77 };
78 let Some((_, Expr::Vector(segments))) = entries.iter().find(
79 |(key, _)| matches!(key, Expr::Symbol(symbol) if symbol.name.as_ref() == "segments"),
80 ) else {
81 panic!("tempo chart has segments")
82 };
83 assert_eq!(segments.len(), 2);
84 }
85}