use truce_params::sample::Sample;
pub struct AudioBuffer<'a, S: Sample = f32> {
inputs: &'a [&'a [S]],
outputs: &'a mut [&'a mut [S]],
in_place_mask: u64,
offset: usize,
num_samples: usize,
}
impl<'a, S: Sample> AudioBuffer<'a, S> {
pub fn from_slices_checked(
inputs: &'a [&'a [S]],
outputs: &'a mut [&'a mut [S]],
num_samples: usize,
) -> Self {
unsafe { Self::from_slices(inputs, outputs, num_samples) }
}
pub unsafe fn from_slices(
inputs: &'a [&'a [S]],
outputs: &'a mut [&'a mut [S]],
num_samples: usize,
) -> Self {
#[cfg(debug_assertions)]
{
for (i, inp) in inputs.iter().enumerate() {
let i_start = inp.as_ptr() as usize;
let i_end = i_start + std::mem::size_of_val(*inp);
for (o, out) in outputs.iter().enumerate() {
let o_start = out.as_ptr() as usize;
let o_end = o_start + std::mem::size_of_val(*out);
assert!(
i_end <= o_start || o_end <= i_start,
"AudioBuffer: input channel {i} and output channel {o} alias \
- pass disjoint slices or use RawBufferScratch::build which \
handles aliasing automatically",
);
}
}
for (i, inp) in inputs.iter().enumerate() {
assert!(
num_samples <= inp.len(),
"AudioBuffer: num_samples ({num_samples}) exceeds input channel {i} length ({})",
inp.len(),
);
}
for (o, out) in outputs.iter().enumerate() {
assert!(
num_samples <= out.len(),
"AudioBuffer: num_samples ({num_samples}) exceeds output channel {o} length ({})",
out.len(),
);
}
}
AudioBuffer {
inputs,
outputs,
in_place_mask: 0,
offset: 0,
num_samples,
}
}
#[inline]
pub fn set_in_place_mask(&mut self, mask: u64) {
self.in_place_mask = mask;
}
#[must_use]
pub fn is_in_place(&self, ch: usize) -> bool {
ch < 64 && (self.in_place_mask >> ch) & 1 == 1
}
pub fn in_out_mut(&mut self, ch: usize) -> &mut [S] {
let end = self.offset + self.num_samples;
&mut self.outputs[ch][self.offset..end]
}
#[must_use]
pub fn num_samples(&self) -> usize {
self.num_samples
}
#[must_use]
pub fn num_input_channels(&self) -> usize {
self.inputs.len()
}
#[must_use]
pub fn num_output_channels(&self) -> usize {
self.outputs.len()
}
#[must_use]
pub fn input(&self, channel: usize) -> &[S] {
let end = self.offset + self.num_samples;
&self.inputs[channel][self.offset..end]
}
pub fn output(&mut self, channel: usize) -> &mut [S] {
let end = self.offset + self.num_samples;
&mut self.outputs[channel][self.offset..end]
}
#[must_use]
pub fn channels(&self) -> usize {
self.inputs.len().min(self.outputs.len())
}
pub fn io_pair(&mut self, in_ch: usize, out_ch: usize) -> (&[S], &mut [S]) {
let end = self.offset + self.num_samples;
let input = &self.inputs[in_ch][self.offset..end];
let output = &mut self.outputs[out_ch][self.offset..end];
(input, output)
}
pub fn io(&mut self, ch: usize) -> (&[S], &mut [S]) {
self.io_pair(ch, ch)
}
pub fn chunks_mut<const N: usize>(&mut self) -> ChunksMut<'_, 'a, S, N> {
ChunksMut {
buffer: self,
ch: 0,
pos: 0,
}
}
pub fn for_each_frame<const N: usize, F>(&mut self, mut tick: F)
where
F: FnMut(&[S; N], &mut [S; N]),
{
debug_assert_eq!(
N,
self.channels(),
"for_each_frame::<{N}> requires the buffer to have exactly {N} channels"
);
let mut frame_in = [S::default(); N];
let mut frame_out = [S::default(); N];
let end = self.offset + self.num_samples;
for i in self.offset..end {
for (ch, slot) in frame_in.iter_mut().enumerate() {
*slot = self.inputs[ch][i];
}
tick(&frame_in, &mut frame_out);
for (ch, sample) in frame_out.iter().enumerate() {
self.outputs[ch][i] = *sample;
}
}
}
#[must_use]
pub fn output_peak(&self, ch: usize) -> f32 {
let end = self.offset + self.num_samples;
let mut peak = 0.0f32;
for &b in &self.outputs[ch][self.offset..end] {
let v = b.to_f32();
if v.is_nan() {
return f32::NAN;
}
let abs = v.abs();
if abs > peak {
peak = abs;
}
}
peak
}
pub fn slice(&mut self, start: usize, len: usize) -> AudioBuffer<'_, S> {
assert!(
start + len <= self.num_samples,
"slice({start}, {len}) out of bounds for buffer of {} samples",
self.num_samples,
);
let new_offset = self.offset + start;
let self_ptr: *mut Self = self;
unsafe {
let s = &mut *self_ptr;
std::mem::transmute::<AudioBuffer<'a, S>, AudioBuffer<'_, S>>(AudioBuffer {
inputs: s.inputs,
outputs: &mut *s.outputs,
in_place_mask: s.in_place_mask,
offset: new_offset,
num_samples: len,
})
}
}
}
pub enum ChunkItem<'b, S: Sample, const N: usize> {
Full {
ch: usize,
sample: usize,
inp: &'b [S; N],
out: &'b mut [S; N],
},
Tail {
ch: usize,
sample: usize,
inp: &'b [S],
out: &'b mut [S],
},
}
pub struct ChunksMut<'b, 'a, S: Sample, const N: usize> {
buffer: &'b mut AudioBuffer<'a, S>,
ch: usize,
pos: usize,
}
impl<S: Sample, const N: usize> ChunksMut<'_, '_, S, N> {
#[allow(clippy::should_implement_trait, clippy::missing_panics_doc)]
pub fn next(&mut self) -> Option<ChunkItem<'_, S, N>> {
loop {
if self.ch >= self.buffer.outputs.len() {
return None;
}
let ns = self.buffer.num_samples;
if self.pos >= ns {
self.ch += 1;
self.pos = 0;
continue;
}
let abs_start = self.buffer.offset + self.pos;
let remaining = ns - self.pos;
let take = remaining.min(N);
let abs_end = abs_start + take;
let ch = self.ch;
let sample = self.pos;
let inp_slice = &self.buffer.inputs[ch][abs_start..abs_end];
let out_slice: &mut [S] = &mut self.buffer.outputs[ch][abs_start..abs_end];
self.pos += take;
return Some(if take == N {
ChunkItem::Full {
ch,
sample,
inp: inp_slice.try_into().expect("len == N by construction"),
out: out_slice.try_into().expect("len == N by construction"),
}
} else {
ChunkItem::Tail {
ch,
sample,
inp: inp_slice,
out: out_slice,
}
});
}
}
}
pub struct RawBufferScratch<S: Sample = f32> {
pub input_slices: Vec<&'static [S]>,
pub output_slices: Vec<&'static mut [S]>,
input_copies: Vec<Vec<S>>,
output_buffers: Vec<Vec<S>>,
}
impl<S: Sample> RawBufferScratch<S> {
pub unsafe fn build(
&mut self,
inputs: *const *const f32,
outputs: *mut *mut f32,
num_in: u32,
num_out: u32,
num_frames: u32,
supports_in_place: bool,
) -> AudioBuffer<'_, S> {
unsafe {
self.build_inner(
inputs,
outputs,
num_in,
num_out,
num_frames,
supports_in_place,
)
}
}
pub unsafe fn finish_widening_f32(
&self,
outputs: *mut *mut f32,
num_out: u32,
num_frames: u32,
) {
if std::any::TypeId::of::<S>() == std::any::TypeId::of::<f32>() {
return;
}
unsafe {
let nf = num_frames as usize;
for ch in 0..(num_out as usize) {
let ptr = *outputs.add(ch);
if ptr.is_null() {
continue;
}
let host = std::slice::from_raw_parts_mut(ptr, nf);
let plugin_out = &self.output_buffers[ch];
for (h, &p) in host.iter_mut().zip(plugin_out.iter()) {
*h = p.to_f32();
}
}
}
}
unsafe fn build_inner<'a>(
&'a mut self,
inputs: *const *const f32,
outputs: *mut *mut f32,
num_in: u32,
num_out: u32,
num_frames: u32,
supports_in_place: bool,
) -> AudioBuffer<'a, S> {
const MAX_CHANNELS_TRACKED: usize = 64;
let same_precision = std::any::TypeId::of::<S>() == std::any::TypeId::of::<f32>();
unsafe {
let nf = num_frames as usize;
let num_out_u = num_out as usize;
let num_in_u = num_in as usize;
debug_assert!(
num_out_u <= MAX_CHANNELS_TRACKED,
"RawBufferScratch::build: alias detection only covers up to {MAX_CHANNELS_TRACKED} \
output channels; got {num_out_u}. Channels beyond the cap won't be \
detected as aliased.",
);
let out_ptrs: [Option<*mut f32>; MAX_CHANNELS_TRACKED] = std::array::from_fn(|ch| {
if ch < num_out_u {
let p = *outputs.add(ch);
if p.is_null() { None } else { Some(p) }
} else {
None
}
});
let aliases_any_output = |in_ptr: *const f32| -> bool {
let in_start = in_ptr as usize;
let in_end = in_start + nf * std::mem::size_of::<f32>();
out_ptrs
.iter()
.take(num_out_u.min(MAX_CHANNELS_TRACKED))
.any(|o| {
o.is_some_and(|op| {
let o_start = op as usize;
let o_end = o_start + nf * std::mem::size_of::<f32>();
!(in_end <= o_start || o_end <= in_start)
})
})
};
while self.input_copies.len() < num_in_u {
self.input_copies.push(Vec::new());
}
if !same_precision {
while self.output_buffers.len() < num_out_u {
self.output_buffers.push(Vec::new());
}
}
self.input_slices.clear();
self.input_slices.reserve(num_in_u);
let mut in_place_mask: u64 = 0;
for ch in 0..num_in_u {
let ptr = *inputs.add(ch);
let slice: &[S] = if ptr.is_null() {
&[]
} else if aliases_any_output(ptr) {
if ch < 64 {
in_place_mask |= 1 << ch;
}
if supports_in_place && same_precision {
&[]
} else {
let host = std::slice::from_raw_parts(ptr, nf);
let copy = &mut self.input_copies[ch];
copy.clear();
copy.reserve(nf);
for &h in host {
copy.push(S::from_f32(h));
}
let p = copy.as_ptr();
let l = copy.len();
std::slice::from_raw_parts(p, l)
}
} else if same_precision {
let raw = ptr.cast::<S>();
std::slice::from_raw_parts(raw, nf)
} else {
let host = std::slice::from_raw_parts(ptr, nf);
let copy = &mut self.input_copies[ch];
copy.clear();
copy.reserve(nf);
for &h in host {
copy.push(S::from_f32(h));
}
let p = copy.as_ptr();
let l = copy.len();
std::slice::from_raw_parts(p, l)
};
self.input_slices.push(slice);
}
self.output_slices.clear();
self.output_slices.reserve(num_out_u);
for ch in 0..num_out_u {
let ptr = *outputs.add(ch);
let slice: &mut [S] = if ptr.is_null() {
&mut []
} else if same_precision {
let raw = ptr.cast::<S>();
std::slice::from_raw_parts_mut(raw, nf)
} else {
let buf = &mut self.output_buffers[ch];
buf.clear();
buf.resize(nf, S::default());
let p = buf.as_mut_ptr();
let l = buf.len();
std::slice::from_raw_parts_mut(p, l)
};
self.output_slices.push(slice);
}
let self_ptr: *mut Self = self;
let s = &mut *self_ptr;
let mut buf = std::mem::transmute::<AudioBuffer<'static, S>, AudioBuffer<'a, S>>(
AudioBuffer::from_slices(&s.input_slices, &mut s.output_slices, nf),
);
buf.set_in_place_mask(in_place_mask);
buf
}
}
pub fn ensure_capacity(&mut self, num_in: usize, num_out: usize, max_frames: usize) {
if self.input_slices.capacity() < num_in {
self.input_slices
.reserve_exact(num_in - self.input_slices.capacity());
}
if self.output_slices.capacity() < num_out {
self.output_slices
.reserve_exact(num_out - self.output_slices.capacity());
}
while self.input_copies.len() < num_in {
self.input_copies.push(Vec::with_capacity(max_frames));
}
for buf in &mut self.input_copies {
if buf.capacity() < max_frames {
buf.reserve_exact(max_frames - buf.capacity());
}
}
while self.output_buffers.len() < num_out {
self.output_buffers.push(Vec::with_capacity(max_frames));
}
for buf in &mut self.output_buffers {
if buf.capacity() < max_frames {
buf.reserve_exact(max_frames - buf.capacity());
}
}
}
}
impl<S: Sample> Default for RawBufferScratch<S> {
fn default() -> Self {
Self {
input_slices: Vec::with_capacity(2),
output_slices: Vec::with_capacity(2),
input_copies: Vec::with_capacity(2),
output_buffers: Vec::with_capacity(2),
}
}
}