babycat 0.0.14

An audio decoding and manipulation library, with bindings for C, Python, and WebAssembly.
use std::io::Read;
use std::marker::Send;
use std::marker::Sync;

use serde::{Deserialize, Serialize};

use crate::backend::constants::{
    DEFAULT_END_TIME_MILLISECONDS, DEFAULT_FRAME_RATE_HZ, DEFAULT_NUM_CHANNELS,
    DEFAULT_RESAMPLE_MODE, DEFAULT_START_TIME_MILLISECONDS,
};
use crate::backend::decoder;
use crate::backend::display::est_num_frames_to_str;
use crate::backend::errors::Error;
use crate::backend::resample::resample;
use crate::backend::source::WaveformSource;
use crate::backend::units::milliseconds_to_frames;
use crate::backend::Signal;
use crate::backend::Source;
use crate::backend::WaveformArgs;

/// Represents a fixed-length audio waveform as a `Vec<f32>`.
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct Waveform {
    interleaved_samples: Vec<f32>,
    frame_rate_hz: u32,
    num_channels: u16,
    num_frames: usize,
}

impl std::fmt::Debug for Waveform {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "Waveform {{ {} frames,  {} channels,  {} hz,  {} }}",
            est_num_frames_to_str(self.num_frames_estimate()),
            self.num_channels(),
            self.frame_rate_hz(),
            self.duration_estimate_to_str(),
        )
    }
}

impl Waveform {
    pub fn from_interleaved_samples(
        frame_rate_hz: u32,
        num_channels: u16,
        interleaved_samples: &[f32],
    ) -> Self {
        Self::new(frame_rate_hz, num_channels, interleaved_samples.to_owned())
    }

    pub fn from_source(
        args: WaveformArgs,
        mut source: Box<dyn Source + '_>,
    ) -> Result<Self, Error> {
        let original_frame_rate_hz = source.frame_rate_hz();
        let original_num_channels = source.num_channels();

        // If the user has provided an end timestamp that is BEFORE
        // our start timestamp, then we raise an error.
        if args.start_time_milliseconds != DEFAULT_START_TIME_MILLISECONDS
            && args.end_time_milliseconds != DEFAULT_END_TIME_MILLISECONDS
            && args.start_time_milliseconds >= args.end_time_milliseconds
        {
            return Err(Error::WrongTimeOffset(
                args.start_time_milliseconds,
                args.end_time_milliseconds,
            ));
        }

        // The user cannot set zero_pad_ending and repeat_pad_ending at the same time.
        if args.zero_pad_ending && args.repeat_pad_ending {
            return Err(Error::CannotSetZeroPadEndingAndRepeatPadEnding);
        }

        // If the user has not specified how long the output audio should be,
        // then we would not know how to zero-pad after it.
        if args.zero_pad_ending && args.end_time_milliseconds == DEFAULT_END_TIME_MILLISECONDS {
            return Err(Error::CannotZeroPadWithoutSpecifiedLength);
        }

        // If the user has not specified how long the output audio should be,
        // then we would not know how to repeat-pad after it.
        if args.repeat_pad_ending && args.end_time_milliseconds == DEFAULT_END_TIME_MILLISECONDS {
            return Err(Error::CannotRepeatPadWithoutSpecifiedLength);
        }

        // We do not allow the user to specify that they want to extract
        // one channels AND to convert the waveform to mono.
        // Converting the waveform to mono only makes sense when
        // we are working with more than one channel.
        if args.num_channels == 1 && args.convert_to_mono {
            return Err(Error::WrongNumChannelsAndMono);
        }

        // This is the first n channels that we want to read from.
        // If the user wants to convert the output to mono, we do that after
        // reading from the first n channels.
        // If args.num_channels was unspecified, then we read from
        // all of the channels.
        if args.num_channels > original_num_channels {
            return Err(Error::WrongNumChannels(
                args.num_channels,
                original_num_channels,
            ));
        }
        let selected_num_channels: u16 = if args.num_channels == DEFAULT_NUM_CHANNELS {
            original_num_channels
        } else {
            args.num_channels
        };

        let output_num_channels: u16 = if args.convert_to_mono {
            1
        } else {
            selected_num_channels
        };

        let start_frame_idx =
            milliseconds_to_frames(args.start_time_milliseconds, original_frame_rate_hz);
        let end_frame_idx =
            milliseconds_to_frames(args.end_time_milliseconds, original_frame_rate_hz);

        let take_frames = end_frame_idx.saturating_sub(start_frame_idx);
        if start_frame_idx != 0 {
            source = Box::new(source.skip_frames(start_frame_idx));
        }
        if take_frames != 0 {
            source = Box::new(source.take_frames(take_frames));
        }
        if selected_num_channels != original_num_channels {
            source = Box::new(source.select_first_channels(selected_num_channels));
        }
        if args.convert_to_mono {
            source = Box::new(source.convert_to_mono());
        }
        let mut interleaved_samples: Vec<f32> = source.collect();

        // Pad the waveform if necessary.
        if (args.zero_pad_ending || args.repeat_pad_ending) && end_frame_idx > start_frame_idx {
            let expected_buffer_len_from_user: usize =
                (end_frame_idx - start_frame_idx) * output_num_channels as usize;
            let actual_buffer_len = interleaved_samples.len();
            if expected_buffer_len_from_user > actual_buffer_len {
                // Pad with zeros.
                if args.zero_pad_ending {
                    interleaved_samples.resize(expected_buffer_len_from_user, 0.0_f32);
                }
                // Pad with elements from the beginning, looping multiple times if necessary.
                else if args.repeat_pad_ending {
                    let buffer_padding_len = expected_buffer_len_from_user - actual_buffer_len;
                    for idx in 0..buffer_padding_len {
                        let rounded_idx = idx % actual_buffer_len;
                        interleaved_samples.push(interleaved_samples[rounded_idx]);
                    }
                }
            }
        }

        // If we want the audio to be at a different frame rate,
        // then resample it.
        let output_frame_rate_hz;
        if args.frame_rate_hz != DEFAULT_FRAME_RATE_HZ
            && args.frame_rate_hz != original_frame_rate_hz
        {
            output_frame_rate_hz = args.frame_rate_hz;
            interleaved_samples = resample(
                original_frame_rate_hz,
                output_frame_rate_hz,
                output_num_channels,
                &interleaved_samples,
                args.resample_mode,
            )?;
        } else {
            output_frame_rate_hz = original_frame_rate_hz;
        }

        Ok(Self::new(
            output_frame_rate_hz,
            output_num_channels,
            interleaved_samples,
        ))
    }

    /// Decodes audio stored in an in-memory byte array.
    ///
    /// # Arguments
    /// - `encoded_bytes`: A byte array containing encoded (e.g. MP3) audio.
    /// - `waveform_args`: Instructions on how to decode the audio.
    ///
    /// # Examples
    /// ```
    /// use babycat::assertions::assert_debug;
    /// use babycat::Waveform;
    ///
    /// let encoded_bytes: Vec<u8> = std::fs::read("audio-for-tests/andreas-theme/track.flac").unwrap();
    ///
    /// let waveform_args = Default::default();
    ///
    /// let waveform = Waveform::from_encoded_bytes(
    ///     &encoded_bytes,
    ///     waveform_args,
    /// ).unwrap();
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 9586415 frames,  2 channels,  44100 hz,  3m 37s 379ms }",
    /// );
    /// ```
    ///
    pub fn from_encoded_bytes(
        encoded_bytes: &[u8],
        waveform_args: WaveformArgs,
    ) -> Result<Self, Error> {
        let d =
            decoder::from_encoded_bytes_by_backend(waveform_args.decoding_backend, encoded_bytes)?;
        Self::from_source(waveform_args, d)
    }

    /// Decodes audio in an in-memory byte array, using user-specified encoding hints.
    ///
    /// # Arguments
    /// - `encoded_bytes`: A byte array containing encoded (e.g. MP3) audio.
    /// - `waveform_args`: Instructions on how to decode the audio.
    /// - `file_extension`: A hint--in the form of a file extension--to indicate
    ///    the encoding of the audio in `encoded_bytes`.
    /// - `mime_type`: A hint--in the form of a MIME type--to indicate
    ///    the encoding of the audio in `encoded_bytes`.
    ///
    pub fn from_encoded_bytes_with_hint(
        encoded_bytes: &[u8],
        waveform_args: WaveformArgs,
        file_extension: &str,
        mime_type: &str,
    ) -> Result<Self, Error> {
        let d = decoder::from_encoded_bytes_with_hint_by_backend(
            waveform_args.decoding_backend,
            encoded_bytes,
            file_extension,
            mime_type,
        )?;
        Self::from_source(waveform_args, d)
    }

    /// Decodes audio stored in a locaselect_first_channelsl file.
    ///
    /// # Arguments
    /// - `filename`: A filename of an encoded audio file on the local filesystem.
    /// - `waveform_args`: Instructions on how to decode the audio.
    ///
    /// # Feature flags
    /// This function is only available if the Cargo feature `enable-fileystem`
    /// flag is enabled. The `enable-filesystem` flag is enabled by default
    /// for the Babycat's Rust, Python, and C frontends, but is disabled
    /// for the WebAssembly frontend.
    ///
    /// # Examples
    /// **Decode one audio file with the default decoding arguments:**
    /// ```
    /// use babycat::{assertions::assert_debug, WaveformArgs, Waveform};
    ///
    /// let waveform = Waveform::from_file(
    ///    "audio-for-tests/circus-of-freaks/track.flac",
    ///     Default::default(),
    /// ).unwrap();
    ///
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 2491247 frames,  2 channels,  44100 hz,  56s 490ms }",
    /// );
    ///
    /// ```
    ///
    /// **Decode only the first 30 seconds and upsample to 48khz:**
    /// ```
    /// use babycat::{assertions::assert_debug, WaveformArgs, Waveform};
    ///
    /// let waveform_args = WaveformArgs {
    ///     end_time_milliseconds: 30000,
    ///     frame_rate_hz: 48000,
    ///     ..Default::default()
    /// };
    /// let waveform = Waveform::from_file(
    ///    "audio-for-tests/circus-of-freaks/track.flac",
    ///     waveform_args,
    /// ).unwrap();
    ///
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 1440000 frames,  2 channels,  48000 hz,  30s }"    
    /// );
    /// ```
    #[cfg(feature = "enable-filesystem")]
    pub fn from_file(filename: &str, waveform_args: WaveformArgs) -> Result<Self, Error> {
        let d = decoder::from_file_by_backend(waveform_args.decoding_backend, filename)?;
        Self::from_source(waveform_args, d)
    }

    /// Decodes audio from an input stream.
    ///
    /// [`Waveform`][crate::Waveform] will take ownership of the stream
    /// and read it until the end. Therefore, you cannot provide an infinte-length
    /// stream.
    ///
    /// # Arguments
    /// - `encoded_stream`: An I/O stream of encoded audio to decode.
    /// - `waveform_args`: Instructions on how to decode the audio.
    ///
    pub fn from_encoded_stream<R: 'static + Read + Send + Sync>(
        encoded_stream: R,
        waveform_args: WaveformArgs,
    ) -> Result<Self, Error> {
        let d = decoder::from_encoded_stream_by_backend(
            waveform_args.decoding_backend,
            encoded_stream,
        )?;
        Self::from_source(waveform_args, d)
    }

    /// Decodes audio from an input stream, using a user-specified decoding hint.
    ///
    /// # Arguments
    /// - `encoded_stream`: An I/O stream of encoded audio to decode.
    /// - `waveform_args`: Instructions on how to decode the audio.
    /// - `file_extension`: A hint--in the form of a file extension--to indicate
    ///    the encoding of the audio in `encoded_bytes`.
    /// - `mime_type`: A hint--in the form of a MIME type--to indicate
    ///   the encoding of the audio in `encoded_bytes`.
    ///
    pub fn from_encoded_stream_with_hint<R: 'static + Read + Send + Sync>(
        encoded_stream: R,
        waveform_args: WaveformArgs,
        file_extension: &str,
        mime_type: &str,
    ) -> Result<Self, Error> {
        let d = decoder::from_encoded_stream_with_hint_by_backend(
            waveform_args.decoding_backend,
            encoded_stream,
            file_extension,
            mime_type,
        )?;
        Self::from_source(waveform_args, d)
    }

    /// Creates a silent waveform measured in frames.
    ///
    /// # Arguments
    /// - `frame_rate_hz`: The frame rate of the waveform to create.
    /// - `num_channels`: The number of channels in the waveform to create.
    /// - `num_frames`: The number of frames of audio to generate.
    ///
    /// # Examples
    /// This creates a `Waveform` containing one second of silent *stereo* audio.
    /// ```
    /// use babycat::Waveform;
    /// use babycat::assertions::assert_debug;
    ///
    /// let waveform = Waveform::from_frames_of_silence(44100, 2, 44100);
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 44100 frames,  2 channels,  44100 hz,  1s }"
    /// );
    /// ```
    ///
    pub fn from_frames_of_silence(
        frame_rate_hz: u32,
        num_channels: u16,
        num_frames: usize,
    ) -> Self {
        Waveform {
            frame_rate_hz,
            num_channels,
            num_frames,
            interleaved_samples: vec![0.0; num_channels as usize * num_frames],
        }
    }

    /// Create a silent waveform measured in milliseconds.
    ///
    /// # Arguments
    /// - `frame_rate_hz`: The frame rate of the waveform to create.
    /// - `num_channels`: The number of channels in the waveform to create.
    /// - `duration_milliseconds`: The length of the audio waveform in milliseconds.
    ///
    /// # Examples
    /// This creates a `Waveform` containing one second of silent *stereo* audio.
    /// ```
    /// use babycat::Waveform;
    /// use babycat::assertions::assert_debug;
    ///
    /// let waveform = Waveform::from_milliseconds_of_silence(44100, 2, 1000);
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 44100 frames,  2 channels,  44100 hz,  1s }",
    /// );
    /// ```
    ///
    pub fn from_milliseconds_of_silence(
        frame_rate_hz: u32,
        num_channels: u16,
        duration_milliseconds: usize,
    ) -> Self {
        let num_frames = milliseconds_to_frames(duration_milliseconds, frame_rate_hz);
        Self::from_frames_of_silence(frame_rate_hz, num_channels, num_frames)
    }

    /// Resamples the waveform using the default resampler.
    ///
    /// # Arguments
    /// - `frame_rate_hz`: The destination frame rate to resample to.
    ///
    /// # Examples
    /// ```
    /// use babycat::{assertions::assert_debug, Waveform};
    ///
    /// let waveform = Waveform::from_file(
    ///     "audio-for-tests/circus-of-freaks/track.flac",
    ///     Default::default()
    /// ).unwrap();
    /// assert_debug(
    ///    &waveform,
    ///    "Waveform { 2491247 frames,  2 channels,  44100 hz,  56s 490ms }"
    /// );
    ///
    /// let upsampled = waveform.resample(96000).unwrap();
    /// assert_debug(
    ///     &upsampled,
    ///     "Waveform { 5423123 frames,  2 channels,  96000 hz,  56s 490ms }"
    /// );
    ///
    /// let downsampled = waveform.resample(8252).unwrap();
    /// assert_debug(
    ///     &downsampled,
    ///     "Waveform { 466163 frames,  2 channels,  8252 hz,  56s 490ms }",
    /// );
    /// ```
    pub fn resample(&self, frame_rate_hz: u32) -> Result<Self, Error> {
        self.resample_by_mode(frame_rate_hz, DEFAULT_RESAMPLE_MODE)
    }

    /// Resamples the audio using a specific resampler.
    ///
    /// # Arguments
    /// - `frame_rate_hz`: The destination frame rate to resample to.
    /// - `resample_mode`: The Babycat resampling backend to pick.
    ///
    /// # Examples
    /// ```
    /// use babycat::{assertions::assert_debug, Waveform};
    ///
    /// let waveform = Waveform::from_file(
    ///     "audio-for-tests/circus-of-freaks/track.flac",
    ///     Default::default()
    /// ).unwrap();
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 2491247 frames,  2 channels,  44100 hz,  56s 490ms }",
    /// );
    ///
    /// // Here we upsample our audio to 96khz with the libsamplerate resampler.
    /// let upsampled_libsamplerate = waveform.resample_by_mode(
    ///     96000,
    ///     babycat::constants::RESAMPLE_MODE_LIBSAMPLERATE
    /// ).unwrap();
    /// assert_debug(
    ///     &upsampled_libsamplerate,
    ///     "Waveform { 5423123 frames,  2 channels,  96000 hz,  56s 490ms }",
    /// );
    ///
    /// // And we upsample our audio again with Babycat's Lanczos resampler.
    /// let upsampled_lanczos = waveform.resample_by_mode(
    ///     96000,
    ///     babycat::constants::RESAMPLE_MODE_BABYCAT_LANCZOS
    /// ).unwrap();
    /// assert_debug(
    ///     &upsampled_lanczos,
    ///     "Waveform { 5423123 frames,  2 channels,  96000 hz,  56s 490ms }",
    /// );
    /// ```
    pub fn resample_by_mode(&self, frame_rate_hz: u32, resample_mode: u32) -> Result<Self, Error> {
        let interleaved_samples = resample(
            self.frame_rate_hz,
            frame_rate_hz,
            self.num_channels,
            &self.interleaved_samples,
            resample_mode,
        )?;
        let num_frames = interleaved_samples.len() / self.num_channels as usize;
        Ok(Self {
            interleaved_samples,
            frame_rate_hz,
            num_channels: self.num_channels,
            num_frames,
        })
    }

    /// Encodes the waveform into a WAV-encoded byte array.
    pub fn to_wav_buffer(&self) -> Result<Vec<u8>, Error> {
        let writer_spec = hound::WavSpec {
            channels: self.num_channels as u16,
            sample_rate: self.frame_rate_hz as u32,
            bits_per_sample: 32,
            sample_format: hound::SampleFormat::Float,
        };
        let mut cursor = std::io::Cursor::new(Vec::new());
        let mut writer = match hound::WavWriter::new(&mut cursor, writer_spec) {
            Ok(w) => w,
            Err(_) => return Err(Error::UnknownEncodeError),
        };
        for sample in &self.interleaved_samples {
            let sample_result = writer.write_sample(*sample);
            if sample_result.is_err() {
                return Err(Error::UnknownEncodeError);
            }
        }
        let finalize_result = writer.finalize();
        if finalize_result.is_err() {
            return Err(Error::UnknownEncodeError);
        }
        Ok(cursor.into_inner())
    }

    /// Writes the waveform to the filesystem as a WAV file.
    ///
    /// # Feature flags
    /// This function is only available if the Cargo feature `enable-fileystem`
    /// flag is enabled. The `enable-filesystem` flag is enabled by default
    /// for the Babycat's Rust, Python, and C frontends, but is disabled
    /// for the WebAssembly frontend.
    ///
    #[cfg(feature = "enable-filesystem")]
    pub fn to_wav_file(&self, filename: &str) -> Result<(), Error> {
        let writer_spec = hound::WavSpec {
            channels: self.num_channels as u16,
            sample_rate: self.frame_rate_hz as u32,
            bits_per_sample: 32,
            sample_format: hound::SampleFormat::Float,
        };
        let mut writer = match hound::WavWriter::create(filename, writer_spec) {
            Ok(w) => w,
            Err(_) => return Err(Error::UnknownEncodeError),
        };
        for sample in &self.interleaved_samples {
            let sample_result = writer.write_sample(*sample);
            if sample_result.is_err() {
                return Err(Error::UnknownEncodeError);
            }
        }
        let finalize_result = writer.finalize();
        if finalize_result.is_err() {
            return Err(Error::UnknownEncodeError);
        }
        Ok(())
    }

    /// Constructs a `Waveform` from an already-decoded vector of 32-bit float samples.
    ///
    /// # Arguments
    /// - `frame_rate_hz`:
    /// - `num_channels`:
    /// - `interleaved_samples`:
    ///
    /// # Examples
    ///
    /// This creates a `Waveform` containing one second of silent *stereo* audio.
    /// Note that the input vector contains 88,200 audio samples--which we divide into
    /// 44,100 frames containing two samples each.
    /// ```
    /// use babycat::Waveform;
    /// use babycat::assertions::assert_debug;
    ///
    /// let frame_rate_hz = 44100;
    /// let num_channels = 2;
    /// let raw_uncompressed_audio: Vec<f32> = vec![0.0_f32; 88200];
    /// let waveform = Waveform::new(frame_rate_hz, num_channels, raw_uncompressed_audio);
    /// assert_debug(
    ///     &waveform,
    ///     "Waveform { 44100 frames,  2 channels,  44100 hz,  1s }",
    /// );
    /// ```
    ///
    pub fn new(frame_rate_hz: u32, num_channels: u16, interleaved_samples: Vec<f32>) -> Self {
        let num_frames = interleaved_samples.len() / num_channels as usize;
        Waveform {
            interleaved_samples,
            frame_rate_hz,
            num_channels,
            num_frames,
        }
    }

    /// Return a given audio sample belonging to a specific frame and channel.
    ///
    /// This method performs bounds checks before returning an audio sample.
    /// If you want a method that does not perform bounds checks,
    /// use [`get_unchecked_sample`](crate::Waveform::get_unchecked_sample).
    ///
    /// # Examples
    /// ```
    /// use babycat::Waveform;
    ///
    /// let interleaved_samples: Vec<f32> = vec![
    ///    -1.0, -0.9, -0.8, //
    ///    -0.7, -0.6, -0.5, //
    ///    -0.4, -0.3, -0.2, //
    ///    -0.1, 0.0, 0.1, //
    ///    0.2, 0.3, 0.4,
    /// ];
    ///
    /// let frame_rate_hz = 44100;
    /// let num_channels = 3;
    ///
    /// let waveform = Waveform::from_interleaved_samples(
    ///     frame_rate_hz, num_channels, &interleaved_samples,
    /// );
    ///
    /// assert_eq!(waveform.get_sample(0, 0).unwrap(), -1.0);
    /// assert_eq!(waveform.get_sample(0, 1).unwrap(), -0.9);
    /// assert_eq!(waveform.get_sample(0, 2).unwrap(), -0.8);
    ///
    /// assert_eq!(waveform.get_sample(1, 0).unwrap(), -0.7);
    /// assert_eq!(waveform.get_sample(1, 1).unwrap(), -0.6);
    /// assert_eq!(waveform.get_sample(1, 2).unwrap(), -0.5);
    /// ```
    #[inline]
    pub fn get_sample(&self, frame_idx: usize, channel_idx: u16) -> Option<f32> {
        if frame_idx >= self.num_frames || channel_idx >= self.num_channels {
            return None;
        }
        unsafe { Some(self.get_unchecked_sample(frame_idx, channel_idx)) }
    }

    /// Return a given audio sample belonging to a specific frame and channel,
    /// *without* performing any bounds checks.
    ///
    /// If you want a method that performs bounds checks,
    /// use [`get_sample`](crate::Waveform::get_sample).
    ///
    /// # Examples
    /// ```
    /// use babycat::Waveform;
    ///
    /// let interleaved_samples: Vec<f32> = vec![
    ///    -1.0, -0.9, -0.8, //
    ///    -0.7, -0.6, -0.5, //
    ///    -0.4, -0.3, -0.2, //
    ///    -0.1, 0.0, 0.1, //
    ///    0.2, 0.3, 0.4,
    /// ];
    ///
    /// let frame_rate_hz = 44100;
    /// let num_channels = 3;
    ///
    /// let waveform = Waveform::from_interleaved_samples(
    ///     frame_rate_hz, num_channels, &interleaved_samples,
    /// );
    ///
    /// unsafe {
    ///     assert_eq!(waveform.get_unchecked_sample(0, 0), -1.0);
    ///     assert_eq!(waveform.get_unchecked_sample(0, 1), -0.9);
    ///     assert_eq!(waveform.get_unchecked_sample(0, 2), -0.8);
    ///
    ///     assert_eq!(waveform.get_unchecked_sample(1, 0), -0.7);
    ///     assert_eq!(waveform.get_unchecked_sample(1, 1), -0.6);
    ///     assert_eq!(waveform.get_unchecked_sample(1, 2), -0.5);
    /// }
    /// ```
    ///
    /// # Safety
    /// Because this method does not peform any bounds checks, it is unsafe.
    #[inline]
    pub unsafe fn get_unchecked_sample(&self, frame_idx: usize, channel_idx: u16) -> f32 {
        *(self
            .interleaved_samples
            .get_unchecked(frame_idx * self.num_channels as usize + channel_idx as usize))
    }

    #[inline]
    pub fn get_interleaved_sample(&self, sample_idx: usize) -> Option<f32> {
        self.interleaved_samples.get(sample_idx).copied()
    }

    /// # Safety
    /// Because this method does not peform any bounds checks, it is unsafe.
    #[inline]
    pub unsafe fn get_unchecked_interleaved_sample(&self, sample_idx: usize) -> f32 {
        *(self.interleaved_samples.get_unchecked(sample_idx))
    }

    pub fn num_samples(&self) -> usize {
        self.interleaved_samples.len()
    }

    /// Returns the total number of decoded frames in the `Waveform`.
    pub fn num_frames(&self) -> usize {
        self.num_frames
    }

    /// Returns the waveform as a slice of channel-interleaved `f32` samples.
    pub fn to_interleaved_samples(&self) -> &[f32] {
        &self.interleaved_samples
    }

    pub fn into_source(self) -> WaveformSource {
        WaveformSource::new(self)
    }
}

impl Signal for Waveform {
    /// Returns the frame rate of the `Waveform`.
    fn frame_rate_hz(&self) -> u32 {
        self.frame_rate_hz
    }

    /// Returns the number of channels in the `Waveform`.
    fn num_channels(&self) -> u16 {
        self.num_channels
    }

    fn num_frames_estimate(&self) -> Option<usize> {
        Some(self.num_frames)
    }
}

impl From<Waveform> for Vec<f32> {
    fn from(waveform: Waveform) -> Vec<f32> {
        waveform.interleaved_samples
    }
}

/// These are unit tests for functionality that is currently specific to
/// the FFmpeg backend.
#[cfg(all(test, feature = "enable-ffmpeg"))]
mod test_waveform_from_file_ffmpeg {
    const TTCT_FILENAME_OGG: &str = "./audio-for-tests/32-channel-tone/track.ogg";
    const TTCT_FILENAME_WAV: &str = "./audio-for-tests/32-channel-tone/track.wav";
    const TTCT_NUM_CHANNELS: u16 = 32;
    const TTCT_NUM_FRAMES: usize = 88200;
    const TTCT_FRAME_RATE_HZ: u32 = 44100;

    use crate::constants::DECODING_BACKEND_FFMPEG;
    use crate::Error;
    use crate::Signal;
    use crate::Waveform;
    use crate::WaveformArgs;

    #[track_caller]
    #[inline]
    fn assert_waveform(
        result: Result<Waveform, Error>,
        num_channels: u16,
        num_frames: usize,
        frame_rate_hz: u32,
    ) {
        let waveform = result.unwrap();
        assert_eq!(num_channels, waveform.num_channels());
        assert_eq!(num_frames, waveform.num_frames());
        assert_eq!(frame_rate_hz, waveform.frame_rate_hz());
        assert_eq!(
            (num_frames * num_channels as usize) as usize,
            waveform.to_interleaved_samples().len()
        );
    }

    /// Try decoding a 32-channel OGG file.
    #[test]
    fn test_32_channel_tone_ogg_default_1() {
        let waveform_args = WaveformArgs {
            decoding_backend: DECODING_BACKEND_FFMPEG,
            ..Default::default()
        };
        let result = Waveform::from_file(TTCT_FILENAME_OGG, waveform_args);
        assert_waveform(
            result,
            TTCT_NUM_CHANNELS,
            TTCT_NUM_FRAMES,
            TTCT_FRAME_RATE_HZ,
        );
    }

    /// Try decoding a 32 channel WAV file.
    #[test]
    fn test_32_channel_tone_wav_default_1() {
        let waveform_args = WaveformArgs {
            decoding_backend: DECODING_BACKEND_FFMPEG,
            ..Default::default()
        };
        let result = Waveform::from_file(TTCT_FILENAME_WAV, waveform_args);
        assert_waveform(
            result,
            TTCT_NUM_CHANNELS,
            TTCT_NUM_FRAMES,
            TTCT_FRAME_RATE_HZ,
        );
    }
}