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