more fixes
All checks were successful
Deploy Website / deploy (push) Has been skipped

This commit is contained in:
2026-03-01 03:33:22 +01:00
parent b72c782b2b
commit 11cc925faf
24 changed files with 269 additions and 189 deletions

View File

@@ -264,6 +264,10 @@ use cpal::Stream;
use crossbeam_channel::{Receiver, Sender};
#[cfg(feature = "cli")]
use doux::{Engine, EngineMetrics};
#[cfg(feature = "cli")]
use std::collections::VecDeque;
#[cfg(feature = "cli")]
use std::sync::Mutex;
#[cfg(feature = "cli")]
use super::AudioCommand;
@@ -271,6 +275,7 @@ use super::AudioCommand;
#[cfg(feature = "cli")]
pub struct AudioStreamConfig {
pub output_device: Option<String>,
pub input_device: Option<String>,
pub channels: u16,
pub buffer_size: u32,
pub max_voices: usize,
@@ -283,6 +288,15 @@ pub struct AudioStreamInfo {
pub channels: u16,
}
#[cfg(feature = "cli")]
type BuildStreamResult = (
Stream,
Option<Stream>,
AudioStreamInfo,
AnalysisHandle,
Arc<doux::SampleRegistry>,
);
#[cfg(feature = "cli")]
#[allow(clippy::too_many_arguments)]
pub fn build_stream(
@@ -295,15 +309,7 @@ pub fn build_stream(
audio_sample_pos: Arc<AtomicU64>,
error_tx: Sender<String>,
sample_paths: &[std::path::PathBuf],
) -> Result<
(
Stream,
AudioStreamInfo,
AnalysisHandle,
Arc<doux::SampleRegistry>,
),
String,
> {
) -> Result<BuildStreamResult, String> {
let device = match &config.output_device {
Some(name) => doux::audio::find_output_device(name)
.ok_or_else(|| format!("Device not found: {name}"))?,
@@ -352,10 +358,72 @@ pub fn build_stream(
let registry = Arc::clone(&engine.sample_registry);
const INPUT_BUFFER_SIZE: usize = 8192;
let input_buffer: Arc<Mutex<VecDeque<f32>>> =
Arc::new(Mutex::new(VecDeque::with_capacity(INPUT_BUFFER_SIZE)));
let input_device = config
.input_device
.as_ref()
.and_then(|name| {
let dev = doux::audio::find_input_device(name);
if dev.is_none() {
eprintln!("input device not found: {name}");
}
dev
});
let input_channels: usize = input_device
.as_ref()
.and_then(|dev| dev.default_input_config().ok())
.map_or(0, |cfg| cfg.channels() as usize);
let input_stream = input_device.and_then(|dev| {
let input_cfg = match dev.default_input_config() {
Ok(cfg) => cfg,
Err(e) => {
eprintln!("input config error: {e}");
return None;
}
};
if input_cfg.sample_rate() != default_config.sample_rate() {
eprintln!(
"warning: input sample rate ({}Hz) differs from output ({}Hz)",
input_cfg.sample_rate(),
default_config.sample_rate()
);
}
eprintln!(
"opening input: {}ch @ {}Hz",
input_cfg.channels(),
input_cfg.sample_rate()
);
let buf = Arc::clone(&input_buffer);
let stream = dev
.build_input_stream(
&input_cfg.into(),
move |data: &[f32], _| {
let mut b = buf.lock().unwrap();
b.extend(data.iter().copied());
let excess = b.len().saturating_sub(INPUT_BUFFER_SIZE);
if excess > 0 {
drop(b.drain(..excess));
}
},
|err| eprintln!("input stream error: {err}"),
None,
)
.ok()?;
stream.play().ok()?;
Some(stream)
});
let (mut fft_producer, analysis_handle) = spawn_analysis_thread(sample_rate, spectrum_buffer);
let mut cmd_buffer = String::with_capacity(256);
let mut rt_set = false;
let mut live_scratch = vec![0.0f32; 4096];
let input_buf_clone = Arc::clone(&input_buffer);
let stream = device
.build_output_stream(
@@ -402,8 +470,49 @@ pub fn build_stream(
}
}
// doux expects stereo interleaved live_input (CHANNELS=2)
let stereo_len = buffer_samples * 2;
if live_scratch.len() < stereo_len {
live_scratch.resize(stereo_len, 0.0);
}
let mut buf = input_buf_clone.lock().unwrap();
match input_channels {
0 => {
live_scratch[..stereo_len].fill(0.0);
}
1 => {
for i in 0..buffer_samples {
let s = buf.pop_front().unwrap_or(0.0);
live_scratch[i * 2] = s;
live_scratch[i * 2 + 1] = s;
}
}
2 => {
for sample in &mut live_scratch[..stereo_len] {
*sample = buf.pop_front().unwrap_or(0.0);
}
}
_ => {
for i in 0..buffer_samples {
let l = buf.pop_front().unwrap_or(0.0);
let r = buf.pop_front().unwrap_or(0.0);
for _ in 2..input_channels {
buf.pop_front();
}
live_scratch[i * 2] = l;
live_scratch[i * 2 + 1] = r;
}
}
}
// Discard excess if input produced more than we consumed
let excess = buf.len().saturating_sub(INPUT_BUFFER_SIZE / 2);
if excess > 0 {
drop(buf.drain(..excess));
}
drop(buf);
engine.metrics.load.set_buffer_time(buffer_time_ns);
engine.process_block(data, &[], &[]);
engine.process_block(data, &[], &live_scratch[..stereo_len]);
scope_buffer.write(data);
// Feed mono mix to analysis thread via ring buffer (non-blocking)
@@ -425,5 +534,5 @@ pub fn build_stream(
host_name,
channels: effective_channels,
};
Ok((stream, info, analysis_handle, registry))
Ok((stream, input_stream, info, analysis_handle, registry))
}