saturation 0.2.3

Real-time saturation and clipping designed for use with vst's
Documentation
use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};

use num::{Float, traits::FloatConst};

use crate::{Saturate, SaturateMut};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct SoftExp;

impl<F> SaturateMut<F, Range<F>> for SoftExp
where
    F: Float + FloatConst
{
    fn saturate_mut(&mut self, x: F, range: Range<F>) -> F
    {
        self.saturate(x, range)
    }
}
impl<F> Saturate<F, Range<F>> for SoftExp
where
    F: Float + FloatConst
{
    #[inline]
    fn saturate(&self, x: F, range: Range<F>) -> F
    {
        if x.is_sign_negative()
        {
            self.saturate(x, range.start..)
        }
        else
        {
            self.saturate(x, ..range.end)
        }
    }
}
impl<F> SaturateMut<F, RangeFrom<F>> for SoftExp
where
    F: Float + FloatConst
{
    fn saturate_mut(&mut self, x: F, range: RangeFrom<F>) -> F
    {
        self.saturate(x, range)
    }
}
impl<F> Saturate<F, RangeFrom<F>> for SoftExp
where
    F: Float + FloatConst
{
    #[inline]
    fn saturate(&self, mut x: F, range: RangeFrom<F>) -> F
    {
        assert!(range.start <= F::zero(), "Lower bound must be negative");
        x = x.max(range.start);
        x + (range.start - x).exp() - range.start.exp()
    }
}
impl<F> SaturateMut<F, RangeTo<F>> for SoftExp
where
    F: Float + FloatConst
{
    fn saturate_mut(&mut self, x: F, range: RangeTo<F>) -> F
    {
        self.saturate(x, range)
    }
}
impl<F> Saturate<F, RangeTo<F>> for SoftExp
where
    F: Float + FloatConst
{
    #[inline]
    fn saturate(&self, mut x: F, range: RangeTo<F>) -> F
    {
        assert!(range.end >= F::zero(), "Upper bound must be positive");
        x = x.min(range.end);
        x - (x - range.end).exp() + (-range.end).exp()
    }
}

impl<F> SaturateMut<F, RangeInclusive<F>> for SoftExp
where
    F: Float + FloatConst
{
    fn saturate_mut(&mut self, x: F, range: RangeInclusive<F>) -> F
    {
        self.saturate(x, range)
    }
}
impl<F> Saturate<F, RangeInclusive<F>> for SoftExp
where
    F: Float + FloatConst
{
    #[inline]
    fn saturate(&self, x: F, range: RangeInclusive<F>) -> F
    {
        self.saturate(x, *range.start()..*range.end())
    }
}
impl<F> SaturateMut<F, RangeToInclusive<F>> for SoftExp
where
    F: Float + FloatConst
{
    fn saturate_mut(&mut self, x: F, range: RangeToInclusive<F>) -> F
    {
        self.saturate(x, range)
    }
}
impl<F> Saturate<F, RangeToInclusive<F>> for SoftExp
where
    F: Float + FloatConst
{
    #[inline]
    fn saturate(&self, x: F, range: RangeToInclusive<F>) -> F
    {
        self.saturate(x, ..range.end)
    }
}
impl<F> SaturateMut<F, RangeFull> for SoftExp
where
    F: Float + FloatConst
{
    fn saturate_mut(&mut self, x: F, range: RangeFull) -> F
    {
        self.saturate(x, range)
    }
}
impl<F> Saturate<F, RangeFull> for SoftExp
where
    F: Float + FloatConst
{
    #[inline]
    fn saturate(&self, x: F, RangeFull: RangeFull) -> F
    {
        x
    }
}

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

    #[test]
    fn it_works()
    {
        let range = -2.0..2.0;
        let clip = -1.2..1.0;

        crate::tests::plot(
            "SoftExp",
            range,
            |x| [
                SoftExp.saturate(x, clip.clone().start..),
                SoftExp.saturate(x, clip.clone()),
                SoftExp.saturate(x, ..clip.clone().end),
            ]
        )
    }
}