@@ -137,12 +137,36 @@ pub fn spawn_dsp_thread(
137137 let mut complex_buf = vec ! [ Complex { re: 0.0f32 , im: 0.0f32 } ; window_size] ;
138138 let mut magnitudes = vec ! [ 0.0f32 ; window_size / 2 ] ;
139139
140+ // Precompute log-spaced bin interpolation mapping for GPU spectrum
141+ let resolution = sample_rate as f32 / window_size as f32 ; // Hz per FFT bin
142+ let min_freq = 20.0f32 ;
143+ let max_f = max_frequency. max ( min_freq * 2.0 ) ;
144+ let num_bins = 1024 ;
145+ let nyquist = window_size / 2 ;
146+
147+ struct BinMapping {
148+ i0 : usize ,
149+ i1 : usize ,
150+ frac : f32 ,
151+ }
152+ let mut log_bin_mappings = Vec :: with_capacity ( num_bins) ;
153+ for i in 0 ..num_bins {
154+ let t = i as f32 / num_bins as f32 ;
155+ let freq = min_freq * ( max_f / min_freq) . powf ( t) ;
156+ let idx = freq / resolution;
157+ let idx_clamped = idx. clamp ( 0.0 , ( nyquist - 1 ) as f32 ) ;
158+ let i0 = idx_clamped. floor ( ) as usize ;
159+ let i1 = ( i0 + 1 ) . min ( nyquist - 1 ) ;
160+ let frac = idx_clamped - i0 as f32 ;
161+ log_bin_mappings. push ( BinMapping { i0, i1, frac } ) ;
162+ }
163+
140164 let mut last_waveform_push = Instant :: now ( ) ;
141165
142166 while let Ok ( msg) = rx. recv ( ) {
143167 let fft_start = Instant :: now ( ) ;
144168
145- // Apply Hann window and prepare complex input
169+ // 1. Apply Hann window and prepare complex input for mono channel
146170 for i in 0 ..window_size {
147171 let sample = * msg. audio_data . get ( i) . unwrap_or ( & 0.0 ) ;
148172 complex_buf[ i] = Complex { re : sample * hann_window[ i] , im : 0.0 } ;
@@ -157,13 +181,7 @@ pub fn spawn_dsp_thread(
157181 magnitudes[ i] = complex_buf[ i] . norm ( ) / n_sqrt;
158182 }
159183
160- // Logarithmic binning into 1024 display bins
161- let resolution = sample_rate as f32 / window_size as f32 ; // Hz per FFT bin
162- let min_freq = 20.0f32 ;
163- let max_f = max_frequency. max ( min_freq * 2.0 ) ;
164- let num_bins = 1024 ;
165- let nyquist = window_size / 2 ;
166-
184+ // Logarithmic binning into 1024 display bins (mono)
167185 binned_data. fill ( 0.0 ) ;
168186 for i in 0 ..num_bins {
169187 let freq_start = min_freq * ( max_f / min_freq) . powf ( i as f32 / num_bins as f32 ) ;
@@ -191,6 +209,33 @@ pub fn spawn_dsp_thread(
191209
192210 binned_data[ i] = ( max_val * 100.0 ) . clamp ( 0.0 , 100.0 ) ;
193211 }
212+
213+ // 2. Process multi-channel FFT for GPU visualizers (always run on background thread)
214+ let mut gpu_spectrum = vec ! [ 0.0f32 ; 32 * 1024 * 2 ] ;
215+ let num_channels = msg. channel_audio_data . len ( ) . min ( 32 ) ;
216+ for c in 0 ..num_channels {
217+ let channel_data = & msg. channel_audio_data [ c] ;
218+ for i in 0 ..window_size {
219+ let sample = * channel_data. get ( i) . unwrap_or ( & 0.0 ) ;
220+ complex_buf[ i] = Complex { re : sample * hann_window[ i] , im : 0.0 } ;
221+ }
222+ fft. process ( & mut complex_buf) ;
223+
224+ for ( i, mapping) in log_bin_mappings. iter ( ) . enumerate ( ) {
225+ let c0 = complex_buf[ mapping. i0 ] ;
226+ let c1 = complex_buf[ mapping. i1 ] ;
227+ let re = c0. re * ( 1.0 - mapping. frac ) + c1. re * mapping. frac ;
228+ let im = c0. im * ( 1.0 - mapping. frac ) + c1. im * mapping. frac ;
229+
230+ let norm_re = re / n_sqrt;
231+ let norm_im = im / n_sqrt;
232+
233+ let out_idx = c * 1024 * 2 + i * 2 ;
234+ gpu_spectrum[ out_idx] = norm_re;
235+ gpu_spectrum[ out_idx + 1 ] = norm_im;
236+ }
237+ }
238+
194239 let fft_elapsed = fft_start. elapsed ( ) . as_micros ( ) as f32 ;
195240
196241 // Sync to UI state
@@ -210,6 +255,7 @@ pub fn spawn_dsp_thread(
210255 }
211256
212257 state. raw_spectrum_data . copy_from_slice ( & binned_data) ;
258+ state. gpu_spectrum_data = gpu_spectrum;
213259 state. raw_audio_channels = msg. channel_audio_data ;
214260
215261 // --- Waveform extraction (Zero-Crossing Edge Trigger) ---
0 commit comments