blob: ea25acd8da95612c433517a6124e11dcdd340c76 [file] [log] [blame]
//! Use the [**Sample**](./trait.Sample.html) trait to remain generic over sample types, easily
//! access sample type conversions, apply basic audio operations and more.
//!
//! The **Sample** trait is the core abstraction throughout dasp on which most other abstractions
//! are based.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(core_intrinsics))]
#[cfg(not(feature = "std"))]
extern crate alloc;
pub use conv::{Duplex, FromSample, ToSample};
pub use types::{I24, I48, U24, U48};
pub mod conv;
mod ops;
pub mod types;
/// A trait for working generically across different **Sample** format types.
///
/// Provides methods for converting to and from any type that implements the
/// [`FromSample`](./trait.FromSample.html) trait and provides methods for performing signal
/// amplitude addition and multiplication.
///
/// # Example
///
/// ```rust
/// use dasp_sample::{I24, Sample};
///
/// fn main() {
/// assert_eq!((-1.0).to_sample::<u8>(), 0);
/// assert_eq!(0.0.to_sample::<u8>(), 128);
/// assert_eq!(0i32.to_sample::<u32>(), 2_147_483_648);
/// assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0));
/// assert_eq!(0.0, Sample::EQUILIBRIUM);
/// }
/// ```
pub trait Sample: Copy + Clone + PartialOrd + PartialEq {
/// When summing two samples of a signal together, it is necessary for both samples to be
/// represented in some signed format. This associated `Addition` type represents the format to
/// which `Self` should be converted for optimal `Addition` performance.
///
/// For example, u32's optimal `Addition` type would be i32, u8's would be i8, f32's would be
/// f32, etc.
///
/// Specifying this as an associated type allows us to automatically determine the optimal,
/// lossless Addition format type for summing any two unique `Sample` types together.
///
/// As a user of the `sample` crate, you will never need to be concerned with this type unless
/// you are defining your own unique `Sample` type(s).
type Signed: SignedSample + Duplex<Self>;
/// When multiplying two samples of a signal together, it is necessary for both samples to be
/// represented in some signed, floating-point format. This associated `Multiplication` type
/// represents the format to which `Self` should be converted for optimal `Multiplication`
/// performance.
///
/// For example, u32's optimal `Multiplication` type would be f32, u64's would be f64, i8's
/// would be f32, etc.
///
/// Specifying this as an associated type allows us to automatically determine the optimal,
/// lossless Multiplication format type for multiplying any two unique `Sample` types together.
///
/// As a user of the `sample` crate, you will never need to be concerned with this type unless
/// you are defining your own unique `Sample` type(s).
type Float: FloatSample + Duplex<Self>;
/// The equilibrium value for the wave that this `Sample` type represents. This is normally the
/// value that is equal distance from both the min and max ranges of the sample.
///
/// # Example
///
/// ```rust
/// use dasp_sample::Sample;
///
/// fn main() {
/// assert_eq!(0.0, f32::EQUILIBRIUM);
/// assert_eq!(0, i32::EQUILIBRIUM);
/// assert_eq!(128, u8::EQUILIBRIUM);
/// assert_eq!(32_768_u16, Sample::EQUILIBRIUM);
/// }
/// ```
///
/// **Note:** This will likely be changed to an "associated const" if the feature lands.
const EQUILIBRIUM: Self;
/// The multiplicative identity of the signal.
///
/// In other words: A value which when used to scale/multiply the amplitude or frequency of a
/// signal, returns the same signal.
///
/// This is useful as a default, non-affecting amplitude or frequency multiplier.
///
/// # Example
///
/// ```rust
/// use dasp_sample::{Sample, U48};
///
/// fn main() {
/// assert_eq!(1.0, f32::IDENTITY);
/// assert_eq!(1.0, i8::IDENTITY);
/// assert_eq!(1.0, u8::IDENTITY);
/// assert_eq!(1.0, U48::IDENTITY);
/// }
/// ```
const IDENTITY: Self::Float = <Self::Float as FloatSample>::IDENTITY;
/// Convert `self` to any type that implements `FromSample<Self>`.
///
/// Find more details on type-specific conversion ranges and caveats in the `conv` module.
///
/// # Example
///
/// ```rust
/// use dasp_sample::Sample;
///
/// fn main() {
/// assert_eq!(0.0.to_sample::<i32>(), 0);
/// assert_eq!(0.0.to_sample::<u8>(), 128);
/// assert_eq!((-1.0).to_sample::<u8>(), 0);
/// }
/// ```
#[inline]
fn to_sample<S>(self) -> S
where
Self: ToSample<S>,
{
self.to_sample_()
}
/// Create a `Self` from any type that implements `ToSample<Self>`.
///
/// Find more details on type-specific conversion ranges and caveats in the `conv` module.
///
/// # Example
///
/// ```rust
/// use dasp_sample::{Sample, I24};
///
/// fn main() {
/// assert_eq!(f32::from_sample(128_u8), 0.0);
/// assert_eq!(i8::from_sample(-1.0), -128);
/// assert_eq!(I24::from_sample(0.0), I24::new(0).unwrap());
/// }
/// ```
#[inline]
fn from_sample<S>(s: S) -> Self
where
Self: FromSample<S>,
{
FromSample::from_sample_(s)
}
/// Converts `self` to the equivalent `Sample` in the associated `Signed` format.
///
/// This is a simple wrapper around `Sample::to_sample` which may provide extra convenience in
/// some cases, particularly for assisting type inference.
///
/// # Example
///
/// ```rust
/// use dasp_sample::Sample;
///
/// fn main() {
/// assert_eq!(128_u8.to_signed_sample(), 0i8);
/// }
/// ```
fn to_signed_sample(self) -> Self::Signed {
self.to_sample()
}
/// Converts `self` to the equivalent `Sample` in the associated `Float` format.
///
/// This is a simple wrapper around `Sample::to_sample` which may provide extra convenience in
/// some cases, particularly for assisting type inference.
///
/// # Example
///
/// ```rust
/// use dasp_sample::Sample;
///
/// fn main() {
/// assert_eq!(128_u8.to_float_sample(), 0.0);
/// }
/// ```
fn to_float_sample(self) -> Self::Float {
self.to_sample()
}
/// Adds (or "offsets") the amplitude of the `Sample` by the given signed amplitude.
///
/// `Self` will be converted to `Self::Signed`, the addition will occur and then the result
/// will be converted back to `Self`. These conversions allow us to correctly handle the
/// addition of unsigned signal formats.
///
/// # Example
///
/// ```rust
/// use dasp_sample::Sample;
///
/// fn main() {
/// assert_eq!(0.25.add_amp(0.5), 0.75);
/// assert_eq!(192u8.add_amp(-128), 64);
/// }
/// ```
#[inline]
fn add_amp(self, amp: Self::Signed) -> Self {
let self_s = self.to_signed_sample();
(self_s + amp).to_sample()
}
/// Multiplies (or "scales") the amplitude of the `Sample` by the given float amplitude.
///
/// - `amp` > 1.0 amplifies the sample.
/// - `amp` < 1.0 attenuates the sample.
/// - `amp` == 1.0 yields the same sample.
/// - `amp` == 0.0 yields the `Sample::EQUILIBRIUM`.
///
/// `Self` will be converted to `Self::Float`, the multiplication will occur and then the
/// result will be converted back to `Self`. These conversions allow us to correctly handle the
/// multiplication of integral signal formats.
///
/// # Example
///
/// ```rust
/// use dasp_sample::Sample;
///
/// fn main() {
/// assert_eq!(64_i8.mul_amp(0.5), 32);
/// assert_eq!(0.5.mul_amp(-2.0), -1.0);
/// assert_eq!(64_u8.mul_amp(0.0), 128);
/// }
/// ```
#[inline]
fn mul_amp(self, amp: Self::Float) -> Self {
let self_f = self.to_float_sample();
(self_f * amp).to_sample()
}
}
/// A macro used to simplify the implementation of `Sample`.
macro_rules! impl_sample {
($($T:ty:
Signed: $Addition:ty,
Float: $Modulation:ty,
EQUILIBRIUM: $EQUILIBRIUM:expr),*) =>
{
$(
impl Sample for $T {
type Signed = $Addition;
type Float = $Modulation;
const EQUILIBRIUM: Self = $EQUILIBRIUM;
}
)*
}
}
// Expands to `Sample` implementations for all of the following types.
impl_sample! {
i8: Signed: i8, Float: f32, EQUILIBRIUM: 0,
i16: Signed: i16, Float: f32, EQUILIBRIUM: 0,
I24: Signed: I24, Float: f32, EQUILIBRIUM: types::i24::EQUILIBRIUM,
i32: Signed: i32, Float: f32, EQUILIBRIUM: 0,
I48: Signed: I48, Float: f64, EQUILIBRIUM: types::i48::EQUILIBRIUM,
i64: Signed: i64, Float: f64, EQUILIBRIUM: 0,
u8: Signed: i8, Float: f32, EQUILIBRIUM: 128,
u16: Signed: i16, Float: f32, EQUILIBRIUM: 32_768,
U24: Signed: i32, Float: f32, EQUILIBRIUM: types::u24::EQUILIBRIUM,
u32: Signed: i32, Float: f32, EQUILIBRIUM: 2_147_483_648,
U48: Signed: i64, Float: f64, EQUILIBRIUM: types::u48::EQUILIBRIUM,
u64: Signed: i64, Float: f64, EQUILIBRIUM: 9_223_372_036_854_775_808,
f32: Signed: f32, Float: f32, EQUILIBRIUM: 0.0,
f64: Signed: f64, Float: f64, EQUILIBRIUM: 0.0
}
/// Integral and floating-point **Sample** format types whose equilibrium is at 0.
///
/// **Sample**s often need to be converted to some mutual **SignedSample** type for signal
/// addition.
pub trait SignedSample:
Sample<Signed = Self>
+ core::ops::Add<Output = Self>
+ core::ops::Sub<Output = Self>
+ core::ops::Neg<Output = Self>
{
}
macro_rules! impl_signed_sample { ($($T:ty)*) => { $( impl SignedSample for $T {} )* } }
impl_signed_sample!(i8 i16 I24 i32 I48 i64 f32 f64);
/// Sample format types represented as floating point numbers.
///
/// **Sample**s often need to be converted to some mutual **FloatSample** type for signal scaling
/// and modulation.
pub trait FloatSample:
Sample<Signed = Self, Float = Self>
+ SignedSample
+ core::ops::Mul<Output = Self>
+ core::ops::Div<Output = Self>
+ Duplex<f32>
+ Duplex<f64>
{
/// Represents the multiplicative identity of the floating point signal.
const IDENTITY: Self;
/// Calculate the square root of `Self`.
fn sample_sqrt(self) -> Self;
}
impl FloatSample for f32 {
const IDENTITY: Self = 1.0;
#[inline]
fn sample_sqrt(self) -> Self {
ops::f32::sqrt(self)
}
}
impl FloatSample for f64 {
const IDENTITY: Self = 1.0;
#[inline]
fn sample_sqrt(self) -> Self {
ops::f64::sqrt(self)
}
}