#audio-samples #sample #lossless

sampletypes

A library for audio processing, specifically for sample format conversion (e.g. i16 scale up to i32, i16 to f32, implementation of i24, etc.)

5 releases

Uses new Rust 2024

new 0.1.3 May 3, 2025
0.1.2 May 1, 2025
0.1.0 Apr 30, 2025
0.0.8 Apr 30, 2025

#355 in Audio

Download history 756/week @ 2025-04-26

756 downloads per month
Used in rustwav

Custom license

82KB
2K SLoC

SampleTypes

A library for audio processing, specifically for sample format conversion (e.g. i16 scale up to i32, i16 to f32, implementation of i24, etc.)

Usage

The main thing is the SampleType trait. Use its methods to scale the samples. The SampleType is designed for you to use as a generic type for your audio processors.

Additionally, we implemented i24 and u24 for your convenience to handle 24-bit audio samples.

The SampleType trait

  • The SampleType trait implements all of the basic numeric types: i8, i16, i32, i64, i128, u8, u16, u32, u64, u128 and the additionally designed i24 and u24 struct.
  • The to() conversion functions are for scaling the samples to the specific sample format.
  • The as() casting functions are for casting the sample types.
  • Every function is small enough to apply #[inline(always)], and for the best compilation result, in Cargo.toml for this project, in the [profile.release] section, lto = "fat" is specified for running performance.

Trait definition

/// * The `SampleType` for audio processing.
/// * The `to_*()` methods are for scaling the sample to the another format.
/// * The `as_*()` methods are for casting the sample to the another format.
pub trait SampleType: SampleFrom {
    /// The type we are implementating for
    type ImplFor;

    /// The longer type, e.g. for `i8`, the longer type is `i16`
    type Longer;

    /// The shorter type, e.g. for `i16`, the shorter type is `i8`
    type Shorter;

    /// The signed type, e.g. for `u32`, the signed type is `i32`.
    type Signed;

    /// The Unsigned type, e.g. for `i32`, the unsigned type is `u32`.
    type Unsigned;

    /// The middle number, e.g. for `u16`, the middle number is `32768`.
    const MIDNUM: Self;

    /// The type name, to avoid using `std::any::type_name`
    const TYPE_NAME: &'static str;

    /// Create a new sample, the value is the middle value of the range of the format.
    fn new() -> Self;

    /// Create a new sample, the value is zero.
    fn zero() -> Self;

    /// Scale a sample to this specified format.
    fn scale_from<T>(v: T) -> Self where T: SampleType;

    /// Cast a sample to this specified format.
    fn cast_from<T>(v: T) -> Self where T: SampleType;

    /// Get the average value of two samples.
    fn average(s1: Self, s2: Self) -> Self;

    /// Get the average value from a sample array.
    fn average_arr(arr: &[Self]) -> Self;

    /// Scale to `i8` range
    fn to_i8 (self) -> i8 ;

    /// Scale to `i16` range
    fn to_i16(self) -> i16;

    /// Scale to `i24` range
    fn to_i24(self) -> i24;

    /// Scale to `i32` range
    fn to_i32(self) -> i32;

    /// Scale to `i64` range
    fn to_i64(self) -> i64;

    /// Scale to `u8` range
    fn to_u8 (self) -> u8 ;

    /// Scale to `u16` range
    fn to_u16(self) -> u16;

    /// Scale to `u24` range
    fn to_u24(self) -> u24;

    /// Scale to `u32` range
    fn to_u32(self) -> u32;

    /// Scale to `u64` range
    fn to_u64(self) -> u64;

    /// Scale to `[-1.0, 1.0]` range
    fn to_f32(self) -> f32;

    /// Scale to `[-1.0, 1.0]` range
    fn to_f64(self) -> f64;

    /// Scale to `i128` range
    fn to_i128(self) -> i128;

    /// Scale to `u128` range
    fn to_u128(self) -> u128;

    /// Cast to `i8`
    fn as_i8 (self) -> i8 ;

    /// Cast to `i16`
    fn as_i16(self) -> i16;

    /// Cast to `i24`
    fn as_i24(self) -> i24;

    /// Cast to `i32`
    fn as_i32(self) -> i32;

    /// Cast to `i64`
    fn as_i64(self) -> i64;

    /// Cast to `u8`
    fn as_u8 (self) -> u8 ;

    /// Cast to `u16`
    fn as_u16(self) -> u16;

    /// Cast to `u24`
    fn as_u24(self) -> u24;

    /// Cast to `u32`
    fn as_u32(self) -> u32;

    /// Cast to `u64`
    fn as_u64(self) -> u64;

    /// Cast to `f32`
    fn as_f32(self) -> f32;

    /// Cast to `f64`
    fn as_f64(self) -> f64;

    /// Cast to `i128`
    fn as_i128(self) -> i128;

    /// Cast to `u128`
    fn as_u128(self) -> u128;

    /// Get the size of the sample in bytes
    fn sizeof(self) -> usize {size_of::<Self>()}

    /// Scale to a longer type, the longest type is `i128` or `u128`
    fn to_longer(self) -> Self::Longer;

    /// Scale to a shorter type, the shortest type is `i8` or `u8`
    fn to_shorter(self) -> Self::Shorter;

    /// Cast to a longer type, the longest type is `i128` or `u128`
    fn as_longer(self) -> Self::Longer;

    /// Cast to a shorter type, the shortest type is `i8` or `u8`
    fn as_shorter(self) -> Self::Shorter;

    /// Is this type a signed type?
    fn is_signed() -> bool;

    /// Is this type an unsigned type?
    fn is_unsigned() -> bool;

    /// Is this type an integer type?
    fn is_integer() -> bool;

    /// Is this type an IEEE 754 floating point number type?
    fn is_float() -> bool;

    /// Convert to a signed number type. No effects to `f32` and `f64`
    fn to_signed(self) -> Self::Signed;

    /// Convert to an unsigned number type. No effects to `f32` and `f64`
    fn to_unsigned(self) -> Self::Unsigned;

    /// Sine wave generator
    /// * The input `x` doesn't need to be related to PI.
    /// * e.g. The type is `i8`, the value is -128, then you will get sin(-PI).
    /// * e.g. The type is `u8`, the value is 0, then you will get sin(-PI) too.
    fn sin<S>(self) -> S where S: SampleType;

    /// Cosine wave generator
    /// * The input `x` doesn't need to be related to PI.
    /// * e.g. The type is `i8`, the value is -128, then you will get cos(-PI).
    /// * e.g. The type is `u8`, the value is 0, then you will get cos(-PI) too.
    fn cos<S>(self) -> S where S: SampleType;

    /// interpolate between `self` to `target` by `s`
    fn interpolate(self, target: Self, s: f64) -> Self;

    /// Read from a reader by little-endian
    fn read_le<T>(r: &mut T) -> Result<Self, Error> where T: Read + ?Sized;

    /// Read from a reader by big-endian
    fn read_be<T>(r: &mut T) -> Result<Self, Error> where T: Read + ?Sized;

    /// Write to a writer by little-endian
    fn write_le<T>(self, w: &mut T) -> Result<(), Error> where T: Write + ?Sized;

    /// Write to a writer by big-endian
    fn write_be<T>(self, w: &mut T) -> Result<(), Error> where T: Write + ?Sized;
}

No runtime deps