11use anyhow:: Result ;
2- use assert_no_alloc:: permit_alloc;
32use crossbeam:: channel:: { Receiver , Sender , bounded} ;
43use log:: { debug, error} ;
54
@@ -17,7 +16,10 @@ use crate::tuner::Tuner;
1716
1817pub struct PreparedIr {
1918 pub name : String ,
20- pub convolver : Convolver ,
19+ /// Boxed so it can be swapped into the cabinet on the RT thread without
20+ /// reallocating, and so the whole `PreparedIr` (old convolver + name) can
21+ /// be retired off the RT thread in one piece.
22+ pub convolver : Box < Convolver > ,
2123}
2224
2325pub enum EngineMessage {
@@ -35,7 +37,9 @@ pub enum EngineMessage {
3537 SetIrBypass ( bool ) ,
3638 SetIrGain ( f32 ) ,
3739 SetTunerEnabled ( bool ) ,
38- SetPitchShift ( i32 ) ,
40+ /// Carries a fully-constructed pitch shifter (built off the RT thread), or
41+ /// `None` to disable pitch shifting (the `0` semitones bypass case).
42+ SetPitchShift ( Option < Box < PitchShifter > > ) ,
3943 SetStageBypassed ( usize , bool ) ,
4044 SetSamplers ( Box < Samplers > ) ,
4145}
@@ -49,12 +53,14 @@ pub struct Engine {
4953 engine_receiver : Receiver < EngineMessage > ,
5054 /// Handle for sending arbitrary objects off the RT thread for deallocation.
5155 rt_drop : RtDropHandle ,
52- samplers : Samplers ,
56+ /// Boxed so swapping samplers (sample-rate / buffer changes) on the RT
57+ /// thread exchanges pointers and retires the old box directly.
58+ samplers : Box < Samplers > ,
5359 tuner : Option < Tuner > ,
5460 recorder : Option < Recorder > ,
5561 peak_meter : Option < PeakMeter > ,
5662 metronome : Option < Metronome > ,
57- pitch_shifter : Option < PitchShifter > ,
63+ pitch_shifter : Option < Box < PitchShifter > > ,
5864 input_highpass : Option < Box < dyn Stage > > ,
5965 input_lowpass : Option < Box < dyn Stage > > ,
6066 /// When true, skip tuner, peak meter, recorder, and metronome processing.
@@ -83,7 +89,7 @@ impl Engine {
8389 ir_cabinet,
8490 engine_receiver,
8591 rt_drop,
86- samplers,
92+ samplers : Box :: new ( samplers ) ,
8793 tuner : Some ( tuner) ,
8894 recorder : None ,
8995 peak_meter : Some ( peak_meter) ,
@@ -115,7 +121,7 @@ impl Engine {
115121 ir_cabinet,
116122 engine_receiver,
117123 rt_drop : rt_drop_handle,
118- samplers,
124+ samplers : Box :: new ( samplers ) ,
119125 tuner : None ,
120126 recorder : None ,
121127 peak_meter : None ,
@@ -162,27 +168,21 @@ impl Engine {
162168 }
163169
164170 if let Some ( ref mut shifter) = self . pitch_shifter {
165- // FIXME(no_alloc): PitchShifter::process_block uses Vec scratch
166- // buffers internally — see audio/pitch_shifter.rs.
167- permit_alloc ( || shifter. process_block ( output) ) ;
171+ shifter. process_block ( output) ;
168172 }
169173
170174 if let Some ( ref mut cab) = self . ir_cabinet {
171175 cab. process_block ( output) ;
172176 }
173177
174178 if let Some ( ref mut peak_meter) = self . peak_meter {
175- // FIXME(no_alloc): PeakMeter::process does Arc::new(PeakMeterInfo)
176- // every block — see audio/peak_meter.rs:62.
177- permit_alloc ( || peak_meter. process ( output) ) ;
179+ peak_meter. process ( output) ;
178180 }
179181
180182 if !self . lightweight
181183 && let Some ( recorder) = self . recorder . as_mut ( )
182184 {
183- // FIXME(no_alloc): Recorder::record_block does Vec::with_capacity
184- // per block — see audio/recorder.rs:47.
185- permit_alloc ( || recorder. record_block ( output) ) ?;
185+ recorder. record_block ( output) ;
186186 }
187187
188188 Ok ( ( ) )
@@ -263,8 +263,14 @@ impl Engine {
263263 }
264264 }
265265 EngineMessage :: AddStage ( idx, stage) => {
266- self . chain . insert_stage ( idx, stage) ;
267- debug ! ( "Added stage at index {idx}" ) ;
266+ if let Some ( rejected) = self . chain . insert_stage ( idx, stage) {
267+ // Chain is at its reserved capacity. Retire the rejected
268+ // stage off the RT thread rather than dropping (freeing)
269+ // it here. The UI caps stage count, so this is a backstop.
270+ self . rt_drop . retire ( rejected) ;
271+ } else {
272+ debug ! ( "Added stage at index {idx}" ) ;
273+ }
268274 }
269275 EngineMessage :: RemoveStage ( idx) => {
270276 if let Some ( old) = self . chain . remove_stage ( idx) {
@@ -286,16 +292,26 @@ impl Engine {
286292 }
287293 }
288294 EngineMessage :: SetInputFilters ( hp, lp) => {
289- self . input_highpass = hp;
290- self . input_lowpass = lp;
295+ // Retire the previous filters off the RT thread instead of
296+ // dropping them here on direct assignment.
297+ if let Some ( old) = std:: mem:: replace ( & mut self . input_highpass , hp) {
298+ self . rt_drop . retire ( old) ;
299+ }
300+ if let Some ( old) = std:: mem:: replace ( & mut self . input_lowpass , lp) {
301+ self . rt_drop . retire ( old) ;
302+ }
291303 debug ! ( "Updated input filters" ) ;
292304 }
293- EngineMessage :: SwapIrConvolver ( prepared) => {
305+ EngineMessage :: SwapIrConvolver ( mut prepared) => {
294306 if let Some ( ref mut cab) = self . ir_cabinet {
295307 debug ! ( "IR convolver swapped: {}" , prepared. name) ;
296- let old = cab. swap_convolver ( prepared. convolver ) ;
297- permit_alloc ( || self . rt_drop . retire ( Box :: new ( old) ) ) ;
308+ // Swap the new convolver in; `prepared` is left holding
309+ // the old convolver. Retire the whole `PreparedIr` (old
310+ // convolver + name `String`) off the RT thread so
311+ // nothing deallocates here.
312+ cab. swap_convolver ( & mut prepared. convolver ) ;
298313 }
314+ self . rt_drop . retire ( prepared) ;
299315 }
300316 EngineMessage :: ClearIr => {
301317 if let Some ( ref mut cab) = self . ir_cabinet {
@@ -326,12 +342,12 @@ impl Engine {
326342 EngineMessage :: StopRecording => {
327343 self . handle_stop_recording ( ) ;
328344 }
329- EngineMessage :: SetPitchShift ( semitones ) => {
330- self . handle_pitch_shift ( semitones ) ;
345+ EngineMessage :: SetPitchShift ( shifter ) => {
346+ self . handle_pitch_shift ( shifter ) ;
331347 }
332348 EngineMessage :: SetSamplers ( new_samplers) => {
333- let old = std:: mem:: replace ( & mut self . samplers , * new_samplers) ;
334- permit_alloc ( || self . rt_drop . retire ( Box :: new ( old) ) ) ;
349+ let old = std:: mem:: replace ( & mut self . samplers , new_samplers) ;
350+ self . rt_drop . retire ( old) ;
335351 debug ! ( "Samplers swapped" ) ;
336352 }
337353 }
@@ -364,19 +380,15 @@ impl Engine {
364380 self . recorder = None ;
365381 }
366382
367- fn handle_pitch_shift ( & mut self , semitones : i32 ) {
368- if semitones == 0 {
369- self . pitch_shifter = None ;
370- debug ! ( "Pitch shift disabled (bypass)" ) ;
371- } else if let Some ( ref mut shifter) = self . pitch_shifter {
372- shifter. set_semitones ( semitones as f32 ) ;
373- debug ! ( "Pitch shift set to {semitones} semitones" ) ;
374- } else {
375- // FIXME(no_alloc): PitchShifter::new allocates FFT scratch buffers
376- // — see audio/pitch_shifter.rs.
377- self . pitch_shifter = Some ( permit_alloc ( || PitchShifter :: new ( semitones as f32 ) ) ) ;
378- debug ! ( "Pitch shift set to {semitones} semitones" ) ;
383+ fn handle_pitch_shift ( & mut self , shifter : Option < Box < PitchShifter > > ) {
384+ // The shifter (if any) is constructed off the RT thread in
385+ // `EngineHandle::set_pitch_shift`; here we just swap it in and retire
386+ // the previous one off the RT thread.
387+ let old = std:: mem:: replace ( & mut self . pitch_shifter , shifter) ;
388+ if let Some ( old) = old {
389+ self . rt_drop . retire ( old) ;
379390 }
391+ debug ! ( "Pitch shifter updated" ) ;
380392 }
381393}
382394
@@ -448,8 +460,14 @@ impl EngineHandle {
448460 }
449461
450462 pub fn set_pitch_shift ( & self , semitones : i32 ) {
451- let update = EngineMessage :: SetPitchShift ( semitones) ;
452- self . send ( update) ;
463+ // Construct the pitch shifter here (GUI thread) so the RT thread never
464+ // allocates its FFT plans / scratch buffers. `0` semitones == bypass.
465+ let shifter = if semitones == 0 {
466+ None
467+ } else {
468+ Some ( Box :: new ( PitchShifter :: new ( semitones as f32 ) ) )
469+ } ;
470+ self . send ( EngineMessage :: SetPitchShift ( shifter) ) ;
453471 }
454472
455473 pub fn set_stage_bypassed ( & self , idx : usize , bypassed : bool ) {
@@ -461,8 +479,13 @@ impl EngineHandle {
461479 self . send ( update) ;
462480 }
463481
464- pub fn start_recording ( & self , sample_rate : usize , output_dir : & str ) -> Result < ( ) > {
465- let recorder = Recorder :: new ( sample_rate as u32 , output_dir) ?;
482+ pub fn start_recording (
483+ & self ,
484+ sample_rate : usize ,
485+ output_dir : & str ,
486+ max_block_samples : usize ,
487+ ) -> Result < ( ) > {
488+ let recorder = Recorder :: new ( sample_rate as u32 , output_dir, max_block_samples) ?;
466489
467490 let update = EngineMessage :: StartRecording ( recorder) ;
468491 self . send ( update) ;
0 commit comments