Skip to main content

sim_lib_stream_clock/
cookbook.rs

1//! Deterministic cookbook builders for stream-clock recipes.
2
3use sim_kernel::{Expr, NumberLiteral, Symbol};
4
5use crate::{Clock, ClockChart, Instant, TempoMap, TempoSegment};
6
7/// Build the modeled tempo chart descriptor used by the cookbook recipe.
8pub 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}