1use sim_kernel::{Error, Expr, Result, Symbol};
2use sim_lib_stream_core::{
3 BufferPolicy, ClockDomain, LatencyClass, StreamDirection, StreamEnvelope, StreamMedia,
4 StreamMetadata, StreamValue, TransportProfile,
5};
6
7use crate::{
8 AtomRef, LaneDescriptor, LaneId, LaneKind, LaneTarget, Music, MusicObject, NoteEvent,
9 PlayEvent, TempoMapRef, Time, TimeRange, time_to_tick,
10};
11
12pub type PlayStream = StreamValue;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct PlayContext {
21 pub transport: Symbol,
23 pub tempo: TempoMapRef,
25 pub sample_rate: u32,
27 pub ppq: u32,
29 pub range: TimeRange,
31 pub seed: u64,
33 pub capabilities: Vec<String>,
35 pub site: SiteHint,
37 pub upstream: Vec<PlayEvent>,
39}
40
41impl PlayContext {
42 pub fn new(range: TimeRange) -> Self {
44 Self {
45 transport: Symbol::qualified("music/transport", "offline"),
46 tempo: TempoMapRef::default(),
47 sample_rate: 48_000,
48 ppq: range.start.tpq,
49 range,
50 seed: 0,
51 capabilities: Vec::new(),
52 site: SiteHint::LocalCoroutine,
53 upstream: Vec::new(),
54 }
55 }
56
57 pub fn stream_metadata(&self, id: Symbol, item_count: usize) -> Result<StreamMetadata> {
59 Ok(StreamMetadata::new(
60 id,
61 StreamMedia::Data,
62 StreamDirection::Source,
63 ClockDomain::MidiTick.symbol(),
64 BufferPolicy::bounded(item_count.max(1))?,
65 ))
66 }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum SiteHint {
72 LocalCoroutine,
74 Thread,
76 Process,
78 BrowserWasm,
80 AudioWorklet,
82 Lan,
84}
85
86impl SiteHint {
87 pub fn symbol(self) -> Symbol {
89 match self {
90 Self::LocalCoroutine => Symbol::qualified("site", "local-coroutine"),
91 Self::Thread => Symbol::qualified("site", "thread"),
92 Self::Process => Symbol::qualified("site", "process"),
93 Self::BrowserWasm => Symbol::qualified("site", "browser-wasm"),
94 Self::AudioWorklet => Symbol::qualified("site", "audio-worklet"),
95 Self::Lan => Symbol::qualified("site", "lan"),
96 }
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct PlayableDescriptor {
103 pub id: Symbol,
105 pub lanes: Vec<LaneDescriptor>,
107 pub clock_domain: ClockDomain,
109 pub latency_class: LatencyClass,
111 pub shape: PlayableShape,
113}
114
115#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct PlayableShape {
118 pub symbol: Symbol,
120 pub fields: Vec<String>,
122}
123
124impl PlayableShape {
125 pub fn music_object() -> Self {
127 Self {
128 symbol: Symbol::qualified("music/shape", "playable"),
129 fields: vec![
130 "describe".to_owned(),
131 "prepare".to_owned(),
132 "render-range".to_owned(),
133 "render-preview".to_owned(),
134 "freeze".to_owned(),
135 "as-shape".to_owned(),
136 ],
137 }
138 }
139
140 pub fn to_expr(&self) -> Expr {
142 Expr::Map(vec![
143 (
144 Expr::Symbol(Symbol::new("shape")),
145 Expr::Symbol(self.symbol.clone()),
146 ),
147 (
148 Expr::Symbol(Symbol::new("fields")),
149 Expr::List(self.fields.iter().cloned().map(Expr::String).collect()),
150 ),
151 ])
152 }
153
154 pub fn from_expr(expr: &Expr) -> Result<Self> {
156 let Expr::Map(entries) = expr else {
157 return Err(Error::Eval("playable shape must be a map".to_owned()));
158 };
159 let symbol = entries
160 .iter()
161 .find_map(|(key, value)| match (key, value) {
162 (Expr::Symbol(key), Expr::Symbol(symbol)) if key.name.as_ref() == "shape" => {
163 Some(symbol.clone())
164 }
165 _ => None,
166 })
167 .ok_or_else(|| Error::Eval("playable shape missing shape field".to_owned()))?;
168 let fields = entries
169 .iter()
170 .find_map(|(key, value)| match (key, value) {
171 (Expr::Symbol(key), Expr::List(fields)) if key.name.as_ref() == "fields" => {
172 Some(fields)
173 }
174 _ => None,
175 })
176 .ok_or_else(|| Error::Eval("playable shape missing fields".to_owned()))?
177 .iter()
178 .map(|field| match field {
179 Expr::String(value) => Ok(value.clone()),
180 _ => Err(Error::Eval("playable shape field must be text".to_owned())),
181 })
182 .collect::<Result<Vec<_>>>()?;
183 Ok(Self { symbol, fields })
184 }
185}
186
187#[derive(Clone, Debug, PartialEq, Eq)]
189pub struct FrozenPlayable {
190 pub descriptor: PlayableDescriptor,
192 pub events: Vec<PlayEvent>,
194 pub content_hash: String,
196}
197
198pub trait Playable {
200 fn describe(&self) -> Result<PlayableDescriptor>;
202
203 fn prepare(&mut self, _cx: &PlayContext) -> Result<()> {
205 Ok(())
206 }
207
208 fn render_range(&self, cx: &PlayContext) -> Result<PlayStream>;
210
211 fn render_preview(&self, cx: &PlayContext) -> Result<PlayStream> {
213 self.render_range(cx)
214 }
215
216 fn freeze(&self, cx: &PlayContext) -> Result<FrozenPlayable>;
218
219 fn as_shape(&self) -> PlayableShape {
221 PlayableShape::music_object()
222 }
223}
224
225impl Playable for Music {
226 fn describe(&self) -> Result<PlayableDescriptor> {
227 default_music_descriptor(Symbol::qualified(
228 "music/playable",
229 self.kind().to_ascii_lowercase(),
230 ))
231 }
232
233 fn render_range(&self, cx: &PlayContext) -> Result<PlayStream> {
234 let mut events = render_music_events(self, cx)?;
235 crate::stable_event_order(&mut events);
236 let items = events
237 .iter()
238 .map(|event| event.to_stream_item(ClockDomain::MidiTick.symbol()))
239 .collect::<Result<Vec<_>>>()?;
240 let metadata = cx.stream_metadata(
241 Symbol::qualified("music/play-stream", self.kind()),
242 items.len(),
243 )?;
244 Ok(StreamValue::pull(metadata, items))
245 }
246
247 fn freeze(&self, cx: &PlayContext) -> Result<FrozenPlayable> {
248 let mut events = render_music_events(self, cx)?;
249 crate::stable_event_order(&mut events);
250 let descriptor = self.describe()?;
251 let content_hash = stable_content_hash(&events, cx);
252 Ok(FrozenPlayable {
253 descriptor,
254 events,
255 content_hash,
256 })
257 }
258}
259
260pub fn render_music_events(object: &dyn MusicObject, cx: &PlayContext) -> Result<Vec<PlayEvent>> {
265 let mut atoms = Vec::new();
266 object.voices(Time::from_integer(0), &mut atoms);
267 let mut events = cx.upstream.clone();
268 let note_lane = LaneId::new("notes");
269 for atom in atoms {
270 if let AtomRef::Note(note) = atom.atom {
271 let onset = time_to_tick(atom.onset, cx.ppq).map_err(music_err)?;
272 let duration = time_to_tick(note.duration, cx.ppq).map_err(music_err)?;
273 let Some((time, duration)) = cx.range.clip_span(onset, duration) else {
274 continue;
275 };
276 events.push(PlayEvent::Note(NoteEvent {
277 lane_id: note_lane.clone(),
278 time,
279 duration,
280 pitch: note.pitch,
281 velocity: note.velocity,
282 channel: note.channel,
283 }));
284 }
285 }
286 crate::stable_event_order(&mut events);
287 Ok(events)
288}
289
290pub fn stream_envelopes(stream: &PlayStream) -> Result<Vec<StreamEnvelope>> {
292 let metadata = stream.metadata().clone();
293 let items = stream.take_packets(usize::MAX)?;
294 items
295 .iter()
296 .enumerate()
297 .map(|(sequence, item)| {
298 StreamEnvelope::from_item_with_profile(
299 &metadata,
300 sequence as u64,
301 item,
302 TransportProfile::memory_local(),
303 )
304 })
305 .collect()
306}
307
308fn default_music_descriptor(id: Symbol) -> Result<PlayableDescriptor> {
309 Ok(PlayableDescriptor {
310 id,
311 lanes: vec![
312 LaneDescriptor::new(
313 LaneId::new("notes"),
314 LaneKind::Note,
315 LaneTarget::Instrument(Symbol::qualified("music/target", "default")),
316 0,
317 )
318 .map_err(music_err)?,
319 ],
320 clock_domain: ClockDomain::MidiTick,
321 latency_class: LatencyClass::Interactive,
322 shape: PlayableShape::music_object(),
323 })
324}
325
326fn stable_content_hash(events: &[PlayEvent], cx: &PlayContext) -> String {
327 let mut hash = 0xcbf29ce484222325u64;
328 for byte in format!("{events:?}:{}", cx.seed).bytes() {
329 hash ^= u64::from(byte);
330 hash = hash.wrapping_mul(0x100000001b3);
331 }
332 format!("fnv1a64:{hash:016x}")
333}
334
335fn music_err(err: crate::MusicError) -> Error {
336 Error::Eval(err.to_string())
337}