conformal_component/parameters/utils/
per_sample.rs1use super::super::{
2 EnumBufferState, NumericBufferState, PiecewiseLinearCurve, PiecewiseLinearCurvePoint,
3 SwitchBufferState, TimedEnumValues, TimedSwitchValues, TimedValue,
4};
5
6fn piecewise_linear_curve_per_sample<
7 I: IntoIterator<Item = PiecewiseLinearCurvePoint, IntoIter: Clone>,
8>(
9 curve: PiecewiseLinearCurve<I>,
10) -> impl Iterator<Item = f32> + Clone {
11 let buffer_size = curve.buffer_size();
12 let mut i = curve.into_iter();
13 let mut next = i.next();
14 let mut last = None;
15 let mut last_sample_offset = 0;
16 (0..buffer_size).map(move |idx| {
17 if let Some(PiecewiseLinearCurvePoint {
18 sample_offset,
19 value,
20 }) = next
21 {
22 if sample_offset == idx {
23 last = Some(value);
24 last_sample_offset = sample_offset;
25 next = i.next();
26 value
27 } else {
28 let delta = value - last.unwrap();
30
31 #[allow(clippy::cast_precision_loss)]
34 {
35 let delta_per_sample = delta / ((sample_offset - last_sample_offset) as f32);
36
37 last.unwrap() + delta_per_sample * ((idx - last_sample_offset) as f32)
38 }
39 }
40 } else {
41 last.unwrap()
43 }
44 })
45}
46
47#[doc(hidden)]
48pub fn decompose_numeric<I: IntoIterator<Item = PiecewiseLinearCurvePoint, IntoIter: Clone>>(
49 state: NumericBufferState<I>,
50) -> (f32, Option<impl Iterator<Item = f32> + Clone>) {
51 match state {
52 NumericBufferState::Constant(v) => (v, None),
53 NumericBufferState::PiecewiseLinear(c) => (0.0, Some(piecewise_linear_curve_per_sample(c))),
54 }
55}
56
57pub fn numeric_per_sample<I: IntoIterator<Item = PiecewiseLinearCurvePoint, IntoIter: Clone>>(
62 state: NumericBufferState<I>,
63) -> impl Iterator<Item = f32> + Clone {
64 match state {
65 NumericBufferState::Constant(v) => itertools::Either::Left(core::iter::repeat(v)),
66 NumericBufferState::PiecewiseLinear(c) => {
67 itertools::Either::Right(piecewise_linear_curve_per_sample(c))
68 }
69 }
70}
71
72#[allow(clippy::missing_panics_doc)] fn timed_enum_per_sample<I: IntoIterator<Item = TimedValue<u32>, IntoIter: Clone>>(
74 values: TimedEnumValues<I>,
75) -> impl Iterator<Item = u32> + Clone {
76 let buffer_size = values.buffer_size();
77 let mut i = values.into_iter();
78 let mut next = i.next();
79 let mut last = None;
80 (0..buffer_size).map(move |idx| {
81 if let Some(TimedValue {
82 sample_offset,
83 value,
84 }) = next
85 {
86 if sample_offset == idx {
87 last = Some(value);
88 next = i.next();
89 value
90 } else {
91 last.unwrap()
93 }
94 } else {
95 last.unwrap()
97 }
98 })
99}
100
101#[doc(hidden)]
102pub fn decompose_enum<I: IntoIterator<Item = TimedValue<u32>, IntoIter: Clone>>(
103 state: EnumBufferState<I>,
104) -> (u32, Option<impl Iterator<Item = u32> + Clone>) {
105 match state {
106 EnumBufferState::Constant(v) => (v, None),
107 EnumBufferState::Varying(c) => (0, Some(timed_enum_per_sample(c))),
108 }
109}
110
111pub fn enum_per_sample<I: IntoIterator<Item = TimedValue<u32>, IntoIter: Clone>>(
116 state: EnumBufferState<I>,
117) -> impl Iterator<Item = u32> + Clone {
118 match state {
119 EnumBufferState::Constant(v) => itertools::Either::Left(core::iter::repeat(v)),
120 EnumBufferState::Varying(c) => itertools::Either::Right(timed_enum_per_sample(c)),
121 }
122}
123
124#[allow(clippy::missing_panics_doc)] fn timed_switch_per_sample<I: IntoIterator<Item = TimedValue<bool>, IntoIter: Clone>>(
126 values: TimedSwitchValues<I>,
127) -> impl Iterator<Item = bool> + Clone {
128 let buffer_size = values.buffer_size();
129 let mut i = values.into_iter();
130 let mut next = i.next();
131 let mut last = None;
132 (0..buffer_size).map(move |idx| {
133 if let Some(TimedValue {
134 sample_offset,
135 value,
136 }) = next
137 {
138 if sample_offset == idx {
139 last = Some(value);
140 next = i.next();
141 value
142 } else {
143 last.unwrap()
145 }
146 } else {
147 last.unwrap()
149 }
150 })
151}
152
153#[doc(hidden)]
154pub fn decompose_switch<I: IntoIterator<Item = TimedValue<bool>, IntoIter: Clone>>(
155 state: SwitchBufferState<I>,
156) -> (bool, Option<impl Iterator<Item = bool> + Clone>) {
157 match state {
158 SwitchBufferState::Constant(v) => (v, None),
159 SwitchBufferState::Varying(c) => (false, Some(timed_switch_per_sample(c))),
160 }
161}
162
163pub fn switch_per_sample<I: IntoIterator<Item = TimedValue<bool>, IntoIter: Clone>>(
168 state: SwitchBufferState<I>,
169) -> impl Iterator<Item = bool> + Clone {
170 match state {
171 SwitchBufferState::Constant(v) => itertools::Either::Left(core::iter::repeat(v)),
172 SwitchBufferState::Varying(c) => itertools::Either::Right(timed_switch_per_sample(c)),
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use crate::audio::all_approx_eq;
179
180 use super::super::super::{
181 PiecewiseLinearCurve, PiecewiseLinearCurvePoint, TimedEnumValues, TimedSwitchValues,
182 TimedValue,
183 };
184 use super::{
185 piecewise_linear_curve_per_sample, timed_enum_per_sample, timed_switch_per_sample,
186 };
187
188 const TEST_EPSILON: f32 = 1e-7;
189
190 #[test]
191 fn piecewise_linear_curve_per_sample_basics() {
192 let vals = piecewise_linear_curve_per_sample(
193 PiecewiseLinearCurve::new(
194 (&[
195 PiecewiseLinearCurvePoint {
196 sample_offset: 0,
197 value: 0.0,
198 },
199 PiecewiseLinearCurvePoint {
200 sample_offset: 5,
201 value: 5.0,
202 },
203 PiecewiseLinearCurvePoint {
204 sample_offset: 7,
205 value: 5.0,
206 },
207 PiecewiseLinearCurvePoint {
208 sample_offset: 8,
209 value: 10.0,
210 },
211 ])
212 .iter()
213 .cloned(),
214 10,
215 0.0..=10.0,
216 )
217 .unwrap(),
218 )
219 .collect::<Vec<_>>();
220 assert!(all_approx_eq(
221 vals.iter().copied(),
222 ([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 10.0, 10.0])
223 .iter()
224 .copied(),
225 TEST_EPSILON
226 ));
227 }
228
229 #[test]
230 fn timed_enum_per_sample_basics() {
231 let vals = timed_enum_per_sample(
232 TimedEnumValues::new(
233 (&[
234 TimedValue {
235 sample_offset: 0,
236 value: 0,
237 },
238 TimedValue {
239 sample_offset: 7,
240 value: 2,
241 },
242 TimedValue {
243 sample_offset: 8,
244 value: 3,
245 },
246 ])
247 .iter()
248 .cloned(),
249 10,
250 0..4,
251 )
252 .unwrap(),
253 )
254 .collect::<Vec<_>>();
255 assert!(
256 vals.iter()
257 .copied()
258 .zip(([0, 0, 0, 0, 0, 0, 0, 2, 3, 3]).iter().copied())
259 .all(|(a, b)| a == b)
260 );
261 }
262
263 #[test]
264 fn timed_switch_per_sample_basics() {
265 let vals = timed_switch_per_sample(
266 TimedSwitchValues::new(
267 (&[
268 TimedValue {
269 sample_offset: 0,
270 value: false,
271 },
272 TimedValue {
273 sample_offset: 7,
274 value: true,
275 },
276 TimedValue {
277 sample_offset: 8,
278 value: false,
279 },
280 ])
281 .iter()
282 .cloned(),
283 10,
284 )
285 .unwrap(),
286 )
287 .collect::<Vec<_>>();
288 assert!(
289 vals.iter()
290 .copied()
291 .zip(
292 ([
293 false, false, false, false, false, false, false, true, false, false
294 ])
295 .iter()
296 .copied()
297 )
298 .all(|(a, b)| a == b)
299 );
300 }
301}