audiofp 0.3.0

Audio fingerprinting SDK: Wang, Panako, Haitsma–Kalker, neural (ONNX), watermark, streaming.
Documentation
//! Shared value types used across the `audiofp` crate.

use core::num::NonZeroU32;

/// A sample rate in hertz, guaranteed non-zero.
///
/// Use one of the `HZ_*` constants for the rates `audiofp` supports out of the
/// box, or [`SampleRate::new`] to validate an arbitrary value.
///
/// # Example
///
/// ```
/// use audiofp::SampleRate;
///
/// assert_eq!(SampleRate::HZ_44100.hz(), 44_100);
/// assert!(SampleRate::new(0).is_none());
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SampleRate(pub NonZeroU32);

impl SampleRate {
    /// 8 kHz — the rate `audiofp`'s classical fingerprinters consume.
    pub const HZ_8000: SampleRate = unsafe { SampleRate(NonZeroU32::new_unchecked(8_000)) };

    /// 11.025 kHz.
    pub const HZ_11025: SampleRate = unsafe { SampleRate(NonZeroU32::new_unchecked(11_025)) };

    /// 16 kHz — typical speech rate.
    pub const HZ_16000: SampleRate = unsafe { SampleRate(NonZeroU32::new_unchecked(16_000)) };

    /// 22.05 kHz — common for music workflows.
    pub const HZ_22050: SampleRate = unsafe { SampleRate(NonZeroU32::new_unchecked(22_050)) };

    /// 44.1 kHz — CD-quality audio.
    pub const HZ_44100: SampleRate = unsafe { SampleRate(NonZeroU32::new_unchecked(44_100)) };

    /// 48 kHz — DAT / professional audio.
    pub const HZ_48000: SampleRate = unsafe { SampleRate(NonZeroU32::new_unchecked(48_000)) };

    /// Build a [`SampleRate`] from any non-zero `u32`.
    ///
    /// Returns `None` if `hz == 0`.
    ///
    /// # Example
    ///
    /// ```
    /// use audiofp::SampleRate;
    ///
    /// assert_eq!(SampleRate::new(32_000).unwrap().hz(), 32_000);
    /// assert!(SampleRate::new(0).is_none());
    /// ```
    #[must_use]
    pub const fn new(hz: u32) -> Option<SampleRate> {
        match NonZeroU32::new(hz) {
            Some(n) => Some(SampleRate(n)),
            None => None,
        }
    }

    /// Return the rate in hertz.
    ///
    /// # Example
    ///
    /// ```
    /// use audiofp::SampleRate;
    ///
    /// assert_eq!(SampleRate::HZ_48000.hz(), 48_000);
    /// ```
    #[must_use]
    pub const fn hz(self) -> u32 {
        self.0.get()
    }
}

/// A borrowed view of a mono PCM buffer in `[-1.0, 1.0]`.
///
/// Channel mixing is the caller's job — every public `audiofp` API takes mono
/// `f32`. Multi-channel inputs must be downmixed (helpers will live in the
/// streaming module once it lands).
///
/// # Example
///
/// ```
/// use audiofp::{AudioBuffer, SampleRate};
///
/// let samples = vec![0.0_f32; 16_000];
/// let buf = AudioBuffer { samples: &samples, rate: SampleRate::HZ_16000 };
/// assert_eq!(buf.samples.len(), 16_000);
/// assert_eq!(buf.rate.hz(), 16_000);
/// ```
#[derive(Clone, Debug)]
pub struct AudioBuffer<'a> {
    /// Mono samples in `[-1.0, 1.0]`. Out-of-range values are not rejected
    /// here; downstream code clips or normalises as needed.
    pub samples: &'a [f32],

    /// Sample rate the samples were captured at.
    pub rate: SampleRate,
}

/// A timestamp in milliseconds since the start of a stream.
///
/// `u64` gives roughly 584 million years of headroom — long enough.
///
/// # Example
///
/// ```
/// use audiofp::TimestampMs;
///
/// let t = TimestampMs(1_500);
/// assert_eq!(t.0, 1_500);
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TimestampMs(pub u64);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sample_rate_constants_match_their_names() {
        assert_eq!(SampleRate::HZ_8000.hz(), 8_000);
        assert_eq!(SampleRate::HZ_11025.hz(), 11_025);
        assert_eq!(SampleRate::HZ_16000.hz(), 16_000);
        assert_eq!(SampleRate::HZ_22050.hz(), 22_050);
        assert_eq!(SampleRate::HZ_44100.hz(), 44_100);
        assert_eq!(SampleRate::HZ_48000.hz(), 48_000);
    }

    #[test]
    fn sample_rate_eq() {
        let a = SampleRate::HZ_44100;
        let b = SampleRate::new(44_100).unwrap();
        let c = SampleRate::HZ_48000;
        assert_eq!(a, b);
        assert_ne!(a, c);
    }

    #[test]
    fn sample_rate_new_rejects_zero() {
        assert!(SampleRate::new(0).is_none());
        assert_eq!(SampleRate::new(1).unwrap().hz(), 1);
    }

    #[test]
    fn timestamp_ord() {
        let a = TimestampMs(100);
        let b = TimestampMs(200);
        assert!(a < b);
        assert_eq!(a.cmp(&b), core::cmp::Ordering::Less);
        assert_eq!(b.cmp(&a), core::cmp::Ordering::Greater);
        assert_eq!(a.cmp(&a), core::cmp::Ordering::Equal);
    }

    #[test]
    fn audio_buffer_borrow() {
        let samples = alloc::vec![0.1_f32, 0.2, 0.3];
        let buf = AudioBuffer {
            samples: &samples,
            rate: SampleRate::HZ_16000,
        };
        // The buffer is a borrowed view — original samples still owned.
        assert_eq!(buf.samples.len(), 3);
        assert_eq!(buf.rate.hz(), 16_000);
        // Vec still usable via the original binding after the buffer's
        // last use.
        assert_eq!(samples.len(), 3);
    }
}