1mod answer;
4mod drive;
5mod end;
6mod run;
7mod slots;
8mod stream;
9mod terms;
10pub mod until;
11pub(crate) mod value_graph;
12mod volatile;
13mod world;
14
15use std::collections::BTreeMap;
16use std::num::NonZeroUsize;
17use std::sync::Arc;
18
19use sva_ast::Graph;
20use sva_formula::{NodeId, SpectralSum};
21use sva_samples::{Buffer, Extent, FilterTrace, Frames, Label, PSYCHOACOUSTIC_V1, Profile};
22
23use crate::bindings::Binding;
24use crate::cache::{CacheStats, Memory, Recording, Stored, Tier, now};
25use crate::error::{Diagnostic, EngineError, Located};
26use crate::instantiate;
27use crate::query::{Ask, Representation};
28use crate::refs;
29use crate::schedule::{self, Schedule};
30use crate::typing::{self, Typing};
31use end::Ending;
32use value_graph::ValueGraph;
33use value_graph::support::{Memo, Supports};
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct Range {
38 pub start: Option<i64>,
39 pub end: Option<i64>,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct RenderConfig {
44 pub rate: u32,
45 pub range: Range,
46 pub until: Option<Until>,
48 pub profile: Profile,
49 pub asks: Vec<Ask>,
51 pub volatile: Vec<String>,
53 pub out: Out,
54 pub threads: NonZeroUsize,
56}
57
58#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
61pub enum Out {
62 #[default]
63 Kept,
64 Dropped,
65}
66
67impl RenderConfig {
68 pub fn at(rate: u32) -> RenderConfig {
69 RenderConfig {
70 rate,
71 range: Range::default(),
72 until: None,
73 profile: PSYCHOACOUSTIC_V1,
74 asks: Vec::new(),
75 volatile: Vec::new(),
76 out: Out::Kept,
77 threads: crate::threads::default_threads(),
78 }
79 }
80
81 pub fn seconds(rate: u32, secs: f64) -> RenderConfig {
82 let end = (secs * f64::from(rate)).round() as i64;
83 RenderConfig {
84 range: Range {
85 start: Some(0),
86 end: Some(end),
87 },
88 ..RenderConfig::at(rate)
89 }
90 }
91
92 pub fn asking(mut self, asks: Vec<Ask>) -> RenderConfig {
93 self.asks = asks;
94 self
95 }
96}
97
98pub use answer::{answer, answer_buffer, sketch_atom};
99pub use drive::Work;
100pub use run::{Abandon, Hear, Never, Session, render_in, render_over};
101pub use stream::{
102 Built, Change, Changed, Counts, LATEST, Placed, Stream, StreamConfig, change, fetch,
103};
104pub use terms::{Handle, NOTES};
105pub use until::Until;
106pub use world::STREAMED;
107
108pub struct Render {
109 pub root: NodeId,
110 pub tys: Typing,
111 pub buffers: BTreeMap<NodeId, Buffer>,
112 pub frames: BTreeMap<NodeId, Frames>,
113 pub symbolic: BTreeMap<NodeId, SpectralSum>,
114 pub labels: BTreeMap<NodeId, Label>,
115 pub traces: Vec<FilterTrace>,
116 pub config: RenderConfig,
117 pub schedule: Schedule,
118 pub bindings: BTreeMap<NodeId, Vec<Binding>>,
119 pub cache_stats: Option<CacheStats>,
120 pub held_bytes: usize,
122 pub range: Option<Extent>,
124 pub(crate) unranged: Option<EngineError>,
125 pub(crate) read_from: Option<i64>,
127 pub(crate) value_graph: Option<ValueGraph>,
129 computed_samples: u64,
131 pub(crate) unslotted: Option<String>,
132 pub(crate) stand_in_typed: Vec<String>,
134 pub(crate) treated_as_silent_from_sample: Option<i64>,
135 pub(crate) memory: Memory,
137}
138
139impl Render {
140 pub(crate) fn shell(
141 (tys, root): (Typing, NodeId),
142 (config, schedule): (RenderConfig, Schedule),
143 memory: Memory,
144 ) -> Self {
145 Render {
146 root,
147 tys,
148 buffers: BTreeMap::new(),
149 frames: BTreeMap::new(),
150 symbolic: BTreeMap::new(),
151 labels: BTreeMap::new(),
152 traces: Vec::new(),
153 config,
154 schedule,
155 bindings: BTreeMap::new(),
156 cache_stats: None,
157 held_bytes: 0,
158 range: None,
159 unranged: None,
160 read_from: None,
161 value_graph: None,
162 computed_samples: 0,
163 unslotted: None,
164 stand_in_typed: Vec::new(),
165 treated_as_silent_from_sample: None,
166 memory,
167 }
168 }
169
170 pub fn work(&self) -> Work {
172 Work {
173 samples: self.range.map_or(0, |range| range.len() as u64),
174 computed_samples: self.computed_samples,
175 }
176 }
177
178 pub fn output(&self, node: NodeId) -> Result<Buffer, EngineError> {
180 match (self.buffers.get(&node), self.range) {
181 (Some(held), Some(range)) => Ok(held.over(range, held.extent())),
182 _ => Err(answer::unheld(self, node)),
183 }
184 }
185
186 pub fn buffer(&self, node: NodeId) -> Option<&Buffer> {
187 self.buffers.get(&node)
188 }
189
190 pub fn id(&self, path: &str) -> Option<NodeId> {
191 self.tys.id(path)
192 }
193
194 pub fn node(&self, path: &str) -> Result<NodeId, EngineError> {
196 self.tys.resolve(path)
197 }
198
199 pub fn evaluated(&self, node: NodeId) -> Vec<Extent> {
202 let Some(value_graph) = &self.value_graph else {
203 return Vec::new();
204 };
205 value_graph
206 .of(node)
207 .map_or(Vec::new(), |at| value_graph.values[at].evaluated.clone())
208 }
209
210 pub fn alias_oversample(&self, node: NodeId) -> Option<u32> {
212 self.config
213 .asks
214 .iter()
215 .find_map(|ask| match ask.representation {
216 crate::query::Representation::Alias { oversample }
217 if self.node(&ask.node).is_ok_and(|asked| asked == node) =>
218 {
219 Some(oversample)
220 }
221 _ => None,
222 })
223 }
224}
225
226pub fn render(
228 graph: &Graph,
229 target: &str,
230 config: RenderConfig,
231 tier: &Tier,
232) -> Result<Render, EngineError> {
233 now(render_over(graph, target, config, tier))
234}
235
236fn closed(held: &mut Render) -> Result<(), EngineError> {
237 scored(held)?;
238 compose_read(held);
239 stamp(held);
240 Ok(())
241}
242
243pub(crate) struct Prepared {
244 pub(crate) instances: instantiate::Instances,
245 pub(crate) tys: Typing,
246 pub(crate) root: NodeId,
247}
248
249pub(crate) fn prepared(graph: &Graph, target: &str, rate: u32) -> Result<Prepared, EngineError> {
250 let instances = instantiate::instantiate(graph, target, rate)?;
251 let held = instances.instance_of(target)?;
252 let order = schedule::schedule_from(&instances, std::slice::from_ref(&held))?;
253 let tys = typing::infer_all(&instances, &order)?;
254 let root = tys
255 .id(&held)
256 .ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
257 Ok(Prepared {
258 instances,
259 tys,
260 root,
261 })
262}
263
264fn planned(
265 (graph, target): (&Graph, &str),
266 prepared: Prepared,
267 (config, memory): (RenderConfig, Memory),
268) -> Result<Render, EngineError> {
269 let Prepared {
270 instances,
271 tys,
272 root,
273 } = prepared;
274 let config = (config, &mut None, memory);
275 let none = ((Memo::default(), None), &BTreeMap::new());
276 planned_over((graph, target, &instances), (tys, root), config, none)
277}
278
279fn planned_over(
280 (graph, target, instances): (&Graph, &str, &instantiate::Instances),
281 (tys, root): (Typing, NodeId),
282 (config, stand_in, memory): (RenderConfig, &mut Option<world::World>, Memory),
283 (decided, hits): (Decided, &BTreeMap<NodeId, Arc<Stored>>),
284) -> Result<Render, EngineError> {
285 let schedule = schedule::plan(&tys, root, &config.asks);
286 let bindings = tys
287 .paths()
288 .filter_map(|(path, id)| Some((id, resolved(instances, path)?)))
289 .collect();
290 let mut held = Render::shell((tys, root), (config, schedule), memory);
291 held.bindings = bindings;
292 ranged(&mut held, (decided, hits))?;
293 let volatile = volatile::mark((graph, instances), &held, (target, stand_in))?;
294 if let Some(value_graph) = &mut held.value_graph {
295 value_graph.slots(volatile.slots());
296 }
297 held.unslotted = volatile.unslotted.clone();
298 held.stand_in_typed = volatile.typed;
299 Ok(held)
300}
301
302pub fn plan(graph: &Graph, target: &str, config: RenderConfig) -> Result<Render, EngineError> {
304 planned(
305 (graph, target),
306 prepared(graph, target, config.rate)?,
307 (config, Memory::holding(0)),
308 )
309}
310
311pub fn ends(
313 graph: &Graph,
314 roots: &[String],
315 config: &RenderConfig,
316) -> Result<Vec<Option<i64>>, EngineError> {
317 let (instances, named) = instantiate::from_roots(graph, roots, config.rate)?;
318 let order = schedule::schedule_from(&instances, &named)?;
319 let tys = typing::infer_all(&instances, &order)?;
320 let supports = Supports::new(&tys);
321 let ending = Ending::new(&tys, &config.profile, &supports);
322 named
323 .iter()
324 .map(|held| {
325 let id = tys
326 .id(held)
327 .ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
328 Ok(default_end(ending.of(id).support))
329 })
330 .collect()
331}
332
333pub(crate) fn ended(
336 tys: &Typing,
337 root: NodeId,
338 mut config: RenderConfig,
339 (mut found, end): Decided,
340) -> (RenderConfig, Decided) {
341 if config.range.end.is_some() {
342 return (config, (found, None));
343 }
344 let end = end.unwrap_or_else(|| {
345 let supports = Supports::over(tys, Some(&found));
346 let end = Ending::new(tys, &config.profile, &supports).of(root);
347 let more = supports.into_memo();
348 found.extend(more);
349 end
350 });
351 config.range.end = default_end(end.support);
352 (config, (found, Some(end)))
353}
354
355pub(crate) type Decided = (Memo, Option<end::End>);
357
358fn ranged(
361 held: &mut Render,
362 ((mut found, decided), hits): (Decided, &BTreeMap<NodeId, Arc<Stored>>),
363) -> Result<(), EngineError> {
364 let asked = |of: fn(&Representation) -> bool| {
365 held.config.asks.iter().any(|ask| of(&ask.representation))
366 };
367 let envelope = asked(|r| {
368 matches!(
369 r,
370 Representation::Envelope { .. } | Representation::Harmony { .. }
371 )
372 });
373 let pitch = asked(|r| matches!(r, Representation::Pitch { .. }));
374 let tabled = materializes(held);
375 if !tabled && !envelope && !pitch {
376 return Ok(());
377 }
378 let supports = Supports::over(&held.tys, Some(&found));
379 let end = match (held.config.range.end, decided) {
380 (_, Some(end)) => end,
381 (Some(_), None) => Ending::new(&held.tys, &held.config.profile, &supports).exact(held.root),
382 (None, None) => Ending::new(&held.tys, &held.config.profile, &supports).of(held.root),
383 };
384 let support = end.support;
385 held.read_from = Some(start_over(&held.config, support));
386 if !tabled && !envelope {
387 return Ok(());
388 }
389 held.treated_as_silent_from_sample = end.treated_as_silent_from_sample;
390 let range = range_over(
391 (&held.config, held.tys.name(held.root)),
392 support,
393 Ends::Refused,
394 );
395 if !tabled {
396 match range {
397 Ok(range) => held.range = Some(range),
398 Err(refused) => held.unranged = Some(refused),
399 }
400 return Ok(());
401 }
402 held.range = Some(range?);
403 let more = supports.into_memo();
404 found.extend(more);
405 let wanted: Vec<NodeId> = held.schedule.wanted.clone();
406 let root = (held.root, wanted.as_slice());
407 let value_graph = ValueGraph::bounded(&held.tys, root, &held.config.profile, (found, hits))?;
408 value_graph.refuse_endless(held.range.expect("a range was decided"))?;
409 held.value_graph = Some(value_graph);
410 Ok(())
411}
412
413fn materializes(held: &Render) -> bool {
414 !held.schedule.wanted.is_empty()
415}
416
417pub(crate) enum Ends {
418 Refused,
419 Pulled,
420}
421
422pub(crate) fn range_over(
424 (config, name): (&RenderConfig, &str),
425 support: Extent,
426 ends: Ends,
427) -> Result<Extent, EngineError> {
428 let start = start_over(config, support);
429 let end = match config.range.end.or(default_end(support)) {
430 Some(end) => end,
431 None => match ends {
432 Ends::Pulled => i64::MAX,
433 Ends::Refused => return Err(endless(name)),
434 },
435 };
436 Ok(Extent::new(start, end.max(start)))
437}
438
439pub(crate) fn start_over(config: &RenderConfig, support: Extent) -> i64 {
441 config.range.start.unwrap_or_else(|| default_start(support))
442}
443
444fn default_start(support: Extent) -> i64 {
445 match support.is_empty() || support.start == i64::MIN || support.start > 0 {
446 true => 0,
447 false => support.start,
448 }
449}
450
451fn default_end(support: Extent) -> Option<i64> {
452 (support.end != i64::MAX).then_some(support.end)
453}
454
455fn endless(name: &str) -> EngineError {
456 EngineError::refused(Diagnostic {
457 code: "render.no_end".to_string(),
458 message: format!(
459 "`{name}` is read over an interval with no end, and its support never ends"
460 ),
461 location: Located::at(name, None),
462 help: "give the interval an end, as `[0, 2s]`, or crop it".to_string(),
463 })
464}
465
466#[cfg(test)]
468fn pulled(held: &mut Render, memo: (&Memory, Recording)) -> Result<(), EngineError> {
469 if let Some(mut driver) = driving(held, memo)? {
470 while driver.pull()? {}
471 drove(held, driver);
472 }
473 Ok(())
474}
475
476fn driving(
478 held: &mut Render,
479 (memory, recording): (&Memory, Recording),
480) -> Result<Option<drive::Driver>, EngineError> {
481 let (Some(value_graph), Some(range)) = (held.value_graph.take(), held.range) else {
482 return Ok(None);
483 };
484 if !materializes(held) {
485 held.value_graph = Some(value_graph);
486 return Ok(None);
487 }
488 Ok(Some(drive::Driver::new(
489 value_graph,
490 range,
491 (sva_samples::BLOCK as usize).saturating_mul(held.config.threads.get()),
492 &held.config,
493 (memory.clone(), recording),
494 )))
495}
496
497pub(crate) fn dropped(config: &RenderConfig) -> bool {
498 config.out == Out::Dropped && config.asks.is_empty()
499}
500
501fn drove(held: &mut Render, driver: drive::Driver) {
502 let keep = !dropped(&held.config);
503 let range = held.range.expect("a pulled render has a range");
504 held.held_bytes = driver.most_bytes();
505 held.computed_samples = driver.work.computed_samples;
506 held.cache_stats = Some(driver.recording.stats(&driver.memory));
507 if let Some(stop) = driver.stop().filter(|stop| *stop < range.end) {
508 held.range = Some(Extent::new(range.start, stop));
509 }
510 let range = held.range.expect("a pulled render has a range");
511 let value_graph = driver.value_graph;
512 let wanted = held.schedule.wanted.iter().filter(|_| keep);
513 for (id, at) in wanted.map(|id| (*id, value_graph.of(*id))) {
514 let Some(at) = at else {
515 continue;
516 };
517 match &value_graph.values[at].holding {
518 value_graph::Holding::Frames(Some(frames)) => {
519 held.frames.insert(id, (**frames).clone());
520 }
521 _ => {
522 held.buffers.insert(id, value_graph.samples(at, range));
523 held.labels.insert(id, value_graph.label(at));
524 }
525 }
526 }
527 held.value_graph = Some(value_graph);
528}
529
530pub(crate) fn finer(
533 render: &Render,
534 node: NodeId,
535 fine: u32,
536 over: Extent,
537) -> Result<Buffer, EngineError> {
538 let profile = &render.config.profile;
539 let mut value_graph = ValueGraph::finer(&render.tys, node, &[node], profile, i128::from(fine))?;
540 let at = value_graph.root;
541 let memory = &render.memory;
542 let threads = render.config.threads;
543 value_graph.pull(over, (memory, &mut Recording::over(memory)), threads)?;
544 let mut held = value_graph.samples(at, over);
545 held.rate = render.config.rate * fine;
546 Ok(held)
547}
548
549fn scored(held: &mut Render) -> Result<(), EngineError> {
551 let asked: Vec<NodeId> = held
552 .labels
553 .keys()
554 .copied()
555 .filter(|id| held.alias_oversample(*id).is_some())
556 .collect();
557 for id in asked {
558 let sva_samples::Detail::Point {
559 rule,
560 alias_db: None,
561 tail_db,
562 } = held.labels[&id].detail
563 else {
564 continue;
565 };
566 let buffer = held.output(id)?;
567 let alias_db = Some(answer::alias_db(held, id, &buffer)?);
568 let label = held.labels.get_mut(&id).expect("an asked label");
569 label.detail = sva_samples::Detail::Point {
570 rule,
571 alias_db,
572 tail_db,
573 };
574 }
575 Ok(())
576}
577
578#[cfg(test)]
580pub(crate) fn render_apart(
581 graph: &Graph,
582 target: &str,
583 config: RenderConfig,
584) -> Result<Render, EngineError> {
585 let mut held = planned(
586 (graph, target),
587 prepared(graph, target, config.rate)?,
588 (config, Memory::holding(0)),
589 )?;
590 if let (Some(range), Some(_)) = (held.range, &held.value_graph) {
591 let wanted = held.schedule.wanted.clone();
592 let value_graph = ValueGraph::apart(&held.tys, held.root, &wanted, &held.config.profile)?;
593 value_graph.refuse_endless(range)?;
594 held.value_graph = Some(value_graph);
595 }
596 let memory = held.memory.clone();
597 pulled(&mut held, (&memory, Recording::over(&memory)))?;
598 Ok(held)
599}
600
601pub(crate) fn sampled(render: &Render, node: NodeId, over: Extent) -> Result<Buffer, EngineError> {
602 let mut value_graph = ValueGraph::build(&render.tys, node, &[node], &render.config.profile)?;
603 let at = value_graph.root;
604 let memory = &render.memory;
605 let threads = render.config.threads;
606 value_graph.pull(over, (memory, &mut Recording::over(memory)), threads)?;
607 Ok(value_graph.samples(at, over))
608}
609
610fn stamp(held: &mut Render) {
612 let root = held.root;
613 let Some(label) = held.labels.remove(&root) else {
614 return;
615 };
616 let treated_as_silent_from_sample = held
617 .treated_as_silent_from_sample
618 .map(|at| (held.tys.name(root).to_string(), at));
619 let cutting = sva_samples::CuttingBelowSilenceThreshold {
620 silence_threshold_dbfs: held.config.profile.silence_threshold_dbfs,
621 treated_as_silent_from_sample: treated_as_silent_from_sample.into_iter().collect(),
622 };
623 let label = sva_samples::Label {
624 rate: held.config.rate,
625 moved: held.value_graph.as_ref().map(ValueGraph::moved),
626 cutting_below_silence_threshold: Some(cutting),
627 ..label
628 };
629 held.labels.insert(root, label);
630}
631
632fn compose_read(held: &mut Render) {
635 for id in held.schedule.compose.clone() {
636 if held.symbolic.contains_key(&id) {
637 continue;
638 }
639 if let Ok(sum) = refs::spectral_sum_of(&held.tys, id, held.tys.var(id)) {
640 held.symbolic.insert(id, sum);
641 }
642 }
643}
644
645fn resolved(instances: &instantiate::Instances, path: &str) -> Option<Vec<Binding>> {
646 Some(
647 instances
648 .bindings(path)?
649 .into_iter()
650 .map(|(name, expr, cx)| Binding {
651 name: name.to_string(),
652 source: instances.render(expr, cx),
653 })
654 .collect(),
655 )
656}