nam_rs/models/
container.rs1use std::cmp::Ordering;
23
24use super::slimmable::SlimmableModel;
25use super::{NamModel, StaticModel};
26use crate::common::spsc::RT_STATUS_SLIMMABLE_RESET_FAILED;
27
28const CROSSFADE_DURATION_MS: f32 = 32.0;
29
30pub struct ContainerModel {
41 submodels: Vec<(f32, Box<StaticModel>)>,
42 active_index: usize,
43 pending_index: Option<usize>,
44 crossfade_elapsed: usize,
45 crossfade_duration: usize,
46 scratch_buffer: Vec<f32>,
47 sample_rate: u32,
48 max_buffer_size: usize,
49 prewarm_on_reset: bool,
50}
51
52impl ContainerModel {
53 pub fn new(
61 mut submodels: Vec<(f32, Box<StaticModel>)>,
62 sample_rate: u32,
63 ) -> anyhow::Result<Self> {
64 if submodels.is_empty() {
65 anyhow::bail!("ContainerModel: no submodels provided");
66 }
67
68 submodels.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
69
70 for (i, (max_value, _)) in submodels.iter().enumerate() {
71 if !max_value.is_finite() || *max_value < 0.0 {
72 anyhow::bail!(
73 "ContainerModel: submodel[{}] has invalid max_value={} \
74 (must be finite and >= 0.0)",
75 i,
76 max_value
77 );
78 }
79 }
80
81 for w in submodels.windows(2) {
82 if w[1].0 <= w[0].0 {
83 anyhow::bail!("ContainerModel: submodels must be sorted by ascending max_value");
84 }
85 }
86 let last = submodels
87 .last()
88 .ok_or_else(|| anyhow::anyhow!("ContainerModel: submodels list is empty"))?;
89 if last.0 < 1.0 {
90 anyhow::bail!("ContainerModel: last submodel max_value must be >= 1.0");
91 }
92
93 let active_index = submodels.len() - 1;
94 let crossfade_duration =
95 (CROSSFADE_DURATION_MS / 1000.0 * sample_rate as f32).round() as usize;
96 let default_buf = 4096usize;
97
98 let mut container = Self {
99 submodels,
100 active_index,
101 pending_index: None,
102 crossfade_elapsed: 0,
103 crossfade_duration: crossfade_duration.max(1),
104 scratch_buffer: vec![0.0f32; default_buf],
105 sample_rate,
106 max_buffer_size: default_buf,
107 prewarm_on_reset: true,
108 };
109
110 for (_, model) in &mut container.submodels {
111 model.set_max_buffer_size(default_buf)?;
112 }
113
114 container.prewarm(4096);
115
116 Ok(container)
117 }
118
119 pub fn submodels(&self) -> &[(f32, Box<StaticModel>)] {
121 &self.submodels
122 }
123
124 pub fn submodels_mut(&mut self) -> &mut [(f32, Box<StaticModel>)] {
126 &mut self.submodels
127 }
128
129 pub fn active_index(&self) -> usize {
131 self.active_index
132 }
133
134 pub fn set_active_index(&mut self, idx: usize) {
136 assert!(idx < self.submodels.len());
137 self.active_index = idx;
138 self.pending_index = None;
139 self.crossfade_elapsed = 0;
140 }
141
142 pub fn pending_index(&self) -> Option<usize> {
144 self.pending_index
145 }
146
147 pub fn is_crossfading(&self) -> bool {
149 self.pending_index.is_some()
150 }
151
152 pub fn sample_rate(&self) -> u32 {
154 self.sample_rate
155 }
156
157 pub(crate) fn active(&self) -> &StaticModel {
158 &self.submodels[self.active_index].1
159 }
160}
161
162impl NamModel for ContainerModel {
163 #[inline(always)]
164 fn process(&mut self, input: &[f32], output: &mut [f32]) {
165 let n = input.len();
166
167 if let Some(pending_idx) = self.pending_index {
168 let active_idx = self.active_index;
169
170 self.submodels[pending_idx]
171 .1
172 .process(input, &mut self.scratch_buffer[..n]);
173 self.submodels[active_idx].1.process(input, output);
174
175 let t = (self.crossfade_elapsed as f32 / self.crossfade_duration as f32).min(1.0);
176 unsafe {
178 crate::math::dsp::gain::crossfade_blend_mono(output, &self.scratch_buffer[..n], t);
179 }
180
181 self.crossfade_elapsed += n;
182 if self.crossfade_elapsed >= self.crossfade_duration {
183 self.active_index = pending_idx;
184 self.pending_index = None;
185 self.crossfade_elapsed = 0;
186 }
187 } else {
188 self.submodels[self.active_index].1.process(input, output);
189 }
190 }
191
192 fn prewarm(&mut self, num_samples: usize) {
193 for (_, model) in &mut self.submodels {
194 model.prewarm(num_samples);
195 }
196 }
197
198 fn reset(&mut self, sample_rate: u32, max_buffer_size: usize) -> anyhow::Result<()> {
199 self.sample_rate = sample_rate;
200 self.max_buffer_size = max_buffer_size;
201 self.scratch_buffer.resize(max_buffer_size, 0.0);
202 self.crossfade_duration =
203 (CROSSFADE_DURATION_MS / 1000.0 * sample_rate as f32).round() as usize;
204 for (_, model) in &mut self.submodels {
205 model.set_max_buffer_size(max_buffer_size)?;
206 }
207 self.submodels[self.active_index]
208 .1
209 .reset(sample_rate, max_buffer_size)
210 }
211
212 fn set_max_buffer_size(&mut self, max_buf: usize) -> anyhow::Result<()> {
213 self.max_buffer_size = max_buf;
214 self.scratch_buffer.resize(max_buf, 0.0);
215 for (_, model) in &mut self.submodels {
216 model.set_max_buffer_size(max_buf)?;
217 }
218 Ok(())
219 }
220
221 fn prewarm_samples(&self) -> usize {
222 self.active().prewarm_samples()
223 }
224
225 fn prewarm_on_reset(&self) -> bool {
226 self.prewarm_on_reset
227 }
228
229 fn set_prewarm_on_reset(&mut self, val: bool) {
230 self.prewarm_on_reset = val;
231 for (_, model) in &mut self.submodels {
232 model.set_prewarm_on_reset(val);
233 }
234 }
235}
236
237impl SlimmableModel for ContainerModel {
238 fn slimmable_breakpoints(&self) -> Vec<f64> {
239 if self.submodels.len() <= 1 {
240 return vec![];
241 }
242 self.submodels[..self.submodels.len() - 1]
243 .iter()
244 .map(|(max_value, _)| *max_value as f64)
245 .collect()
246 }
247
248 fn set_slimmable_size(
249 &mut self,
250 val: f32,
251 rt_status: Option<&crate::common::spsc::RtStatusFlags>,
252 ) {
253 let mut next = self.submodels.len() - 1;
254
255 for (i, (max_value, _)) in self.submodels.iter().enumerate() {
256 if val < *max_value {
257 next = i;
258 break;
259 }
260 }
261
262 if next == self.active_index && self.pending_index.is_none() {
263 return;
264 }
265
266 if self.pending_index == Some(next) {
267 return;
268 }
269
270 debug_assert!(
271 self.scratch_buffer.len() >= self.max_buffer_size,
272 "scratch_buffer capacity invariant violated: {} < {}",
273 self.scratch_buffer.len(),
274 self.max_buffer_size
275 );
276
277 if let Some(idx) = self.pending_index {
278 self.active_index = idx;
279 }
280
281 if let Err(_e) = self.submodels[next]
282 .1
283 .reset(self.sample_rate, self.max_buffer_size)
284 {
285 if let Some(s) = rt_status {
286 s.set_flag(RT_STATUS_SLIMMABLE_RESET_FAILED);
287 }
288 return;
289 }
290
291 self.pending_index = Some(next);
292 self.crossfade_elapsed = 0;
293 }
294}
295
296impl super::sealed::Sealed for ContainerModel {}
297
298#[cfg(test)]
299#[path = "container_test.rs"]
300mod tests;