euphony-units 0.1.1

Core types and traits for music composition
Documentation
macro_rules! new_ratio {
    ($name:ident, $inner:ty) => {
        new_ratio_struct!($name, $inner);
        new_ratio_methods!($name, $inner);
        new_ratio_ops!($name, $inner);
        new_ratio_conversions!($name, $inner);
    };
}

macro_rules! new_ratio_struct {
    ($name:ident, $inner:ty) => {
        #[derive(Clone, Copy, Eq)]
        pub struct $name(pub $inner, pub $inner);

        impl Default for $name {
            fn default() -> Self {
                Self(0, 1)
            }
        }

        impl core::fmt::Debug for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
                write!(f, "{}({})", stringify!($name), self)
            }
        }

        impl core::fmt::Display for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
                self.as_ratio().fmt(f)
            }
        }

        impl PartialEq for $name {
            fn eq(&self, other: &Self) -> bool {
                self.cmp(other) == core::cmp::Ordering::Equal
            }
        }

        impl PartialOrd for $name {
            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
                Some(self.cmp(&other))
            }
        }

        impl Ord for $name {
            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
                self.reduce().as_ratio().cmp(&other.reduce().as_ratio())
            }
        }

        impl core::hash::Hash for $name {
            fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
                self.0.hash(hasher);
                self.1.hash(hasher);
            }
        }
    };
}

macro_rules! new_ratio_methods {
    ($name:ident, $inner:ty) => {
        impl $name {
            pub fn new<Value: Into<Self>>(value: Value) -> Self {
                value.into()
            }

            pub fn reduce(self) -> Self {
                self.as_ratio().reduce().into()
            }

            pub fn simplify(self, denominator: $inner) -> Self {
                self.as_ratio().simplify(denominator).into()
            }

            pub fn truncate(self) -> Self {
                self.as_ratio().truncate().into()
            }

            pub fn ceil(self) -> Self {
                self.as_ratio().ceil().into()
            }

            pub fn is_whole(&self) -> bool {
                self.as_ratio().is_whole()
            }

            pub fn whole(self) -> $inner {
                self.as_ratio().whole()
            }

            pub fn try_into_whole(&self) -> Option<$inner> {
                if self.is_whole() {
                    Some(self.whole())
                } else {
                    None
                }
            }

            pub fn fraction(self) -> Self {
                self.as_ratio().fraction().into()
            }

            pub fn as_ratio(self) -> $crate::ratio::Ratio<$inner> {
                $crate::ratio::Ratio(self.0, self.1)
            }

            pub fn as_f32(self) -> f32 {
                self.0 as f32 / self.1 as f32
            }

            pub fn as_f64(self) -> f64 {
                self.0 as f64 / self.1 as f64
            }
        }

        impl From<$name> for f32 {
            fn from(value: $name) -> f32 {
                value.as_f32()
            }
        }

        impl From<$name> for f64 {
            fn from(value: $name) -> f64 {
                value.as_f64()
            }
        }
    };
}

macro_rules! new_ratio_ops {
    ($name:ident, $inner:ty) => {
        impl core::ops::Add for $name {
            type Output = $name;

            fn add(self, rhs: Self) -> Self {
                self.as_ratio().add(rhs.as_ratio()).into()
            }
        }

        impl core::ops::AddAssign for $name {
            fn add_assign(&mut self, rhs: Self) {
                *self = core::ops::Add::add(*self, rhs);
            }
        }

        impl core::ops::Sub for $name {
            type Output = $name;

            fn sub(self, rhs: Self) -> Self {
                self.as_ratio().sub(rhs.as_ratio()).into()
            }
        }

        impl core::ops::SubAssign for $name {
            fn sub_assign(&mut self, rhs: Self) {
                *self = core::ops::Sub::sub(*self, rhs);
            }
        }

        impl core::ops::Div for $name {
            type Output = $crate::ratio::Ratio<$inner>;

            fn div(self, rhs: Self) -> Self::Output {
                self.as_ratio().div(rhs.as_ratio())
            }
        }

        impl core::ops::Rem for $name {
            type Output = $crate::ratio::Ratio<$inner>;

            fn rem(self, rhs: Self) -> Self::Output {
                self.as_ratio().rem(rhs.as_ratio())
            }
        }
    };
}

macro_rules! new_ratio_conversions {
    ($name:ident, $inner:ty) => {
        new_ratio_conversion!($name, $inner, i8);
        new_ratio_conversion!($name, $inner, u8);
        new_ratio_conversion!($name, $inner, i16);
        new_ratio_conversion!($name, $inner, u16);
        new_ratio_conversion!($name, $inner, i32);
        new_ratio_conversion!($name, $inner, u32);
        new_ratio_conversion!($name, $inner, i64);
        new_ratio_conversion!($name, $inner, u64);
        new_ratio_conversion!($name, $inner, isize);
        new_ratio_conversion!($name, $inner, usize);
    };
}

macro_rules! new_ratio_conversion {
    ($name:ident, $inner:ty, $ty:ident) => {
        impl From<$ty> for $name {
            fn from(value: $ty) -> Self {
                use core::convert::TryInto;
                Self(
                    value
                        .try_into()
                        .expect(concat!("value should fit into a ", stringify!($inner))),
                    1,
                )
            }
        }

        impl From<$crate::ratio::Ratio<$ty>> for $name {
            fn from(value: $crate::ratio::Ratio<$ty>) -> Self {
                use core::convert::TryInto;
                Self(
                    (value.0)
                        .try_into()
                        .expect(concat!("value should fit into a ", stringify!($inner))),
                    (value.1)
                        .try_into()
                        .expect(concat!("value should fit into a ", stringify!($inner))),
                )
            }
        }

        impl From<($ty, $ty)> for $name {
            fn from(value: ($ty, $ty)) -> Self {
                let value: $crate::ratio::Ratio<$ty> = value.into();
                value.into()
            }
        }

        impl PartialEq<$ty> for $name {
            fn eq(&self, other: &$ty) -> bool {
                self.partial_cmp(other) == Some(core::cmp::Ordering::Equal)
            }
        }

        impl PartialOrd<$ty> for $name {
            fn partial_cmp(&self, other: &$ty) -> Option<core::cmp::Ordering> {
                use core::{cmp::Ordering::*, convert::TryInto};
                let other: $inner = (*other).try_into().ok()?;
                if self.1 == 1 {
                    self.0.partial_cmp(&other)
                } else {
                    Some(match (self.0 / self.1).partial_cmp(&other)? {
                        Equal | Greater => Greater,
                        Less => Less,
                    })
                }
            }
        }

        new_ratio_arithmetic!($name, Add, add, AddAssign, add_assign, $ty);
        new_ratio_arithmetic!($name, Sub, sub, SubAssign, sub_assign, $ty);
        new_ratio_arithmetic!($name, Mul, mul, MulAssign, mul_assign, $ty);
        new_ratio_arithmetic!($name, Div, div, DivAssign, div_assign, $ty);
        new_ratio_arithmetic!($name, Rem, rem, RemAssign, rem_assign, $ty);
    };
}

macro_rules! new_ratio_arithmetic {
    ($name:ident, $op:ident, $call:ident, $assign_op:ident, $assign:ident, $ty:ident) => {
        impl core::ops::$op<$ty> for $name {
            type Output = $name;

            fn $call(self, rhs: $ty) -> Self {
                let rhs: Self = rhs.into();
                self.as_ratio().$call(rhs.as_ratio()).into()
            }
        }

        impl core::ops::$op<$crate::ratio::Ratio<$ty>> for $name {
            type Output = $name;

            fn $call(self, rhs: $crate::ratio::Ratio<$ty>) -> Self {
                let rhs: Self = rhs.into();
                self.as_ratio().$call(rhs.as_ratio()).into()
            }
        }

        impl core::ops::$op<($ty, $ty)> for $name {
            type Output = $name;

            fn $call(self, rhs: ($ty, $ty)) -> Self {
                let rhs: Self = rhs.into();
                self.as_ratio().$call(rhs.as_ratio()).into()
            }
        }

        impl core::ops::$assign_op<$ty> for $name {
            fn $assign(&mut self, rhs: $ty) {
                *self = core::ops::$op::$call(*self, rhs);
            }
        }

        impl core::ops::$assign_op<$crate::ratio::Ratio<$ty>> for $name {
            fn $assign(&mut self, rhs: $crate::ratio::Ratio<$ty>) {
                *self = core::ops::$op::$call(*self, rhs);
            }
        }

        impl core::ops::$assign_op<($ty, $ty)> for $name {
            fn $assign(&mut self, rhs: ($ty, $ty)) {
                *self = core::ops::$op::$call(*self, rhs);
            }
        }
    };
}

macro_rules! new_extern_ratio_arithmetic {
    ($name:ident, $op:ident, $call:ident, $assign_op:ident, $assign:ident, $ty:ident) => {
        impl core::ops::$op<$ty> for $name {
            type Output = $name;

            fn $call(self, rhs: $ty) -> Self::Output {
                self.as_ratio().$call(rhs.as_ratio()).into()
            }
        }

        impl core::ops::$op<$name> for $ty {
            type Output = $name;

            fn $call(self, rhs: $name) -> Self::Output {
                self.as_ratio().$call(rhs.as_ratio()).into()
            }
        }

        impl core::ops::$assign_op<$ty> for $name {
            fn $assign(&mut self, rhs: $ty) {
                *self = core::ops::$op::$call(*self, rhs);
            }
        }
    };
}