sample-resource 0.1.0

Traits for working with arbitrary resources of audio samples
Documentation
//! # sample-resource
//! Traits for working with arbitrary resources of audio samples

#![forbid(missing_docs)]
#![forbid(unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]

pub mod helpers;

use core::ops::Range;

/// An immutable resource of audio samples which has its data stored in de-interleaved
/// `f32` format, and which has all of its data loaded into memory.
///
/// # Implementation example
/// ```
/// # use sample_resource::{SampleResourceF32, SampleResourceInfo};
/// struct MyResource {
///     data: Vec<Vec<f32>>,
///     frames: usize,
///     sample_rate: Option<f64>,
/// }
///
/// impl SampleResourceF32 for MyResource {
///     fn info(&self) -> SampleResourceInfo {
///         SampleResourceInfo {
///             num_channels: self.data.len(),
///             len_frames: self.frames as u64,
///             sample_rate: self.sample_rate,
///         }
///     }
///
///     fn channel(&self, i: usize) -> Option<&[f32]> {
///         self.data.get(i).map(|ch| ch.as_slice())
///     }
/// }
/// ```
///
/// # Realtime Safety
/// 3rd party libraries that implement this trait should expect that all methods in
/// this trait may be called in a realtime thread. For more information, see:
/// <http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing>
pub trait SampleResourceF32 {
    /// Retrieve information about this sample resource. This information is
    /// not allowed to change for the lifetime of this sample resource.
    fn info(&self) -> SampleResourceInfo;

    /// Return an immutable slice to the entire channel at index `i`.
    ///
    /// Returns `None` if `i` is out-of-bounds.
    fn channel(&self, i: usize) -> Option<&[f32]>;
}

/// A mutable resource of audio samples which has its data stored in de-interleaved
/// `f32` format, and which has all of its data loaded into memory.
///
/// # Implementation example
/// ```
/// # use sample_resource::{SampleResourceF32, SampleResourceF32Mut, SampleResourceInfo};
/// struct MyResource {
///     data: Vec<Vec<f32>>,
///     frames: usize,
///     sample_rate: Option<f64>,
/// }
///
/// impl SampleResourceF32 for MyResource {
///     fn info(&self) -> SampleResourceInfo {
///         SampleResourceInfo {
///             num_channels: self.data.len(),
///             len_frames: self.frames as u64,
///             sample_rate: self.sample_rate,
///         }
///     }
///     fn channel(&self, i: usize) -> Option<&[f32]> {
///         self.data.get(i).map(|ch| ch.as_slice())
///     }
/// }
///
/// impl SampleResourceF32Mut for MyResource {
///     fn channel_mut(&mut self, i: usize) -> Option<&mut [f32]> {
///         self.data.get_mut(i).map(|ch| ch.as_mut_slice())
///     }
///
///     fn channel_mut_2(&mut self, indices: [usize; 2]) -> Option<[&mut [f32]; 2]> {
///         self.data
///             .get_disjoint_mut(indices)
///             .map(|ch| ch.map(|v| v.as_mut_slice()))
///             .ok()
///     }
/// }
/// ```
///
/// # Realtime Safety
/// 3rd party libraries that implement this trait should expect that all methods in
/// this trait may be called in a realtime thread. For more information, see:
/// <http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing>
pub trait SampleResourceF32Mut: SampleResourceF32 {
    /// Return a mutable slice to the entire channel at index `i`.
    ///
    /// Returns `None` if `i` is out-of-bounds.
    fn channel_mut(&mut self, i: usize) -> Option<&mut [f32]>;

    /// Return a mutable slice to two channels at once.
    ///
    /// Returns `None` if any index is out-of-bounds or if the
    /// indices overlap.
    fn channel_mut_2(&mut self, indices: [usize; 2]) -> Option<[&mut [f32]; 2]>;
}

/// Information about a [`SampleResource`] or a [`SampleResourceF32`], a resource
/// of audio samples with a known length.
///
/// This information is *NOT* allowed to change for the lifetime of this sample
/// resource.
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct SampleResourceInfo {
    /// The number of audio channels in this resource.
    pub num_channels: usize,

    /// The length of this resource in frames (samples of a single channel
    /// of audio, not to be confused with video frames).
    pub len_frames: u64,

    /// The sample rate of this resource. This will be`None` if the sample
    /// rate is unknown.
    pub sample_rate: Option<f64>,
}

/// Information about the caching behavior of a [`SampleResource`].
///
/// This is only relevant for [`SampleResource`]s which don't have all of
/// their data loaded in memory, such as those that stream data from a disk or
/// over a network. [`SampleResource`]s which already have all of their data
/// loaded in memory do not need to implement this.
///
/// This information is allowed to change (usually in response to a call
/// to [`SampleResource::notify_playhead_behavior()`]).
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct SampleCacheInfo {
    /// The number of frames (samples in a single channel of audio)
    /// contained in a single cached block.
    ///
    /// This size must be sufficient enough to where if
    /// [`SampleResource::fill_buffers_f32`] is called at the starting frame of
    /// a cached block, there is ample enough time* (when playing back at the
    /// speed reported in [`SampleResource::notify_playhead_behavior()`]) for
    /// the stream to catch up before the cache is exhausted.
    pub cache_block_len: usize,

    /// The maximum supported playback speed. (Where `1.0` is playing at the
    /// sample rate of this resource, `0.5` is playing at half the sample rate
    /// of this resource, and `2.0` is playing at twice the speed of the
    /// sample rate of this resource).
    pub max_playback_speed: f64,

    /// The number of available cache blocks.
    pub num_cache_blocks: usize,
}

/// Information a [`SampleResource`] can use to make optimal caching decisions
/// based on the behavior of the playhead.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PlayheadBehavior {
    /// The speed at which the playhead is moving.  (Where `1.0` is playing at
    /// the sample rate of this resource, `0.5` is playing at half the sample
    /// rate of this resource, and `2.0` is playing at twice the speed of the
    /// sample rate of this resource).
    pub playback_speed: f64,

    /// `True` if playing backwards, `false` if playing forwards.
    pub is_playing_backwards: bool,
}

impl Default for PlayheadBehavior {
    fn default() -> Self {
        Self {
            playback_speed: 1.0,
            is_playing_backwards: false,
        }
    }
}

/// A resource of audio samples with a known length.
///
/// # Implementation examples
/// ```
/// # use core::ops::Range;
/// # use sample_resource::{SampleResource, SampleResourceInfo};
/// // Interleaved example
/// struct MyInterleavedResource {
///     data: Vec<i16>,
///     channels: usize,
///     sample_rate: Option<f64>,
/// }
///
/// impl SampleResource for MyInterleavedResource {
///     fn info(&self) -> SampleResourceInfo {
///         SampleResourceInfo {
///             num_channels: self.channels,
///             len_frames: (self.data.len() / self.channels) as u64,
///             sample_rate: self.sample_rate,
///         }
///     }
///
///     fn fill_buffers_f32(
///         &self,
///         buffer: &mut [&mut [f32]],
///         buffer_range: Range<usize>,
///         start_frame: u64,
///     ) -> usize {
///         sample_resource::helpers::fill_buffers_interleaved(
///             buffer,
///             buffer_range,
///             start_frame,
///             self.channels,
///             self.data.len() / self.channels,
///             &self.data,
///         )
///     }
/// }
///
/// // Deinterleaved example
/// struct MyDeinterleavedResource {
///     data: Vec<Vec<i16>>,
///     frames: usize,
///     channels: usize,
///     sample_rate: Option<f64>,
/// }
///
/// impl SampleResource for MyDeinterleavedResource {
///     fn info(&self) -> SampleResourceInfo {
///         SampleResourceInfo {
///             num_channels: self.channels,
///             len_frames: self.frames as u64,
///             sample_rate: self.sample_rate,
///         }
///     }
///
///     fn fill_buffers_f32(
///         &self,
///         buffer: &mut [&mut [f32]],
///         buffer_range: Range<usize>,
///         start_frame: u64,
///     ) -> usize {
///         sample_resource::helpers::fill_buffers_deinterleaved(
///             buffer,
///             buffer_range,
///             start_frame,
///             self.frames,
///             &self.data,
///         )
///     }
/// }
/// ```
///
/// # Realtime Safety
/// 3rd party libraries that implement this trait should expect that all methods in
/// this trait may be called in a realtime thread. For more information, see:
/// <http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing>
pub trait SampleResource {
    /// Retrieve information about this sample resource. This information is
    /// *NOT* allowed to change for the lifetime of this sample resource.
    fn info(&self) -> SampleResourceInfo;

    /// Fill the given buffers with audio data in `f32` format starting from the
    /// given starting frame in the sample resource.
    ///
    /// Returns the number of frames (samples in a single channel of audio) that
    /// were successfully written to the buffer. This may be less than the requested
    /// number of frames if `buffer_range` falls outside the range of the sample
    /// resource or if a cache miss occured.
    ///
    /// * `buffer` - The buffers to fill with data. If the length of `buffers`
    ///   is greater than the number of channels in this resource, then the extra
    ///   channels will be left untouched.
    /// * `buffer_range` - The range inside each channel slice in `buffers` to
    ///   fill with data.
    ///     * If a range falls outside the range of the sample
    ///       resource, then that range will be left untouched. (Note it is the
    ///       caller's responsibility to handle looping behavior, not the sample
    ///       resource's responsibility.)
    ///     * If a cache miss occured, then the range
    ///       [`buffer_range.start + return_value..buffer_range.end`] will be filled
    ///       with zeros.
    /// * `start_frame` - The frame (sample of a single channel of audio) in the
    ///   sample resource to start copying from (inclusive).
    ///
    /// The data will *NOT* be declicked (as in clicking caused by pausing/resuming,
    /// the playhead jumping to a different position, or by a cache miss). It is up
    /// to the caller to do their own declicking.
    ///
    /// When playing backwards (notified in
    /// [`SampleResource::notify_playhead_behavior()`]), the data in the buffer
    /// will *NOT* be reversed. It is up to the caller to reverse the data
    /// themselves.
    ///
    /// # Panics
    /// Panics if `buffer_range` is out-of-bounds for any channel slice in `buffer`.
    fn fill_buffers_f32(
        &self,
        buffer: &mut [&mut [f32]],
        buffer_range: Range<usize>,
        start_frame: u64,
    ) -> usize;

    // -- Optional methods ---------------------------------------------------------

    /// Information about the caching behavior of this sample resource. Returns
    /// `None` if caching is not used by this resource.
    ///
    /// This information is allowed to change (usually in response to a call
    /// to [`SampleResource::notify_playhead_behavior()`]).
    ///
    /// This is only relevant for sample resources which don't have all of
    /// their data loaded in memory, such as those that stream data from a disk or
    /// over a network. Sample resources which already have all of their data
    /// loaded in memory should return `None`.
    ///
    /// By default this returns `None`.
    fn cache_info(&self) -> Option<&SampleCacheInfo> {
        None
    }

    /// Request the sample resource to cache a block of frames (samples
    /// in a single channel of audio).
    ///
    /// * `start_frame` - The starting frame (samples in a single channel of audio)
    ///   in the sample resource to cache a new block (inclusive). If a resulting
    ///   range falls outside the range of the sample resource, then ignore that
    ///   range.
    /// * `priority` - A number describing how important this cache is (higher
    ///   means more important). This can be used by the resource to determine
    ///   which caches to discard if there are no more cache blocks available.
    ///
    /// This is only relevant for sample resources which stream data from disk
    /// or over a network. Sample resources which already have all their contents
    /// stored in memory do not need to implement this method.
    fn cache(&self, start_frame: u64, priority: u32) {
        let _ = start_frame;
        let _ = priority;
    }

    /// Notify the sample resource the speed and the direction that the playhead
    /// is moving. This can be used to help the sample resource make optimal
    /// caching decisions.
    ///
    /// Until this method is called, sample resources should assume that
    /// `playback_speed = 1.0` and `is_playing_backwards = false`.
    ///
    /// This is only relevant for sample resources which stream data from disk
    /// or over a network. Sample resources which already have all their contents
    /// stored in memory do not need to implement this method.
    fn notify_playhead_behavior(&self, behavior: PlayheadBehavior) {
        let _ = behavior;
    }
}