use super::bridge::DspBridgeReader;
#[cfg(feature = "standalone")]
use crate::common::spsc::RtStatusFlags;
#[cfg(feature = "standalone")]
use crate::dsp::oversample::OversampleFactor;
#[cfg(feature = "standalone")]
use std::sync::atomic::Ordering;
#[cfg(feature = "standalone")]
use crate::common::diagnostics::SystemSnapshot;
#[cfg(feature = "standalone")]
use pipewire as pw;
#[cfg(feature = "standalone")]
use rtrb;
#[cfg(feature = "standalone")]
#[expect(
dead_code,
reason = "RAII anchor — fields held solely for drop-semantics lifetime management"
)]
pub(crate) struct AppState<S1, L1, S2, L2> {
pub capture_stream: S1,
pub capture_listener: L1,
pub playback_stream: S2,
pub playback_listener: L2,
}
#[cfg(feature = "standalone")]
pub struct PipewireHostConfig {
pub buffer_size: u32,
pub sys: SystemSnapshot,
pub ir_raw_samples: Option<Vec<f32>>,
pub full_wavenet_model: Option<Box<crate::models::StaticModel>>,
pub slimmable_producer: rtrb::Producer<Option<Box<crate::models::StaticModel>>>,
pub os_producer: rtrb::Producer<Box<crate::dsp::oversample::OsEnginePair>>,
pub oversample: OversampleFactor,
}
#[cfg(feature = "standalone")]
#[inline(always)]
pub(crate) fn playback_dsp_cycle(
stream: &pw::stream::Stream,
bridge: DspBridgeReader,
last_bridge_gen: &mut u64,
rt_status: &RtStatusFlags,
) {
bridge.read_block(last_bridge_gen, |buf_l, buf_r| {
let n_samples = buf_l.len();
if n_samples == 0 {
return;
}
let mut buf = match stream.dequeue_buffer() {
Some(b) => b,
None => {
rt_status.playback_miss.fetch_add(1, Ordering::Relaxed);
return;
}
};
let datas = buf.datas_mut();
if datas.len() < 2 {
return;
}
let (datas_left, datas_right) = datas.split_at_mut(1);
let data_l = &mut datas_left[0];
let data_r = &mut datas_right[0];
let max_l = data_l.as_raw().maxsize as usize / std::mem::size_of::<f32>();
let max_r = data_r.as_raw().maxsize as usize / std::mem::size_of::<f32>();
let n_out = n_samples.min(max_l).min(max_r);
if n_out == 0 {
return;
}
if let Some(raw_l) = data_l.data() {
let out_l =
unsafe { std::slice::from_raw_parts_mut(raw_l.as_mut_ptr().cast::<f32>(), n_out) };
unsafe {
core::ptr::copy_nonoverlapping(buf_l.as_ptr(), out_l.as_mut_ptr(), n_out);
}
}
if let Some(raw_r) = data_r.data() {
let out_r =
unsafe { std::slice::from_raw_parts_mut(raw_r.as_mut_ptr().cast::<f32>(), n_out) };
unsafe {
core::ptr::copy_nonoverlapping(buf_r.as_ptr(), out_r.as_mut_ptr(), n_out);
}
}
{
let chunk = data_l.chunk_mut();
*chunk.size_mut() = (n_out * std::mem::size_of::<f32>()) as u32;
*chunk.offset_mut() = 0;
*chunk.stride_mut() = std::mem::size_of::<f32>() as i32;
}
{
let chunk = data_r.chunk_mut();
*chunk.size_mut() = (n_out * std::mem::size_of::<f32>()) as u32;
*chunk.offset_mut() = 0;
*chunk.stride_mut() = std::mem::size_of::<f32>() as i32;
}
});
}
#[cfg(feature = "standalone")]
pub(crate) unsafe fn build_spa_format_pod<'a>(
audio_info: &pw::spa::param::audio::AudioInfoRaw,
format_buf: &'a mut [u8; 1024],
) -> anyhow::Result<&'a pw::spa::pod::Pod> {
unsafe {
let mut builder: pw::spa::sys::spa_pod_builder = std::mem::zeroed();
pw::spa::sys::spa_pod_builder_init(
&mut builder,
format_buf.as_mut_ptr().cast(),
format_buf.len() as u32,
);
let pod_ptr = pw::spa::sys::spa_format_audio_raw_build(
&mut builder,
pw::spa::param::ParamType::EnumFormat.as_raw(),
&audio_info.as_raw(),
);
if pod_ptr.is_null() {
return Err(anyhow::anyhow!(
"Failed to build the audio negotiation document (SPA Pod)"
));
}
Ok(&*(pod_ptr as *const pw::spa::pod::Pod))
}
}