1use std::collections::{BTreeMap, BTreeSet, VecDeque};
4
5use crate::buffer::Buffer;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum Unit {
9 Amplitude,
10 Hertz,
11 Q,
12 Decibels,
13 Seconds,
14 Radians,
15 Ratio,
16}
17
18impl Unit {
19 pub fn name(self) -> &'static str {
20 match self {
21 Unit::Amplitude => "amplitude",
22 Unit::Hertz => "hz",
23 Unit::Q => "q",
24 Unit::Decibels => "db",
25 Unit::Seconds => "seconds",
26 Unit::Radians => "radians",
27 Unit::Ratio => "ratio",
28 }
29 }
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum SignalKind {
34 Audio,
35 Control(Unit),
36}
37
38impl SignalKind {
39 pub fn is_audio(self) -> bool {
40 self == SignalKind::Audio
41 }
42
43 pub fn unit(self) -> Unit {
44 match self {
45 SignalKind::Audio => Unit::Amplitude,
46 SignalKind::Control(u) => u,
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq)]
52pub struct LedgerEntry {
53 pub node: String,
54 pub channel: Option<usize>,
55 pub depth: usize,
56 pub kind: SignalKind,
57 pub rms: f64,
58 pub peak: f64,
59 pub min: f64,
60 pub max: f64,
61 pub share: Option<f64>,
65 pub clipped: Option<bool>,
68}
69
70pub struct Sources<'a> {
72 pub read: &'a BTreeMap<String, usize>,
73 pub own: &'a BTreeMap<String, usize>,
74 pub attributed: &'a BTreeSet<String>,
75 pub widths: &'a [usize],
76}
77
78pub struct Tally {
80 rows: Vec<Row>,
81}
82
83struct Row {
84 node: String,
85 depth: usize,
86 kind: SignalKind,
87 attributed: bool,
88 read: Option<usize>,
89 reader: Option<usize>,
90 channels: Vec<Sums>,
91}
92
93#[derive(Clone, Copy)]
94struct Sums {
95 squares: f64,
96 count: usize,
97 peak: f64,
98 min: f64,
99 max: f64,
100 product: f64,
101 energy: f64,
102}
103
104impl Sums {
105 fn new() -> Sums {
106 let zero: f64 = std::iter::empty::<f64>().sum();
108 Sums {
109 squares: zero,
110 count: 0,
111 peak: 0.0,
112 min: f64::INFINITY,
113 max: f64::NEG_INFINITY,
114 product: zero,
115 energy: zero,
116 }
117 }
118}
119
120impl Tally {
121 pub fn new(
122 sources: &Sources,
123 deps: &BTreeMap<String, Vec<String>>,
124 kinds: &BTreeMap<String, SignalKind>,
125 target: &str,
126 depth_limit: usize,
127 ) -> Tally {
128 let width = |path: &str| sources.read.get(path).map_or(1, |s| sources.widths[*s]);
129 let mut seen: BTreeSet<&str> = BTreeSet::new();
130 let mut queue: VecDeque<(&str, usize, &str)> = VecDeque::new();
131 let mut rows = Vec::new();
132 queue.push_back((target, 0, target));
133 seen.insert(target);
134 while let Some((path, depth, reader)) = queue.pop_front() {
135 rows.push(Row {
137 node: path.to_string(),
138 depth,
139 kind: kinds.get(path).copied().unwrap_or(SignalKind::Audio),
140 attributed: path == target || sources.attributed.contains(path),
141 read: sources.read.get(path).copied(),
142 reader: sources
143 .own
144 .get(reader)
145 .copied()
146 .filter(|s| sources.widths[*s] > 0),
147 channels: vec![Sums::new(); width(path)],
148 });
149 if depth >= depth_limit {
150 continue;
151 }
152 for child in deps.get(path).map(Vec::as_slice).unwrap_or(&[]) {
153 if seen.insert(child.as_str()) {
154 let key = deps
155 .get_key_value(child.as_str())
156 .map(|(k, _)| k.as_str())
157 .unwrap_or(child.as_str());
158 queue.push_back((key, depth + 1, path));
159 }
160 }
161 }
162 Tally { rows }
163 }
164
165 pub fn add(&mut self, held: &[Option<&Buffer>], range: std::ops::Range<usize>) {
166 let empty: &[f64] = &[];
167 let slice = |source: Option<usize>, c: usize| -> Option<std::borrow::Cow<'_, [f64]>> {
168 let buffer = held[source?]?;
169 Some(buffer.plane_slice(c % buffer.width(), range.clone()))
170 };
171 for row in &mut self.rows {
172 for (c, sums) in row.channels.iter_mut().enumerate() {
173 let x = match row.read {
174 Some(source) => slice(Some(source), c).expect("a row's own source"),
175 None => std::borrow::Cow::Borrowed(empty),
176 };
177 sums.count += x.len();
178 sums.squares = x.iter().map(|&v| v * v).fold(sums.squares, |a, b| a + b);
179 sums.peak = x.iter().fold(sums.peak, |a, &v| a.max(v.abs()));
180 sums.min = x.iter().fold(sums.min, |a, &v| a.min(v));
181 sums.max = x.iter().fold(sums.max, |a, &v| a.max(v));
182 if let Some(y) = slice(row.reader, c) {
183 sums.energy = y.iter().map(|&v| v * v).fold(sums.energy, |a, b| a + b);
184 let products = x.iter().zip(y.iter()).map(|(a, b)| a * b);
185 sums.product = products.fold(sums.product, |a, b| a + b);
186 }
187 }
188 }
189 }
190
191 pub fn entries(&self) -> Vec<LedgerEntry> {
192 let mut out = Vec::new();
193 for row in &self.rows {
194 let components = row.channels.len();
195 let gated = row.kind.is_audio();
196 for (c, sums) in row.channels.iter().enumerate() {
197 let rms = match sums.count {
198 0 => 0.0,
199 count => (sums.squares / count as f64).sqrt(),
200 };
201 let share = || match (row.reader, sums.energy > 0.0) {
203 (Some(_), true) => Some(sums.product / sums.energy),
204 (Some(_), false) => None,
205 (None, _) => Some(0.0),
206 };
207 out.push(LedgerEntry {
208 node: row.node.clone(),
209 channel: (components > 1).then_some(c),
210 depth: row.depth,
211 kind: row.kind,
212 rms,
213 peak: sums.peak,
214 min: sums.min,
215 max: sums.max,
216 share: (gated && row.attributed).then(share).flatten(),
217 clipped: gated.then_some(sums.peak > 1.0),
218 });
219 }
220 }
221 out
222 }
223}