Skip to content

Commit

Permalink
WIP: chapter 6: 6.2..6.4
Browse files Browse the repository at this point in the history
  • Loading branch information
tsionyx committed Jan 21, 2024
1 parent a98f285 commit 29e21d0
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 1 deletion.
122 changes: 121 additions & 1 deletion examples/hsom-exercises/ch6.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use musik::{Dur, Music, Octave};
use std::collections::HashSet;

use musik::{music::Primitive, Dur, Interval, Music, Octave};

type M = Music;

Expand Down Expand Up @@ -72,3 +74,121 @@ mod retro_invert {
assert_eq!(m.clone().invert_retro().retro_invert(), m);
}
}

/// Exercise 6.2
/// Define a function `properRow :: Music Pitch -> Bool`
/// that determines whether or not its argument is a “proper” twelve-tone row,
/// meaning that: (a) it must have exactly twelve notes,
/// and (b) each unique pitch class is used exactly once (regardless of the octave).
/// Enharmonically equivalent pitch classes are not considered unique.
/// You may assume that the `Music Pitch` value is generated by the function `line`
/// but note that rests are allowed.
fn is_proper_row(m: Music) -> bool {
let tones: Vec<_> = Vec::from(m)
.into_iter()
.map(|m| match m {
Music::Prim(Primitive::Note(_, p)) => {
let interval_with_c = (12 + Interval::from(p.class()).get_inner()) % 12;
Some(Some(interval_with_c))
}
Music::Prim(Primitive::Rest(_)) => Some(None),
_ => None,
})
.collect();

if tones.contains(&None) {
// an item is not a `Primitive`
return false;
}

// ignore the rests
let tones: Vec<_> = tones.into_iter().flatten().collect();
if tones.len() != 12 {
// proper row has size 12
return false;
}

let uniq: HashSet<_> = tones.iter().collect();
// proper row has all uniques PitchClass
uniq.len() == tones.len()
}

/// Exercise 6.3
/// Define a function `palin :: Music Pitch -> Bool`
/// that determines whether or not a given line
/// (as generated by the `line` function) is a palindrome or not.
/// You should ignore rests, and disregard note durations —
/// the main question is whether or not the melody is a palindrome.
fn is_palindrome(m: Music) -> bool {
let abs_pitches: Vec<_> = Vec::from(m)
.into_iter()
.map(|m| match m {
Music::Prim(Primitive::Note(_, p)) => Some(Some(p.abs())),
Music::Prim(Primitive::Rest(_)) => Some(None),
_ => None,
})
.collect();

if abs_pitches.contains(&None) {
// an item is not a `Primitive`
return false;
}

// ignore the rests
let abs_pitches: Vec<_> = abs_pitches.into_iter().flatten().collect();
abs_pitches.iter().copied().rev().collect::<Vec<_>>() == abs_pitches
}

/// Exercise 6.4
/// Define a function `retroPitches :: Music Pitch -> Music Pitch`
/// that reverses the pitches in a line, but maintains
/// the durations in the same order from beginning to end.
fn retro_pitches(m: Music) -> Option<Music> {
let abs_pitches = Vec::from(m)
.into_iter()
.map(|m| {
if let Music::Prim(p) = m {
Some(p)
} else {
None
}
})
.collect::<Option<Vec<_>>>()?;

let (durations, pitches): (Vec<_>, Vec<_>) = abs_pitches
.into_iter()
.map(|p| match p {
Primitive::Note(d, p) => (d, Some(p)),
Primitive::Rest(d) => (d, None),
})
.unzip();

let musics = pitches.into_iter().rev().zip(durations).map(|(p, d)| {
if let Some(p) = p {
Music::note(d, p)
} else {
Music::rest(d)
}
});

Some(Music::line(musics.collect()))
}

#[test]
fn test_retro_pitches() {
let oc4 = Octave::ONE_LINED;
let m = Music::line(vec![
M::C(oc4, Dur::EN),
M::rest(Dur::SN),
M::D(oc4, Dur::QN),
]);

assert_eq!(
retro_pitches(m).unwrap(),
Music::line(vec![
M::D(oc4, Dur::EN),
M::rest(Dur::SN),
M::C(oc4, Dur::QN),
])
);
}
12 changes: 12 additions & 0 deletions src/music.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ impl Interval {
pub const fn tone() -> Self {
Self(2)
}

pub const fn get_inner(self) -> i8 {
self.0
}
}

impl From<i8> for Interval {
Expand Down Expand Up @@ -226,6 +230,14 @@ impl Pitch {
Self { class, octave }
}

pub const fn octave(self) -> Octave {
self.octave
}

pub const fn class(self) -> PitchClass {
self.class
}

def_pitch_constructor![Aff, Af, A, As, Ass];
def_pitch_constructor![Bff, Bf, B, Bs, Bss];
def_pitch_constructor![Cff, Cf, C, Cs, Css];
Expand Down

0 comments on commit 29e21d0

Please sign in to comment.