use sim_kernel::{Error, Result};
use sim_lib_audio_graph_core::{
BlockArena, NullEventSink, PrepareConfig, ProcessBlock, Processor, Transport,
};
use sim_lib_stream_audio::{
PcmBuffer, PcmSampleFormat, PcmSpec, f32_interleaved_to_planar, f32_planar_to_interleaved,
f32_samples_to_i16, i16_samples_to_f32,
};
use sim_lib_stream_core::{PcmPacket, PushResult, StreamPacket};
use sim_lib_stream_host::HostCallbackQueue;
#[derive(Debug)]
pub struct AlsaPlaybackBridge<P> {
processor: P,
spec: PcmSpec,
max_block_frames: u32,
scratch: BlockArena,
sample_pos: u64,
}
#[derive(Clone)]
pub struct AlsaCaptureBridge {
queue: HostCallbackQueue,
spec: PcmSpec,
}
impl<P: Processor> AlsaPlaybackBridge<P> {
pub fn new(mut processor: P, spec: PcmSpec, max_block_frames: u32) -> Result<Self> {
if max_block_frames == 0 {
return Err(Error::Eval(
"ALSA playback max block frames must be greater than zero".to_owned(),
));
}
processor.prepare(PrepareConfig::new(
spec.sample_rate_hz(),
max_block_frames,
0,
checked_channels(spec.channels(), "playback output")?,
));
Ok(Self {
processor,
spec,
max_block_frames,
scratch: BlockArena::with_f32_capacity(max_block_frames as usize * spec.channels()),
sample_pos: 0,
})
}
pub fn render_buffer(&mut self, frames: usize) -> Result<PcmBuffer> {
let interleaved = self.render_interleaved_f32(frames)?;
match self.spec.sample_format() {
PcmSampleFormat::F32 => PcmBuffer::f32(self.spec, frames, interleaved),
PcmSampleFormat::I16 => {
PcmBuffer::i16(self.spec, frames, f32_samples_to_i16(&interleaved)?)
}
}
}
pub fn render_interleaved_f32(&mut self, frames: usize) -> Result<Vec<f32>> {
if frames > self.max_block_frames as usize {
return Err(Error::Eval(format!(
"ALSA playback callback received {frames} frames, max is {}",
self.max_block_frames
)));
}
let mut output_planar = vec![vec![0.0; frames]; self.spec.channels()];
{
let input_refs: [&[f32]; 0] = [];
let mut output_refs = output_planar
.iter_mut()
.map(Vec::as_mut_slice)
.collect::<Vec<_>>();
self.scratch.reset();
let mut event_sink = NullEventSink;
let mut block = ProcessBlock {
frames: frames as u32,
in_audio: &input_refs,
out_audio: &mut output_refs,
in_events: &[],
out_events: &mut event_sink,
transport: Transport {
playing: true,
sample_pos: self.sample_pos,
tempo_bpm: 120.0,
ppq_pos: 0.0,
},
scratch: &mut self.scratch,
};
block.validate_audio_lanes()?;
self.processor.process(&mut block);
block.validate_audio_lanes()?;
}
self.sample_pos = self.sample_pos.saturating_add(frames as u64);
f32_planar_to_interleaved(&output_planar)
}
pub fn sample_pos(&self) -> u64 {
self.sample_pos
}
pub fn reset(&mut self) {
self.processor.reset();
self.sample_pos = 0;
self.scratch.reset();
}
}
impl AlsaCaptureBridge {
pub fn new(queue: HostCallbackQueue, spec: PcmSpec) -> Self {
Self { queue, spec }
}
pub fn capture_interleaved_f32(&self, samples: &[f32]) -> Result<PushResult> {
let frames = samples_to_frames(samples.len(), self.spec.channels())?;
let planar = f32_interleaved_to_planar(samples, self.spec.channels())?;
let packet_samples = f32_planar_to_interleaved(&planar)?;
let packet = match self.spec.sample_format() {
PcmSampleFormat::F32 => PcmPacket::f32(self.spec.channels(), frames, packet_samples)?,
PcmSampleFormat::I16 => PcmPacket::i16(
self.spec.channels(),
frames,
f32_samples_to_i16(&packet_samples)?,
)?,
};
self.queue.callback_packet(StreamPacket::Pcm(packet))
}
pub fn capture_interleaved_i16(&self, samples: &[i16]) -> Result<PushResult> {
let frames = samples_to_frames(samples.len(), self.spec.channels())?;
let f32_samples = i16_samples_to_f32(samples);
let packet = match self.spec.sample_format() {
PcmSampleFormat::F32 => PcmPacket::f32(self.spec.channels(), frames, f32_samples)?,
PcmSampleFormat::I16 => PcmPacket::i16(self.spec.channels(), frames, samples.to_vec())?,
};
self.queue.callback_packet(StreamPacket::Pcm(packet))
}
pub fn spec(&self) -> PcmSpec {
self.spec
}
}
fn samples_to_frames(samples: usize, channels: usize) -> Result<usize> {
if channels == 0 {
return Err(Error::Eval(
"ALSA capture channel count must be greater than zero".to_owned(),
));
}
if !samples.is_multiple_of(channels) {
return Err(Error::Eval(format!(
"ALSA capture sample length {samples} is not divisible by channels {channels}"
)));
}
Ok(samples / channels)
}
fn checked_channels(channels: usize, role: &str) -> Result<u16> {
u16::try_from(channels)
.map_err(|_| Error::Eval(format!("ALSA {role} channel count exceeds u16")))
}