1use std::collections::{BTreeMap, BTreeSet, VecDeque};
4
5use crate::buffer::Buffer;
6use crate::measure::envelope::rms;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum Unit {
10 Amplitude,
11 Hertz,
12 Q,
13 Decibels,
14 Seconds,
15 Radians,
16 Ratio,
17}
18
19impl Unit {
20 pub fn name(self) -> &'static str {
21 match self {
22 Unit::Amplitude => "amplitude",
23 Unit::Hertz => "hz",
24 Unit::Q => "q",
25 Unit::Decibels => "db",
26 Unit::Seconds => "seconds",
27 Unit::Radians => "radians",
28 Unit::Ratio => "ratio",
29 }
30 }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum SignalKind {
35 Audio,
36 Control(Unit),
37}
38
39impl SignalKind {
40 pub fn is_audio(self) -> bool {
41 self == SignalKind::Audio
42 }
43
44 pub fn unit(self) -> Unit {
45 match self {
46 SignalKind::Audio => Unit::Amplitude,
47 SignalKind::Control(u) => u,
48 }
49 }
50}
51
52#[derive(Clone, Debug, PartialEq)]
53pub struct LedgerEntry {
54 pub node: String,
55 pub channel: Option<usize>,
56 pub depth: usize,
57 pub kind: SignalKind,
58 pub rms: f64,
59 pub peak: f64,
60 pub min: f64,
61 pub max: f64,
62 pub share: Option<f64>,
65 pub clipped: Option<bool>,
68}
69
70pub fn attribute(
72 buffers: &BTreeMap<String, Buffer>,
73 contributed: &BTreeMap<String, Buffer>,
74 deps: &BTreeMap<String, Vec<String>>,
75 kinds: &BTreeMap<String, SignalKind>,
76 target: &str,
77 range: std::ops::Range<usize>,
78 depth_limit: usize,
79) -> Vec<LedgerEntry> {
80 let held = |path: &str| contributed.get(path).or_else(|| buffers.get(path));
81 let slice = |path: &str, c: usize| -> std::borrow::Cow<'_, [f64]> {
82 held(path).map_or(std::borrow::Cow::Borrowed(&[][..]), |b| {
83 b.window(c, range.clone())
84 })
85 };
86 let width = |path: &str| held(path).map_or(1, |b| b.width);
87 let share_of = |reader: &str, x: &[f64], c: usize| -> f64 {
89 let Some(buffer) = buffers.get(reader).filter(|b| b.width > 0) else {
90 return 0.0;
91 };
92 let y = buffer.window(c % buffer.width, range.clone());
93 let energy: f64 = y.iter().map(|&v| v * v).sum();
94 match energy > 0.0 {
95 true => x.iter().zip(y.iter()).map(|(a, b)| a * b).sum::<f64>() / energy,
96 false => 0.0,
97 }
98 };
99
100 let mut seen: BTreeSet<&str> = BTreeSet::new();
101 let mut queue: VecDeque<(&str, usize, &str)> = VecDeque::new();
102 let mut out = Vec::new();
103 queue.push_back((target, 0, target));
104 seen.insert(target);
105
106 while let Some((path, depth, reader)) = queue.pop_front() {
107 let kind = kinds.get(path).copied().unwrap_or(SignalKind::Audio);
109 let components = width(path);
110 let gated = kind.is_audio();
111 let attributed = path == target || contributed.contains_key(path);
112 for c in 0..components {
113 let buf = slice(path, c);
114 let node_rms = rms(&buf);
115 let peak = buf.iter().fold(0f64, |a, &x| a.max(x.abs()));
116 let min = buf.iter().fold(f64::INFINITY, |a, &x| a.min(x));
117 let max = buf.iter().fold(f64::NEG_INFINITY, |a, &x| a.max(x));
118 out.push(LedgerEntry {
119 node: path.to_string(),
120 channel: (components > 1).then_some(c),
121 depth,
122 kind,
123 rms: node_rms,
124 peak,
125 min,
126 max,
127 share: (gated && attributed).then(|| share_of(reader, &buf, c)),
128 clipped: gated.then_some(peak > 1.0),
129 });
130 }
131
132 if depth >= depth_limit {
133 continue;
134 }
135 for child in deps.get(path).map(Vec::as_slice).unwrap_or(&[]) {
136 if seen.insert(child.as_str()) {
137 let key = deps
138 .get_key_value(child.as_str())
139 .map(|(k, _)| k.as_str())
140 .unwrap_or(child.as_str());
141 queue.push_back((key, depth + 1, path));
142 }
143 }
144 }
145 out
146}