Skip to content

Commit 7bb69f4

Browse files
authored
test(nam): guard model-name recall + document deferred automation (#246)
1 parent 28824e2 commit 7bb69f4

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

rustortion-core/src/preset/stage_config.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,50 @@ impl StageConfig {
197197
}
198198
}
199199
}
200+
201+
#[cfg(test)]
202+
mod tests {
203+
use super::*;
204+
205+
/// The plugin persists its chain as `Vec<StageConfig>` JSON in `chain_state`
206+
/// (nih-plug `#[persist]`), and a NAM stage stores its model BY NAME. This is
207+
/// the exact path that recalls a selected model when a DAW project reopens, so
208+
/// guard it: the model name (and the other NAM fields) must survive a JSON
209+
/// round-trip.
210+
#[test]
211+
fn nam_model_name_survives_chain_state_round_trip() {
212+
let chain = vec![
213+
StageConfig::Nam(NamConfig {
214+
model_name: Some("S-[AMP] Divine Sheep #04".to_owned()),
215+
input_gain_db: 3.0,
216+
output_gain_db: -2.0,
217+
mix: 0.75,
218+
bypassed: true,
219+
}),
220+
// A passthrough NAM stage (no model) must round-trip as `None`, not "".
221+
StageConfig::Nam(NamConfig::default()),
222+
];
223+
224+
let json = serde_json::to_string(&chain).expect("serialize chain_state");
225+
let restored: Vec<StageConfig> =
226+
serde_json::from_str(&json).expect("deserialize chain_state");
227+
228+
assert_eq!(restored.len(), 2);
229+
let StageConfig::Nam(cfg) = &restored[0] else {
230+
panic!("expected a NAM stage at index 0");
231+
};
232+
assert_eq!(cfg.model_name.as_deref(), Some("S-[AMP] Divine Sheep #04"));
233+
// Small fixed tolerance — `f32::EPSILON` is the spacing near 1.0, not a
234+
// general-purpose comparison bound.
235+
const TOL: f32 = 1e-6;
236+
assert!((cfg.input_gain_db - 3.0).abs() < TOL);
237+
assert!((cfg.output_gain_db - (-2.0)).abs() < TOL);
238+
assert!((cfg.mix - 0.75).abs() < TOL);
239+
assert!(cfg.bypassed);
240+
241+
let StageConfig::Nam(cfg) = &restored[1] else {
242+
panic!("expected a NAM stage at index 1");
243+
};
244+
assert_eq!(cfg.model_name, None);
245+
}
246+
}

rustortion-plugin/src/params.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,15 @@ impl Default for EqSlotParams {
481481
}
482482
}
483483

484+
/// Per-slot NAM params — intentionally **no** `model` parameter here.
485+
///
486+
/// The selected model is stored by NAME in `NamConfig.model_name` inside the
487+
/// serialized `chain_state`, so it persists with and recalls from the DAW project
488+
/// (the chain is rebuilt and each NAM stage resolves its model by name on load).
489+
/// Name-based recall is robust against the model list reordering. Exposing model
490+
/// choice as a host-automatable param is deferred — it needs a general slot<->stage
491+
/// param pump (and a background reload, since switching models loads a neural net
492+
/// off the audio thread), with no clean NAM-only slice.
484493
#[derive(Params)]
485494
pub struct NamSlotParams {
486495
#[id = "input_gain_db"]

0 commit comments

Comments
 (0)