Worker thread for audio processing. Processes audio in a separate thread to avoid blocking the real-time audio callback. Uses pre-allocated buffers to prevent memory allocations during processing. Model is created based on the initial state value.
(
mut input_consumer: ringbuf::HeapCons<f32>,
mut output_producer: ringbuf::HeapProd<f32>,
state: Arc<SharedState>,
host_sample_rate: usize,
)
| 148 | /// audio callback. Uses pre-allocated buffers to prevent memory allocations |
| 149 | /// during processing. Model is created based on the initial state value. |
| 150 | fn worker_thread( |
| 151 | mut input_consumer: ringbuf::HeapCons<f32>, |
| 152 | mut output_producer: ringbuf::HeapProd<f32>, |
| 153 | state: Arc<SharedState>, |
| 154 | host_sample_rate: usize, |
| 155 | ) { |
| 156 | let needs_resample = host_sample_rate != MODEL_SAMPLE_RATE; |
| 157 | let resample_ratio = host_sample_rate as f64 / MODEL_SAMPLE_RATE as f64; |
| 158 | |
| 159 | // Calculate chunk sizes for resampling |
| 160 | // At 48kHz host and 16kHz model, ratio is 3.0 |
| 161 | // We process HOP_SIZE (256) samples at model rate per frame |
| 162 | let host_chunk_size = (HOP_SIZE as f64 * resample_ratio).ceil() as usize; |
| 163 | |
| 164 | // Initialize high-quality sinc resamplers |
| 165 | let mut downsampler: Option<FftFixedIn<f32>> = if needs_resample { |
| 166 | Some( |
| 167 | FftFixedIn::new(host_sample_rate, MODEL_SAMPLE_RATE, host_chunk_size, 1, 1) |
| 168 | .expect("Failed to create downsampler"), |
| 169 | ) |
| 170 | } else { |
| 171 | None |
| 172 | }; |
| 173 | |
| 174 | let mut upsampler: Option<FftFixedOut<f32>> = if needs_resample { |
| 175 | Some( |
| 176 | FftFixedOut::new(MODEL_SAMPLE_RATE, host_sample_rate, host_chunk_size, 1, 1) |
| 177 | .expect("Failed to create upsampler"), |
| 178 | ) |
| 179 | } else { |
| 180 | None |
| 181 | }; |
| 182 | |
| 183 | // Initialize STFT processor |
| 184 | let mut stft = StftProcessor::new(NFFT, HOP_SIZE); |
| 185 | |
| 186 | // Wait for first run() call to set port values before creating model |
| 187 | while !state.is_initialized() { |
| 188 | if state.should_shutdown() { |
| 189 | return; |
| 190 | } |
| 191 | thread::sleep(Duration::from_millis(1)); |
| 192 | } |
| 193 | |
| 194 | // Create model based on current state value (now set by run() call) |
| 195 | let initial_model_type = state.get_model_type(); |
| 196 | let mut model = GtcrnModel::new(initial_model_type); |
| 197 | |
| 198 | // Pre-allocated fixed-size buffers (no heap allocations during processing) |
| 199 | let mut input_buffer = vec![0.0_f32; host_chunk_size + 64]; |
| 200 | let mut window = vec![0.0_f32; NFFT]; |
| 201 | let mut model_accum = vec![0.0_f32; HOP_SIZE * 4]; // Accumulator for downsampled samples |
| 202 | let mut model_accum_len: usize = 0; |
| 203 | |
| 204 | // Resampler I/O buffers (single channel, fixed size) |
| 205 | let mut resample_in = vec![vec![0.0_f32; host_chunk_size + 64]]; |
| 206 | let mut resample_out = vec![vec![0.0_f32; HOP_SIZE + 64]]; |
| 207 | let mut upsample_in = vec![vec![0.0_f32; HOP_SIZE + 64]]; |
no test coverage detected