Skip to content

Commit

Permalink
Duration ops
Browse files Browse the repository at this point in the history
  • Loading branch information
tsionyx committed Jan 24, 2024
1 parent 3e2be3d commit 673d820
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 9 deletions.
13 changes: 12 additions & 1 deletion examples/hsom-exercises/ch6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ fn retro_pitches(m: Music) -> Option<Music> {

#[cfg(test)]
mod tests {
use super::*;
use musik::{Dur, Octave};

use super::*;

#[test]
fn test_retro_pitches() {
let oc4 = Octave::ONE_LINED;
Expand All @@ -200,4 +201,14 @@ mod tests {
])
);
}

#[test]
fn strip_zeros() {
let oc4 = Octave::ONE_LINED;
let m = M::C(oc4, Dur::EN) & M::D(oc4, Dur::EN).times(16);
assert_eq!(
m.drop(Dur::HN).take(Dur::HN).remove_zeros(),
M::D(oc4, Dur::EN).times(4).remove_zeros()
);
}
}
168 changes: 160 additions & 8 deletions src/music.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
cmp::Ordering,
convert::TryFrom,
ops::{Add, AddAssign, BitAnd, BitOr, Sub},
ops::{Add, AddAssign, BitAnd, BitOr, Div, Mul, Sub},
};

use num_rational::Ratio;
Expand Down Expand Up @@ -333,6 +334,18 @@ impl From<AbsPitch> for Pitch {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Dur(u8, u8);

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

impl Ord for Dur {
fn cmp(&self, other: &Self) -> Ordering {
self.into_rational().cmp(&other.into_rational())
}
}

impl Dur {
const fn from_integer(i: u8) -> Self {
Self(i, 1)
Expand Down Expand Up @@ -399,6 +412,14 @@ impl Dur {
Self::new(self.0, self.1 << 1)
}
}

pub fn saturating_sub(self, rhs: Self) -> Self {
if self > rhs {
self - rhs
} else {
Self::ZERO
}
}
}

impl From<Ratio<u8>> for Dur {
Expand All @@ -407,6 +428,54 @@ impl From<Ratio<u8>> for Dur {
}
}

impl Add<Self> for Dur {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
(self.into_rational() + rhs.into_rational()).into()
}
}

impl Sub<Self> for Dur {
type Output = Self;

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

impl Mul<u8> for Dur {
type Output = Self;

fn mul(self, rhs: u8) -> Self::Output {
(self.into_rational() * rhs).into()
}
}

impl Mul<Ratio<u8>> for Dur {
type Output = Self;

fn mul(self, rhs: Ratio<u8>) -> Self::Output {
(self.into_rational() * rhs).into()
}
}

impl Div<u8> for Dur {
type Output = Self;

fn div(self, rhs: u8) -> Self::Output {
(self.into_rational() / rhs).into()
}
}

impl Div<Ratio<u8>> for Dur {
type Output = Self;

fn div(self, rhs: Ratio<u8>) -> Self::Output {
(self.into_rational() / rhs).into()
}
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Primitive<P> {
Note(Dur, P),
Expand Down Expand Up @@ -542,13 +611,8 @@ impl Music {

pub fn grace_note(&self, offset: AbsPitch, grace_fraction: Ratio<u8>) -> Result<Self, String> {
if let Self::Prim(Primitive::Note(d, p)) = self {
Ok(Self::note(
(grace_fraction * d.into_rational()).into(),
p.trans(offset.into()),
) & Self::note(
((Ratio::from_integer(1) - grace_fraction) * d.into_rational()).into(),
*p,
))
Ok(Self::note(*d * grace_fraction, p.trans(offset.into()))
& Self::note(*d * (Ratio::from_integer(1) - grace_fraction), *p))
} else {
Err("Can only add a grace note to a note".into())
}
Expand Down Expand Up @@ -613,6 +677,94 @@ impl<P> Music<P> {
pub fn retrograde(self) -> Self {
Self::line(Vec::from(self).into_iter().rev().collect())
}

pub fn duration(&self) -> Dur {
match self {
Self::Prim(Primitive::Note(d, _)) => *d,
Self::Prim(Primitive::Rest(d)) => *d,
Self::Sequential(m1, m2) => m1.duration() + m2.duration(),
Self::Parallel(m1, m2) => m1.duration().max(m2.duration()),
Self::Modify(Control::Tempo(r), m) => m.duration() / *r,
Self::Modify(_, m) => m.duration(),
}
}

pub fn reverse(self) -> Self {
match self {
n @ Self::Prim(_) => n,
Self::Sequential(m1, m2) => m2.reverse() & m1.reverse(),
Self::Parallel(m1, m2) => {
let d1 = m1.duration();
let d2 = m2.duration();
if d1 > d2 {
m1.reverse() | (Self::rest(d1 - d2) & m2.reverse())
} else {
(Self::rest(d2 - d1) & m1.reverse()) | m2.reverse()
}
}
Self::Modify(c, m) => Self::Modify(c, Box::new(m.reverse())),
}
}

/// Take the first N whole beats and drop the other
pub fn take(self, n: Dur) -> Self {
if n == Dur::ZERO {
return Self::rest(Dur::ZERO);
}

match self {
Self::Prim(Primitive::Note(d, p)) => Self::note(d.min(n), p),
Self::Prim(Primitive::Rest(d)) => Self::rest(d.min(n)),
Self::Sequential(m1, m2) => {
let m1 = m1.take(n);
let m2 = m2.take(n - m1.duration());
m1 & m2
}
Self::Parallel(m1, m2) => m1.take(n) | m2.take(n),
Self::Modify(Control::Tempo(r), m) => m.take(n * r).with_tempo(r),
Self::Modify(c, m) => Self::Modify(c, Box::new(m.take(n))),
}
}

/// Drop the first N whole beats and take the other
pub fn drop(self, n: Dur) -> Self {
if n == Dur::ZERO {
return self;
}

match self {
Self::Prim(Primitive::Note(d, p)) => Self::note(d.saturating_sub(n), p),
Self::Prim(Primitive::Rest(d)) => Self::rest(d.saturating_sub(n)),
Self::Sequential(m1, m2) => {
let m2 = (*m2).drop(n.saturating_sub(m1.duration()));
(*m1).drop(n) & m2
}
Self::Parallel(m1, m2) => (*m1).drop(n) | (*m2).drop(n),
Self::Modify(Control::Tempo(r), m) => (*m).drop(n * r).with_tempo(r),
Self::Modify(c, m) => Self::Modify(c, Box::new((*m).drop(n))),
}
}

pub fn remove_zeros(self) -> Self {
match self {
n @ Self::Prim(_) => n,
Self::Sequential(m1, m2) => match (m1.remove_zeros(), m2.remove_zeros()) {
(Self::Prim(Primitive::Note(Dur::ZERO, _)), m) => m,
(Self::Prim(Primitive::Rest(Dur::ZERO)), m) => m,
(m, Self::Prim(Primitive::Note(Dur::ZERO, _))) => m,
(m, Self::Prim(Primitive::Rest(Dur::ZERO))) => m,
(m1, m2) => m1 & m2,
},
Self::Parallel(m1, m2) => match (m1.remove_zeros(), m2.remove_zeros()) {
(Self::Prim(Primitive::Note(Dur::ZERO, _)), m) => m,
(Self::Prim(Primitive::Rest(Dur::ZERO)), m) => m,
(m, Self::Prim(Primitive::Note(Dur::ZERO, _))) => m,
(m, Self::Prim(Primitive::Rest(Dur::ZERO))) => m,
(m1, m2) => m1 | m2,
},
Self::Modify(c, m) => Self::Modify(c, Box::new(m.remove_zeros())),
}
}
}

impl<P: Clone> Music<P> {
Expand Down

0 comments on commit 673d820

Please sign in to comment.