diff --git a/crates/forth/src/words/effects.rs b/crates/forth/src/words/effects.rs index 5d1cc4e..ad41f6d 100644 --- a/crates/forth/src/words/effects.rs +++ b/crates/forth/src/words/effects.rs @@ -454,6 +454,36 @@ pub(super) const WORDS: &[Word] = &[ compile: Param, varargs: true, }, + Word { + name: "eqlofreq", + aliases: &[], + category: "Filter", + stack: "(v.. --)", + desc: "Set low shelf frequency (Hz)", + example: "400 eqlofreq", + compile: Param, + varargs: true, + }, + Word { + name: "eqmidfreq", + aliases: &[], + category: "Filter", + stack: "(v.. --)", + desc: "Set mid peak frequency (Hz)", + example: "2000 eqmidfreq", + compile: Param, + varargs: true, + }, + Word { + name: "eqhifreq", + aliases: &[], + category: "Filter", + stack: "(v.. --)", + desc: "Set high shelf frequency (Hz)", + example: "8000 eqhifreq", + compile: Param, + varargs: true, + }, Word { name: "tilt", aliases: &[], diff --git a/crates/forth/src/words/sound.rs b/crates/forth/src/words/sound.rs index 16484f6..f4aa46a 100644 --- a/crates/forth/src/words/sound.rs +++ b/crates/forth/src/words/sound.rs @@ -246,6 +246,16 @@ pub(super) const WORDS: &[Word] = &[ compile: Param, varargs: true, }, + Word { + name: "inchan", + aliases: &[], + category: "Sample", + stack: "(v.. --)", + desc: "Select input channel for live input (0-indexed)", + example: "0 inchan", + compile: Param, + varargs: true, + }, Word { name: "cut", aliases: &[], diff --git a/src/engine/audio.rs b/src/engine/audio.rs index 3b8f7e7..539079d 100644 --- a/src/engine/audio.rs +++ b/src/engine/audio.rs @@ -355,9 +355,6 @@ pub fn build_stream( let registry = Arc::clone(&engine.sample_registry); - const INPUT_BUFFER_SIZE: usize = 8192; - let (input_producer, input_consumer) = HeapRb::::new(INPUT_BUFFER_SIZE).split(); - let input_device = config .input_device .as_ref() @@ -374,6 +371,12 @@ pub fn build_stream( .and_then(|dev| dev.default_input_config().ok()) .map_or(0, |cfg| cfg.channels() as usize); + engine.input_channels = input_channels; + + const INPUT_BUFFER_BASE: usize = 8192; + let input_buffer_size = INPUT_BUFFER_BASE * (input_channels.max(2) / 2); + let (input_producer, input_consumer) = HeapRb::::new(input_buffer_size).split(); + let input_stream = input_device.and_then(|dev| { let input_cfg = match dev.default_input_config() { Ok(cfg) => cfg, @@ -476,47 +479,16 @@ 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); - } - match input_channels { - 0 => { - live_scratch[..stereo_len].fill(0.0); - } - 1 => { - for i in 0..buffer_samples { - let s = input_consumer.try_pop().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 = input_consumer.try_pop().unwrap_or(0.0); - } - } - _ => { - for i in 0..buffer_samples { - let l = input_consumer.try_pop().unwrap_or(0.0); - let r = input_consumer.try_pop().unwrap_or(0.0); - for _ in 2..input_channels { - input_consumer.try_pop(); - } - live_scratch[i * 2] = l; - live_scratch[i * 2 + 1] = r; - } - } - } - // Discard excess if input produced more than we consumed - let excess = input_consumer.occupied_len().saturating_sub(INPUT_BUFFER_SIZE / 2); - for _ in 0..excess { - input_consumer.try_pop(); + let nch_in = input_channels.max(1); + let raw_len = buffer_samples * nch_in; + if live_scratch.len() < raw_len { + live_scratch.resize(raw_len, 0.0); } + live_scratch[..raw_len].fill(0.0); + input_consumer.pop_slice(&mut live_scratch[..raw_len]); engine.metrics.load.set_buffer_time(buffer_time_ns); - engine.process_block(data, &[], &live_scratch[..stereo_len]); + engine.process_block(data, &[], &live_scratch[..raw_len]); scope_buffer.write(data); // Feed mono mix to analysis thread via ring buffer (non-blocking)