@@ -36,6 +36,24 @@ impl NamStage {
3636 sample_rate_mismatch : false ,
3737 }
3838 }
39+
40+ /// Passthrough used when the model's native rate mismatches the engine rate.
41+ /// Carries the real native rate so the UI/params can report the bypass reason.
42+ const fn bypassed_for_mismatch (
43+ input_gain : f32 ,
44+ output_gain : f32 ,
45+ mix : f32 ,
46+ native_sample_rate : f32 ,
47+ ) -> Self {
48+ Self {
49+ wavenet : None ,
50+ input_gain,
51+ output_gain,
52+ mix,
53+ native_sample_rate,
54+ sample_rate_mismatch : true ,
55+ }
56+ }
3957}
4058
4159impl Stage for NamStage {
@@ -135,11 +153,18 @@ impl NamConfig {
135153 } ;
136154
137155 let native_sample_rate = model. sample_rate ( ) as f32 ;
138- let sample_rate_mismatch = ( native_sample_rate - sample_rate) . abs ( ) > 1.0 ;
139- if sample_rate_mismatch {
156+ if ( native_sample_rate - sample_rate) . abs ( ) > 1.0 {
157+ // Resampling is intentionally avoided (too expensive on the RT path), so a
158+ // rate mismatch bypasses the model entirely: pass the dry signal through.
140159 warn ! (
141160 "NAM model '{name}' native rate {native_sample_rate} Hz differs from engine \
142- rate {sample_rate} Hz; tone may be affected"
161+ rate {sample_rate} Hz; bypassing model (dry passthrough)"
162+ ) ;
163+ return NamStage :: bypassed_for_mismatch (
164+ input_gain,
165+ output_gain,
166+ mix,
167+ native_sample_rate,
143168 ) ;
144169 }
145170
@@ -150,7 +175,8 @@ impl NamConfig {
150175 output_gain,
151176 mix,
152177 native_sample_rate,
153- sample_rate_mismatch,
178+ // Rates match (mismatch returned early above).
179+ sample_rate_mismatch : false ,
154180 } ,
155181 Err ( e) => {
156182 warn ! ( "Failed to build NAM model '{name}': {e}; using passthrough" ) ;
@@ -173,6 +199,25 @@ mod tests {
173199 }
174200 }
175201
202+ #[ test]
203+ fn mismatch_bypass_is_dry_passthrough ( ) {
204+ // A rate-mismatch stage is built without a WaveNet but records the real native
205+ // rate and the mismatch flag. We construct it directly here because building a
206+ // real `WaveNet` requires loading a `.nam` model into the registry, which unit
207+ // tests can't do; this still verifies the RT-path passthrough contract and the
208+ // params reported to the UI.
209+ let mut stage =
210+ NamStage :: bypassed_for_mismatch ( db_to_lin ( 6.0 ) , db_to_lin ( -3.0 ) , 0.5 , 44_100.0 ) ;
211+
212+ // No model runs: output is the dry input, with no gain or mix applied.
213+ for x in [ -1.0 , 0.0 , 0.25 , 0.9 ] {
214+ assert_eq ! ( stage. process( x) , x) ;
215+ }
216+
217+ assert ! ( ( stage. get_parameter( "native_sample_rate" ) . unwrap( ) - 44_100.0 ) . abs( ) < 1e-3 ) ;
218+ assert ! ( ( stage. get_parameter( "sample_rate_mismatch" ) . unwrap( ) - 1.0 ) . abs( ) < 1e-6 ) ;
219+ }
220+
176221 #[ test]
177222 fn gain_and_mix_round_trip ( ) {
178223 let mut stage = NamConfig :: default ( ) . to_stage ( 48_000.0 ) ;
0 commit comments