1mod envelope;
4mod floor;
5mod range;
6mod ringing;
7
8use std::borrow::Cow;
9use std::cell::RefCell;
10use std::collections::BTreeMap;
11
12use sva_ast::Graph;
13use sva_formula::{Hash, NodeId};
14use sva_samples::{Buffer, Horizon, Label};
15
16pub(crate) use envelope::Live;
17use envelope::{Bounds, Envelope, Forms, Grid, STEP, Unbounded};
18
19use super::{Lenses, Prepared, Render, RenderConfig, materialize, prepared, run};
20use crate::cache::{Cache, Expected, Lens, Payload, Recording};
21use crate::error::{Diagnostic, EngineError, Located};
22use crate::schedule::Schedule;
23
24#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct Silent {
27 pub bits: u32,
28 pub max_secs: f64,
29}
30
31impl Silent {
32 pub fn threshold(self) -> f64 {
33 2f64.powi(-(self.bits as i32))
34 }
35}
36
37pub fn render_until_silent(
40 graph: &Graph,
41 target: &str,
42 config: RenderConfig,
43 silent: Silent,
44 cache: Option<&Cache>,
45) -> Result<Render, EngineError> {
46 let held = prepared(graph, target)?;
47 let rate = config.rate;
48 let width = held.tys.ty(held.root).width as usize;
49 let key = silent_key(held.identity(&config.asks)?, &config, silent);
50 let volatile = super::volatile::mark(&held.instances, &held.tys, &config, &held.target)?;
52 let recording = cache.map(|c| Recording::over(c, config.cache_policy));
53 let lens = recording
54 .as_ref()
55 .filter(|_| volatile.slot(held.root).is_none())
56 .map(|r| r.at(None, false, true));
57 if let Some((buffer, label)) = recalled(lens.as_ref(), key, &held, rate, width) {
58 let config = ended(config, buffer.len());
59 let render = run(held, config, recording.as_ref(), Some((buffer, label)))?;
60 return Ok(finished(render, recording));
61 }
62 let (proven, proofs) = proven_at(&held, &config, silent)?;
63 let mut render = run(held, ended(config, proven.max(1)), recording.as_ref(), None)?;
64 render.proofs = proofs;
65 let heard = render.buffers.get(&render.root).map_or(0, |b| {
66 (0..b.len())
67 .rev()
68 .find(|&n| (0..b.width).any(|c| b.plane(c)[n].abs() >= silent.threshold()))
69 .map_or(0, |n| n + 1)
70 });
71 trimmed(&mut render, heard.max(1));
72 if let (Some(lens), Some(buffer), Some(label)) = (
73 lens.as_ref(),
74 render.buffers.get(&render.root),
75 render.labels.get(&render.root),
76 ) {
77 remember(lens, key, buffer, label);
78 }
79 Ok(finished(render, recording))
80}
81
82fn finished(mut render: Render, recording: Option<Recording>) -> Render {
83 render.cache_stats = recording.map(Recording::finish);
84 render
85}
86
87pub(super) fn proven_at(
90 held: &Prepared,
91 config: &RenderConfig,
92 silent: Silent,
93) -> Result<(usize, u64), EngineError> {
94 let rate = f64::from(config.rate);
95 let start = config.horizon.start_secs;
96 let samples = ((silent.max_secs - start) * rate).ceil().max(0.0) as usize;
97 let grid = Grid {
98 start,
99 rate,
100 points: samples / STEP + 1,
101 };
102 let rendered = |id: NodeId, end: f64| heard_alone(&held.tys, id, config, end);
103 let threshold = silent.threshold();
104 let mut level = threshold;
105 let forms = Forms::new(Cow::Borrowed(&held.tys), Cow::Borrowed(config));
106 let mut proofs = 0;
107 loop {
108 proofs += 1;
109 let mut bounds = Bounds::new(&forms, grid.clone(), &rendered, level);
110 let envelope = bounded(&held.tys, held.root, &mut bounds, silent)?;
111 if let Some(j) = envelope.at.iter().position(|v| *v < threshold) {
112 return Ok((j * STEP, proofs));
113 }
114 let last = envelope.at.last().copied().unwrap_or(f64::INFINITY);
115 if !bounds.held_flat || !last.is_finite() {
116 return Err(not_by(held, &envelope, silent));
117 }
118 level *= threshold / last / 2.0;
120 }
121}
122
123pub(crate) struct Kept {
126 heard: RefCell<BTreeMap<(NodeId, u64), Buffer>>,
127 forms: Forms<'static>,
128}
129
130impl Kept {
131 pub(crate) fn new(tys: crate::typing::Typing, config: RenderConfig) -> Kept {
132 Kept {
133 heard: RefCell::default(),
134 forms: Forms::new(Cow::Owned(tys), Cow::Owned(config)),
135 }
136 }
137}
138
139pub(crate) fn bound_from(
141 kept: &Kept,
142 root: NodeId,
143 silent: Silent,
144 live: &dyn Live,
145 now: usize,
146) -> Result<f64, EngineError> {
147 let (tys, config) = (&*kept.forms.tys, &*kept.forms.config);
148 let heard = &kept.heard;
149 let grid = Grid {
150 start: now as f64 / f64::from(config.rate),
151 rate: f64::from(config.rate),
152 points: 1,
153 };
154 let rendered = |id: NodeId, end: f64| {
155 if let Some(buffer) = heard.borrow().get(&(id, end.to_bits())) {
156 return Ok(buffer.clone());
157 }
158 let buffer = heard_alone(tys, id, config, end)?;
159 heard
160 .borrow_mut()
161 .insert((id, end.to_bits()), buffer.clone());
162 Ok(buffer)
163 };
164 let mut bounds = Bounds::new(&kept.forms, grid, &rendered, silent.threshold());
165 bounds.live = Some(live);
166 Ok(bounded(tys, root, &mut bounds, silent)?.at[0])
167}
168
169fn bounded(
171 tys: &crate::typing::Typing,
172 root: NodeId,
173 bounds: &mut Bounds,
174 silent: Silent,
175) -> Result<Envelope, EngineError> {
176 let envelope = match bounds.of(root)? {
177 Ok(envelope) => envelope,
178 Err(Unbounded { node, class }) => {
179 return Err(refusal(
180 tys,
181 root,
182 "engine.no_tail_bound",
183 format!(
184 "`{node}` is {class}, and no bound on its tail is derived yet, so silence \
185 is never proven for it."
186 ),
187 "render it to a stated --to instead, or crop it to a window",
188 ));
189 }
190 };
191 let threshold = silent.threshold();
192 if envelope.floor >= threshold {
193 return Err(refusal(
194 tys,
195 root,
196 "engine.never_silent",
197 format!(
198 "it returns to {} forever, at or above the {}-bit floor of {}.",
199 dbfs(envelope.floor),
200 silent.bits,
201 dbfs(threshold)
202 ),
203 "crop it, or give it a release",
204 ));
205 }
206 Ok(envelope)
207}
208
209fn not_by(held: &Prepared, envelope: &Envelope, silent: Silent) -> EngineError {
210 let last = envelope.at.last().copied().unwrap_or(f64::INFINITY);
211 not_silent_by(&held.tys, held.root, Some(last), silent)
212}
213
214pub(crate) fn not_silent_by(
216 tys: &crate::typing::Typing,
217 root: NodeId,
218 last: Option<f64>,
219 silent: Silent,
220) -> EngineError {
221 let floor = format!(
222 "the {}-bit floor of {}",
223 silent.bits,
224 dbfs(silent.threshold())
225 );
226 let found = match last {
227 Some(last) => format!(
228 "its bound at {}s is {}, not under {floor}",
229 silent.max_secs,
230 dbfs(last)
231 ),
232 None => format!(
233 "no block has ended by {}s to prove it under {floor}",
234 silent.max_secs
235 ),
236 };
237 refusal(
238 tys,
239 root,
240 "engine.not_silent_by",
241 format!("{found}, so silence is not proven by then."),
242 "if it decays, raise --max or lower the bits",
243 )
244}
245
246fn dbfs(v: f64) -> String {
247 match v.is_finite() {
248 true => format!("{:.1} dBFS", 20.0 * v.log10()),
249 false => "unbounded".to_string(),
250 }
251}
252
253fn refusal(
254 tys: &crate::typing::Typing,
255 root: NodeId,
256 code: &str,
257 message: String,
258 help: &str,
259) -> EngineError {
260 EngineError::refused(Diagnostic {
261 code: code.to_string(),
262 message,
263 location: Located::at(tys.name(root), None),
264 help: help.to_string(),
265 })
266}
267
268fn ended(mut config: RenderConfig, samples: usize) -> RenderConfig {
269 let start = config.horizon.start_secs;
270 config.horizon = Horizon::secs(start, start + samples as f64 / f64::from(config.rate));
271 config
272}
273
274fn trimmed(render: &mut Render, samples: usize) {
277 for buffer in render.buffers.values_mut() {
278 if buffer.len() > samples {
279 let origin = buffer.origin_secs;
280 *buffer = Buffer::of_planes(
281 buffer.rate,
282 (0..buffer.width)
283 .map(|c| buffer.plane(c)[..samples].to_vec())
284 .collect(),
285 );
286 buffer.origin_secs = origin;
287 }
288 }
289 render.config = ended(render.config.clone(), samples);
290}
291
292fn heard_alone(
294 tys: &crate::typing::Typing,
295 id: NodeId,
296 config: &RenderConfig,
297 end: f64,
298) -> Result<Buffer, EngineError> {
299 let mut held = Render {
300 root: id,
301 tys: tys.clone(),
302 buffers: BTreeMap::new(),
303 frames: BTreeMap::new(),
304 symbolic: BTreeMap::new(),
305 labels: BTreeMap::new(),
306 traces: Vec::new(),
307 config: RenderConfig {
308 horizon: Horizon::secs(config.horizon.start_secs, end),
309 asks: Vec::new(),
310 ..config.clone()
311 },
312 schedule: Schedule::default(),
313 bindings: BTreeMap::new(),
314 cache_stats: None,
315 proofs: 0,
316 };
317 materialize(&mut held, id, &Lenses::none())?;
318 held.buffers
319 .remove(&id)
320 .ok_or_else(|| EngineError::UnknownNode(tys.name(id).to_string()))
321}
322
323const SILENT_TAG: u64 = 0x73_69_6c_65_6e_74_00_01;
324
325fn silent_key((identity, scored): (Hash, bool), config: &RenderConfig, silent: Silent) -> Hash {
328 crate::cache::mixed(
329 identity,
330 &[
331 u64::from(config.rate),
332 config.horizon.start_secs.to_bits(),
333 u64::from(silent.bits),
334 silent.max_secs.to_bits(),
335 u64::from(scored),
336 SILENT_TAG,
337 ],
338 )
339}
340
341fn samples_key(key: Hash, samples: usize) -> Hash {
344 crate::cache::mixed(key, &[samples as u64, SILENT_TAG])
345}
346
347fn recalled(
348 cache: Option<&Lens>,
349 key: Hash,
350 held: &Prepared,
351 rate: u32,
352 width: usize,
353) -> Option<(Buffer, Label)> {
354 let cache = cache?;
355 let name = held.tys.name(held.root);
356 let length = cache.load(
357 key,
358 name,
359 Expected::Samples {
360 rate,
361 width: 1,
362 samples: 1,
363 },
364 )?;
365 let samples = length.payload.samples()?.plane(0)[0] as usize;
366 let entry = cache.load(
367 samples_key(key, samples),
368 name,
369 Expected::Samples {
370 rate,
371 width,
372 samples,
373 },
374 )?;
375 Some((entry.payload.samples().cloned()?, entry.label?))
376}
377
378fn remember(cache: &Lens, key: Hash, buffer: &Buffer, label: &Label) {
379 let payload = Payload::Samples(Box::new(buffer.clone()));
380 cache.store(samples_key(key, buffer.len()), &payload, Some(label));
381 let length = Buffer::of_planes(buffer.rate, vec![vec![buffer.len() as f64]]);
382 cache.store(key, &Payload::Samples(Box::new(length)), None);
383}