Add audio controls and focused hotkeys

This commit is contained in:
2026-06-16 17:23:38 -04:00
parent 22f0eed94d
commit 20643a24de
12 changed files with 1341 additions and 59 deletions
+23 -18
View File
@@ -283,8 +283,9 @@ fn run_playback(
let core = context.connect_rc(None)
.map_err(|e| AudioError::Init(e.to_string()))?;
// Ring buffer setup: 9600 samples (200ms capacity for mono 48kHz).
const RING_CAPACITY: usize = 9600;
// Ring buffer setup: 19200 interleaved samples (200ms capacity for stereo
// 48kHz).
const RING_CAPACITY: usize = 9600 * crate::audio::PLAYBACK_CHANNELS;
let rb = HeapRb::<i16>::new(RING_CAPACITY);
let (mut producer, consumer) = rb.split();
@@ -371,7 +372,7 @@ fn run_playback(
let data = &mut datas[0];
let mut total_size = 0;
if let Some(slice) = data.data() {
let stride = 2; // S16LE Mono = 2 bytes per frame
let stride = 2 * crate::audio::PLAYBACK_CHANNELS; // S16LE stereo
// Fill exactly what the graph asked for this cycle (with
// a safe fallback), never the whole mapped slice — that
// over-pull past the ring depth was the original crackle.
@@ -383,17 +384,20 @@ fn run_playback(
user_data.callback_count.fetch_add(1, Ordering::Relaxed);
let mut starved = 0u64;
for i in 0..n_frames {
let val = match user_data.consumer.try_pop() {
Some(v) => v,
None => {
starved += 1;
0
}
};
let bytes = val.to_le_bytes();
let start = i * stride;
slice[start] = bytes[0];
slice[start + 1] = bytes[1];
for ch in 0..crate::audio::PLAYBACK_CHANNELS {
let val = match user_data.consumer.try_pop() {
Some(v) => v,
None => {
starved += 1;
0
}
};
let bytes = val.to_le_bytes();
let offset = start + ch * 2;
slice[offset] = bytes[0];
slice[offset + 1] = bytes[1];
}
}
if starved > 0 {
// One wait-free atomic add per quantum — RT-safe.
@@ -403,7 +407,8 @@ fn run_playback(
// actually pulled (excluding underruns, which removed
// nothing) so the mixer paces against true ring depth.
// Wait-free fetch_sub, RT-safe.
let popped = n_frames - starved as usize;
let requested_samples = n_frames * crate::audio::PLAYBACK_CHANNELS;
let popped = requested_samples - starved as usize;
if popped > 0 {
user_data.fill_gauge.fetch_sub(popped, Ordering::Relaxed);
}
@@ -411,7 +416,7 @@ fn run_playback(
}
let chunk = data.chunk_mut();
*chunk.offset_mut() = 0;
*chunk.stride_mut() = 2;
*chunk.stride_mut() = (2 * crate::audio::PLAYBACK_CHANNELS) as _;
*chunk.size_mut() = total_size as _;
}
}
@@ -422,7 +427,7 @@ fn run_playback(
let mut audio_info = spa::param::audio::AudioInfoRaw::new();
audio_info.set_format(spa::param::audio::AudioFormat::S16LE);
audio_info.set_rate(48000);
audio_info.set_channels(1); // Mono
audio_info.set_channels(crate::audio::PLAYBACK_CHANNELS as u32); // Stereo playback
let obj = pw::spa::pod::Object {
type_: pw::spa::utils::SpaTypes::ObjectParamFormat.as_raw(),
@@ -450,7 +455,7 @@ fn run_playback(
// `frames_to_produce`). `requested()`, not the buffer size, now governs
// per-cycle output, so this is a generous max rather than a hard pin.
const MAX_QUANTUM_FRAMES: i32 = 8192;
const STRIDE: i32 = 2; // S16LE mono = 2 bytes/frame
const STRIDE: i32 = 2 * crate::audio::PLAYBACK_CHANNELS as i32; // S16LE stereo
let buffers_obj = pw::spa::pod::Object {
type_: pw::spa::utils::SpaTypes::ObjectParamBuffers.as_raw(),
id: pw::spa::param::ParamType::Buffers.as_raw(),
@@ -555,7 +560,7 @@ fn run_playback(
if verbose || du > 0 || dd > 0 {
crate::log_msg(&format!(
"playout-health: fill={fill} samples (~{}ms) | underrun +{du} samples/s (total {u}) | dropped +{dd} frames/s (total {d}) | quantum={q} frames, {dc} callbacks/s",
fill / 48,
fill / (48 * crate::audio::PLAYBACK_CHANNELS),
));
}
}