audiofox 1.0.3

Cross platform audio capture made easy!
Documentation
// SPDX-License-Identifier: GPL-2.0-or-later WITH Classpath-exception-2.0
// Audiofox, the audio capturer!
// Copyright (c) 2026 foxberries

use crate::common::chunk::Chunk;

pub const CHUNK_SIZE_U8: usize = 32768;
pub const CHUNK_SIZE_F32: usize = 8192;

/// Builder for accumulating F32LE PCM data.
#[derive(Clone, Copy, Debug)]
pub struct ChunkBuilder<T, const N: usize> {
    pub data: [T; N],
    pub position: usize,
}

impl<T: Default + Copy, const N: usize> ChunkBuilder<T, N> {
    #[must_use]
    pub fn new() -> Self {
        Self {
            data: [T::default(); N],
            position: 0,
        }
    }

    /// Extend the contents of the `ChunkBuilder` with a slice. If the slice is too large, the
    /// overflow is returned.
    pub fn extend_from_slice<'a>(&mut self, slice: &'a [T]) -> Option<&'a [T]> {
        let remaining = Self::space_remaining(self);
        if slice.len() <= remaining {
            self.data[self.position..self.position + slice.len()].copy_from_slice(slice);
            self.position += slice.len();
            None
        } else {
            self.data[self.position..].copy_from_slice(&slice[..remaining]);
            self.position += remaining;
            Some(&slice[remaining..])
        }
    }

    /// Reset the `ChunkBuilder` with the overflow from a previously emitted [`Chunk`]. The
    /// overflow must not be larger than its capacity.
    ///
    /// # Panics
    ///
    /// Panics if the overflow itself overflows. This can occur if the size of the `ChunkBuilder`
    /// is less than that of a single audio packet received by the system, which it should never be.
    pub fn reset_with_overflow(&mut self, slice: &[T]) {
        self.data[..slice.len()].copy_from_slice(slice);
        self.position = slice.len();
    }

    /// Get a mutable slice of the unfilled portion of the `ChunkBuilder`.
    pub fn unfilled_mut(&mut self) -> &mut [T] {
        &mut self.data[self.position..]
    }

    /// Get the space remaining in the `ChunkBuilder` from its `position`.
    pub const fn space_remaining(&self) -> usize {
        N - self.position
    }
}

impl<'a> From<&'a ChunkBuilder<u8, CHUNK_SIZE_U8>> for Chunk<'a> {
    /// Converts a `ChunkBuilder` to a finalized [`Chunk`] to be returned with an `AudioCallback`.
    ///
    /// # Safety
    ///
    /// This reinterpretation is safe so long as `ChunkBuilder` is fully populated with genuine
    /// F32LE data, yet to be reinterpreted from its buffers. If this precondition is violated, the
    /// behavior is undefined.
    fn from(builder: &'a ChunkBuilder<u8, CHUNK_SIZE_U8>) -> Self {
        unsafe {
            let ptr = builder.data.as_ptr().cast::<[f32; CHUNK_SIZE_F32]>();
            Self(&*ptr)
        }
    }
}

impl<'a> From<&'a ChunkBuilder<f32, CHUNK_SIZE_F32>> for Chunk<'a> {
    /// Converts a `ChunkBuilder` to a finalized [`Chunk`] to be returned with an `AudioCallback`.
    /// This reinterpretation is valid so long as `ChunkBuilder` is fully populated.
    fn from(builder: &'a ChunkBuilder<f32, CHUNK_SIZE_F32>) -> Self {
        Self(&builder.data)
    }
}

/// Alternative to `::new()`
impl<T: Default + Copy, const N: usize> Default for ChunkBuilder<T, N> {
    fn default() -> Self {
        Self::new()
    }
}

/// Alternative to `::<u8, CHUNK_SIZE_U8>::new()`
impl ChunkBuilder<u8, CHUNK_SIZE_U8> {
    #[must_use]
    pub fn new_u8() -> Self {
        Self::new()
    }
}

/// Alternative to `::<f32, CHUNK_SIZE_F32>::new()`
impl ChunkBuilder<f32, CHUNK_SIZE_F32> {
    #[must_use]
    pub fn new_f32() -> Self {
        Self::new()
    }
}