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