This repository has no description
1use super::hooks::{InnerHook, InnerHookRenderFunction};
2use crate::Timestamp;
3use crate::synchronization::audio::{Note, StemAtInstant};
4use crate::synchronization::sync::SyncData;
5use itertools::Itertools;
6use std::fmt::Display;
7use std::fs::{self};
8use std::path::PathBuf;
9use std::time::Duration;
10
11pub struct Context<'a, AdditionalContext = ()> {
12 pub ms: usize,
13 pub fps: usize,
14 pub bpm: usize,
15 pub syncdata: &'a SyncData,
16 pub audiofile: PathBuf,
17 pub inner_hooks: Vec<InnerHook<AdditionalContext>>,
18 pub extra: AdditionalContext,
19 pub duration_override: Option<Duration>,
20 pub current_scene: Option<String>,
21 pub scene_started_at_ms: Option<usize>,
22 pub rendered_frames: usize,
23}
24
25impl<C> Context<'_, C> {
26 pub fn timestamp(&self) -> Timestamp {
27 Timestamp(self.ms)
28 }
29
30 pub fn beat_fractional(&self) -> f32 {
31 (self.bpm * self.ms) as f32 / (1000.0 * 60.0)
32 }
33
34 pub fn beat(&self) -> usize {
35 self.beat_fractional() as usize
36 }
37
38 pub fn frame(&self) -> usize {
39 self.ms_to_frame(self.ms)
40 }
41
42 pub fn scene_frame(&self) -> Option<usize> {
43 self.scene_started_at_ms
44 .map(|start_ms| self.ms_to_frame(self.ms - start_ms))
45 }
46
47 pub fn ms_to_frame(&self, ms: usize) -> usize {
48 self.fps * ms / 1000
49 }
50
51 pub fn stem(&self, name: &str) -> StemAtInstant {
52 let stems = &self.syncdata.stems;
53 if !stems.contains_key(name) {
54 panic!(
55 "No stem named {:?} found. Available stems:\n{}\n",
56 name,
57 stems
58 .keys()
59 .sorted()
60 .fold(String::new(), |acc, k| format!("{acc}\n\t{k}"))
61 );
62 }
63 StemAtInstant {
64 amplitude: *stems[name].amplitude_db.get(self.ms).unwrap_or(&0.0),
65 amplitude_max: stems[name].amplitude_max,
66 velocity_max: stems[name]
67 .notes
68 .get(&self.ms)
69 .iter()
70 .map(|notes| {
71 notes.iter().map(|note| note.velocity).max().unwrap_or(0)
72 })
73 .max()
74 .unwrap_or(0),
75 duration: stems[name].duration_ms,
76 notes: stems[name].notes.get(&self.ms).cloned().unwrap_or(vec![]),
77 playcount: stems[name]
78 .notes
79 .iter()
80 .filter(|&(&ms, notes)| {
81 ms < self.ms
82 && !notes.is_empty()
83 && notes.iter().any(|note| note.is_on())
84 })
85 .count(),
86 }
87 }
88
89 pub fn since_start(&self) -> Duration {
90 Duration::from_millis(self.ms as _)
91 }
92
93 pub fn since_scene_start(&self) -> Option<Duration> {
94 self.scene_started_at_ms
95 .map(|start_ms| Duration::from_millis((self.ms - start_ms) as _))
96 }
97
98 pub fn notes_of_stem(&self, name: &str) -> impl Iterator<Item = Note> + '_ {
99 let stem = &self.syncdata.stems[name];
100 stem.notes
101 .get(&self.ms)
102 .into_iter()
103 .flat_map(|notes| notes.iter().cloned())
104 }
105
106 pub fn dump_syncdata(&self, to: PathBuf) -> anyhow::Result<()> {
107 Ok(serde_cbor::to_writer(fs::File::create(to)?, self.syncdata)?)
108 }
109
110 pub fn marker(&self) -> String {
111 self.syncdata
112 .markers
113 .get(&self.ms)
114 .map(|marker| marker.to_string())
115 .unwrap_or_default()
116 }
117
118 pub fn latest_marker(&self) -> String {
119 self.syncdata
120 .markers
121 .iter()
122 .filter(|&(&ms, _)| ms <= self.ms)
123 .max_by_key(|&(&ms, _)| ms)
124 .map(|(_, marker)| marker.to_string())
125 .unwrap_or_default()
126 }
127
128 pub fn duration_ms(&self) -> usize {
129 match self.duration_override {
130 Some(duration) => duration.as_millis() as _,
131 None => self
132 .syncdata
133 .stems
134 .values()
135 .map(|stem| stem.duration_ms)
136 .max()
137 .unwrap(),
138 }
139 }
140
141 pub fn later_frames(
142 &mut self,
143 delay: usize,
144 render_function: &'static InnerHookRenderFunction,
145 ) {
146 let current_frame = self.frame();
147
148 self.inner_hooks.insert(
149 0,
150 InnerHook {
151 once: true,
152 when: Box::new(move |_, context, _previous_beat| {
153 context.frame() >= current_frame + delay
154 }),
155 render_function: Box::new(render_function),
156 },
157 );
158 }
159
160 pub fn later_ms(
161 &mut self,
162 delay: usize,
163 render_function: &'static InnerHookRenderFunction,
164 ) {
165 let current_ms = self.ms;
166
167 self.inner_hooks.insert(
168 0,
169 InnerHook {
170 once: true,
171 when: Box::new(move |_, context, _previous_beat| {
172 context.ms >= current_ms + delay
173 }),
174 render_function: Box::new(render_function),
175 },
176 );
177 }
178
179 pub fn later_beats(
180 &mut self,
181 delay: f32,
182 render_function: &'static InnerHookRenderFunction,
183 ) {
184 let current_beat = self.beat();
185
186 self.inner_hooks.insert(
187 0,
188 InnerHook {
189 once: true,
190 when: Box::new(move |_, context, _previous_beat| {
191 context.beat_fractional() >= current_beat as f32 + delay
192 }),
193 render_function: Box::new(render_function),
194 },
195 );
196 }
197
198 pub fn switch_scene(&mut self, scene_name: impl Display) {
199 self.current_scene = Some(scene_name.to_string());
200 self.scene_started_at_ms = Some(self.ms);
201 }
202}