Wip: refacto

This commit is contained in:
2026-01-25 22:17:08 +01:00
parent 2d609f6b7a
commit 016d050678
11 changed files with 289 additions and 82 deletions

View File

@@ -2,9 +2,11 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::Stream;
use crossbeam_channel::Receiver;
use doux::{Engine, EngineMetrics};
use ringbuf::{traits::*, HeapRb};
use rustfft::{num_complex::Complex, FftPlanner};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use super::AudioCommand;
@@ -75,6 +77,7 @@ impl SpectrumBuffer {
const FFT_SIZE: usize = 512;
const NUM_BANDS: usize = 32;
const ANALYSIS_RING_SIZE: usize = 4096;
struct SpectrumAnalyzer {
ring: Vec<f32>,
@@ -151,6 +154,72 @@ impl SpectrumAnalyzer {
}
}
pub struct AnalysisHandle {
running: Arc<AtomicBool>,
#[allow(dead_code)]
thread: Option<JoinHandle<()>>,
}
impl AnalysisHandle {
#[allow(dead_code)]
pub fn shutdown(mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(t) = self.thread.take() {
let _ = t.join();
}
}
}
impl Drop for AnalysisHandle {
fn drop(&mut self) {
self.running.store(false, Ordering::SeqCst);
}
}
pub fn spawn_analysis_thread(
sample_rate: f32,
spectrum_buffer: Arc<SpectrumBuffer>,
) -> (ringbuf::HeapProd<f32>, AnalysisHandle) {
let rb = HeapRb::<f32>::new(ANALYSIS_RING_SIZE);
let (producer, consumer) = rb.split();
let running = Arc::new(AtomicBool::new(true));
let running_clone = Arc::clone(&running);
let thread = thread::Builder::new()
.name("fft-analysis".into())
.spawn(move || {
analysis_loop(consumer, spectrum_buffer, sample_rate, running_clone);
})
.expect("Failed to spawn FFT analysis thread");
let handle = AnalysisHandle {
running,
thread: Some(thread),
};
(producer, handle)
}
fn analysis_loop(
mut consumer: ringbuf::HeapCons<f32>,
spectrum_buffer: Arc<SpectrumBuffer>,
sample_rate: f32,
running: Arc<AtomicBool>,
) {
let mut analyzer = SpectrumAnalyzer::new(sample_rate);
let mut local_buf = [0.0f32; 256];
while running.load(Ordering::Relaxed) {
let count = consumer.pop_slice(&mut local_buf);
if count > 0 {
analyzer.feed(&local_buf[..count], &spectrum_buffer);
} else {
thread::sleep(std::time::Duration::from_micros(500));
}
}
}
pub struct AudioStreamConfig {
pub output_device: Option<String>,
pub channels: u16,
@@ -165,7 +234,7 @@ pub fn build_stream(
spectrum_buffer: Arc<SpectrumBuffer>,
metrics: Arc<EngineMetrics>,
initial_samples: Vec<doux::sample::SampleEntry>,
) -> Result<(Stream, f32), String> {
) -> Result<(Stream, f32, AnalysisHandle), String> {
let host = cpal::default_host();
let device = match &config.output_device {
@@ -193,12 +262,13 @@ pub fn build_stream(
let sr = sample_rate;
let channels = config.channels as usize;
let max_voices = config.max_voices;
let metrics_clone = Arc::clone(&metrics);
let mut engine = Engine::new_with_metrics(sample_rate, channels, Arc::clone(&metrics));
let mut engine = Engine::new_with_metrics(sample_rate, channels, max_voices, Arc::clone(&metrics));
engine.sample_index = initial_samples;
let mut analyzer = SpectrumAnalyzer::new(sample_rate);
let (mut fft_producer, analysis_handle) = spawn_analysis_thread(sample_rate, spectrum_buffer);
let stream = device
.build_output_stream(
@@ -224,7 +294,7 @@ pub fn build_stream(
AudioCommand::ResetEngine => {
let old_samples = std::mem::take(&mut engine.sample_index);
engine =
Engine::new_with_metrics(sr, channels, Arc::clone(&metrics_clone));
Engine::new_with_metrics(sr, channels, max_voices, Arc::clone(&metrics_clone));
engine.sample_index = old_samples;
}
}
@@ -234,11 +304,11 @@ pub fn build_stream(
engine.process_block(data, &[], &[]);
scope_buffer.write(&engine.output);
// Feed mono mix to spectrum analyzer
let mono: Vec<f32> = engine.output.chunks(channels)
.map(|ch| ch.iter().sum::<f32>() / channels as f32)
.collect();
analyzer.feed(&mono, &spectrum_buffer);
// Feed mono mix to analysis thread via ring buffer (non-blocking)
for chunk in engine.output.chunks(channels) {
let mono = chunk.iter().sum::<f32>() / channels as f32;
let _ = fft_producer.try_push(mono);
}
},
|err| eprintln!("stream error: {err}"),
None,
@@ -248,5 +318,5 @@ pub fn build_stream(
stream
.play()
.map_err(|e| format!("Failed to play stream: {e}"))?;
Ok((stream, sample_rate))
Ok((stream, sample_rate, analysis_handle))
}