@@ -80,6 +80,15 @@ impl SpectrogramConfig {
8080 }
8181}
8282
83+ enum Transforms {
84+ Reassigned {
85+ analysis : Arc < dyn Fft < f32 > > ,
86+ hilbert_forward : Arc < dyn Fft < f32 > > ,
87+ hilbert_inverse : Arc < dyn Fft < f32 > > ,
88+ } ,
89+ Classic ( Arc < dyn RealToComplex < f32 > > ) ,
90+ }
91+
8392#[ derive( Default ) ]
8493struct ReassignmentBuffers {
8594 derivative_window : Vec < f32 > ,
@@ -168,10 +177,7 @@ pub struct SpectrogramUpdate {
168177
169178pub struct SpectrogramProcessor {
170179 config : SpectrogramConfig ,
171- fft : Arc < dyn Fft < f32 > > ,
172- classic_fft : Arc < dyn RealToComplex < f32 > > ,
173- hilbert_fft : Arc < dyn Fft < f32 > > ,
174- hilbert_ifft : Arc < dyn Fft < f32 > > ,
180+ transforms : Option < Transforms > ,
175181 fft_size : usize ,
176182 window : Arc < [ f32 ] > ,
177183 real : Vec < f32 > ,
@@ -190,15 +196,9 @@ pub struct SpectrogramProcessor {
190196impl SpectrogramProcessor {
191197 pub fn new ( mut cfg : SpectrogramConfig ) -> Self {
192198 cfg. normalize ( ) ;
193- let mut planner = FftPlanner :: new ( ) ;
194- let placeholder_fft = planner. plan_fft_forward ( 1024 ) ;
195- let classic_fft = RealFftPlanner :: new ( ) . plan_fft_forward ( 1024 ) ;
196- let mut processor = Self {
199+ Self {
197200 config : cfg,
198- fft : placeholder_fft. clone ( ) ,
199- classic_fft,
200- hilbert_fft : placeholder_fft. clone ( ) ,
201- hilbert_ifft : placeholder_fft,
201+ transforms : None ,
202202 fft_size : 0 ,
203203 window : Arc :: from ( [ ] ) ,
204204 real : Vec :: new ( ) ,
@@ -212,9 +212,7 @@ impl SpectrogramProcessor {
212212 pending_skip_samples : 0 ,
213213 audio_last_nonzero : None ,
214214 reset : true ,
215- } ;
216- processor. rebuild_fft ( ) ;
217- processor
215+ }
218216 }
219217
220218 pub fn config ( & self ) -> SpectrogramConfig {
@@ -228,6 +226,12 @@ impl SpectrogramProcessor {
228226 self . reset = true ;
229227 }
230228
229+ pub ( in crate :: visuals) fn prepare ( & mut self ) {
230+ if self . transforms . is_none ( ) {
231+ self . rebuild_fft ( ) ;
232+ }
233+ }
234+
231235 fn hilbert_len_for ( window_size : usize ) -> usize {
232236 ( window_size * 2 ) . next_power_of_two ( ) . max ( 2 )
233237 }
@@ -239,12 +243,14 @@ impl SpectrogramProcessor {
239243 let use_reassignment = self . config . use_reassignment ;
240244 let active_len = if use_reassignment { hilbert_len } else { self . fft_size } ;
241245 let mut planner = FftPlanner :: new ( ) ;
242- self . fft = planner. plan_fft_forward ( self . fft_size ) ;
243- self . classic_fft = RealFftPlanner :: new ( ) . plan_fft_forward ( self . fft_size ) ;
244- ( self . hilbert_fft , self . hilbert_ifft ) = if use_reassignment {
245- ( planner. plan_fft_forward ( hilbert_len) , planner. plan_fft_inverse ( hilbert_len) )
246+ let transforms = if use_reassignment {
247+ Transforms :: Reassigned {
248+ analysis : planner. plan_fft_forward ( self . fft_size ) ,
249+ hilbert_forward : planner. plan_fft_forward ( hilbert_len) ,
250+ hilbert_inverse : planner. plan_fft_inverse ( hilbert_len) ,
251+ }
246252 } else {
247- ( self . fft . clone ( ) , self . fft . clone ( ) )
253+ Transforms :: Classic ( RealFftPlanner :: new ( ) . plan_fft_forward ( self . fft_size ) )
248254 } ;
249255 self . window = window_coefficients ( self . config . window , window_size) ;
250256 let bin_count = self . fft_size / 2 + 1 ;
@@ -253,15 +259,19 @@ impl SpectrogramProcessor {
253259 resize_trim ( & mut self . real , active_len, 0.0 ) ;
254260 resize_trim ( & mut self . hilbert_buf , reassigned_len, Complex32 :: ZERO ) ;
255261 resize_trim ( & mut self . spectrum , classic_bin_count, Complex32 :: ZERO ) ;
256- let scratch_len = if use_reassignment {
257- self . fft
262+ let scratch_len = match & transforms {
263+ Transforms :: Reassigned {
264+ analysis,
265+ hilbert_forward,
266+ hilbert_inverse,
267+ } => analysis
258268 . get_inplace_scratch_len ( )
259- . max ( self . hilbert_fft . get_inplace_scratch_len ( ) )
260- . max ( self . hilbert_ifft . get_inplace_scratch_len ( ) )
261- } else {
262- self . classic_fft . get_scratch_len ( )
269+ . max ( hilbert_forward. get_inplace_scratch_len ( ) )
270+ . max ( hilbert_inverse. get_inplace_scratch_len ( ) ) ,
271+ Transforms :: Classic ( fft) => fft. get_scratch_len ( ) ,
263272 } ;
264273 resize_trim ( & mut self . scratch , scratch_len, Complex32 :: ZERO ) ;
274+ self . transforms = Some ( transforms) ;
265275 self . bin_norm = compute_fft_bin_normalization ( & self . window , self . fft_size ) ;
266276 self . reassigned_power_scale = if use_reassignment {
267277 let inv_hilbert_len = ( hilbert_len as f32 ) . recip ( ) ;
@@ -325,18 +335,26 @@ impl SpectrogramProcessor {
325335 }
326336
327337 let col = if reassignment_enabled {
338+ let Some ( Transforms :: Reassigned {
339+ analysis,
340+ hilbert_forward,
341+ hilbert_inverse,
342+ } ) = & self . transforms
343+ else {
344+ unreachable ! ( "reassignment transforms" )
345+ } ;
328346 copy_from_deque ( & mut self . real [ ..read_len] , & self . audio_buffer ) ;
329347 // Use an analytic signal so low-frequency bins are not polluted
330348 // by the negative-frequency mirror of the windowed real signal.
331349 hilbert_transform (
332350 & self . real [ ..read_len] ,
333351 & mut self . hilbert_buf ,
334- & * self . hilbert_fft ,
335- & * self . hilbert_ifft ,
352+ & * * hilbert_forward ,
353+ & * * hilbert_inverse ,
336354 & mut self . scratch ,
337355 ) ;
338356 let analytic = & self . hilbert_buf [ center_offset..center_offset + window_size] ;
339- let fft = & * self . fft ;
357+ let fft = & * * analysis ;
340358 let r = & mut self . reassign ;
341359 let ( base, auxiliary) = r. spectra . split_at_mut ( self . fft_size ) ;
342360 let ( derivative, time_weighted) = auxiliary. split_at_mut ( self . fft_size ) ;
@@ -357,8 +375,10 @@ impl SpectrogramProcessor {
357375 & self . window ,
358376 ) ;
359377 self . real [ window_size..] . fill ( 0.0 ) ;
360- if self
361- . classic_fft
378+ let Some ( Transforms :: Classic ( fft) ) = & self . transforms else {
379+ unreachable ! ( "classic transform" )
380+ } ;
381+ if fft
362382 . process_with_scratch (
363383 & mut self . real ,
364384 & mut self . spectrum ,
@@ -501,6 +521,7 @@ impl SpectrogramProcessor {
501521 self . audio_last_nonzero = None ;
502522 self . reset = true ;
503523 }
524+ self . prepare ( ) ;
504525 self . push_audio ( block) ;
505526 let cols = self . process_ready_windows ( ) ;
506527 let bin_count = self . fft_size / 2 + 1 ;
@@ -523,6 +544,7 @@ impl SpectrogramProcessor {
523544 pub fn update_config ( & mut self , mut cfg : SpectrogramConfig ) {
524545 cfg. normalize ( ) ;
525546 let prev = self . config ;
547+ let prepared = self . transforms . is_some ( ) ;
526548 self . config = cfg;
527549
528550 let rate_changed = prev. sample_rate != cfg. sample_rate ;
@@ -532,7 +554,7 @@ impl SpectrogramProcessor {
532554 || prev. use_reassignment != cfg. use_reassignment
533555 || rate_changed;
534556
535- if rebuild {
557+ if rebuild && prepared {
536558 self . rebuild_fft ( ) ;
537559 if rate_changed {
538560 self . audio_buffer . clear ( ) ;
@@ -789,13 +811,16 @@ mod tests {
789811
790812 #[ test]
791813 fn classic_retention_budget_uses_packed_column_width ( ) {
792- let processor = SpectrogramProcessor :: new ( SpectrogramConfig {
814+ let mut processor = SpectrogramProcessor :: new ( SpectrogramConfig {
793815 fft_size : 16_384 ,
794816 zero_padding_factor : 32 ,
795817 history_length : MAX_SPECTROGRAM_HISTORY_COLUMNS ,
796818 use_reassignment : false ,
797819 ..Default :: default ( )
798820 } ) ;
821+ assert ! ( processor. transforms. is_none( ) ) ;
822+ assert ! ( processor. real. is_empty( ) ) ;
823+ processor. prepare ( ) ;
799824 let bins = processor. fft_size / 2 + 1 ;
800825 let packed_stride = bins. div_ceil ( 2 ) * std:: mem:: size_of :: < u32 > ( ) ;
801826
@@ -808,6 +833,7 @@ mod tests {
808833 #[ test]
809834 fn fft_rebuild_keeps_newest_pending_audio ( ) {
810835 let mut p = SpectrogramProcessor :: new ( cfg ( 64 , 16 , false ) ) ;
836+ p. prepare ( ) ;
811837 let samples: Vec < _ > = ( 0 ..200 ) . map ( |i| i as f32 ) . collect ( ) ;
812838 p. push_audio ( & AudioBlock :: new ( & samples, 1 , DEFAULT_SAMPLE_RATE ) ) ;
813839 let mut next = p. config ( ) ;
0 commit comments