diff --git a/neo/src/error.rs b/neo/src/error.rs index c4387ee..9832d5d 100644 --- a/neo/src/error.rs +++ b/neo/src/error.rs @@ -5,7 +5,7 @@ #[doc(hidden)] pub use res::EResult; /// A type alias for a [`Result`](core::result::Result) that uses the [`TriadError`](TriadError) type. -pub type TriadResult = core::result::Result; +pub type TriadResult = core::result::Result; use rstmt::{Note, Pitch}; @@ -29,7 +29,7 @@ use rstmt::{Note, Pitch}; )] #[repr(C)] #[strum(serialize_all = "PascalCase")] -pub enum TriadError { +pub enum NeoError { #[error("InvalidPitch: {0}")] InvalidPitch(String), #[error( @@ -46,7 +46,7 @@ pub enum TriadError { Unknown(String), } -impl TriadError { +impl NeoError { pub fn invalid_pitch(msg: impl ToString) -> Self { Self::InvalidPitch(msg.to_string()) } @@ -68,15 +68,15 @@ impl TriadError { } } -impl From for TriadError { +impl From for NeoError { fn from(err: Pitch) -> Self { - TriadError::InvalidPitch(err.to_string()) + NeoError::InvalidPitch(err.to_string()) } } -impl From<(Note, Note, Note)> for TriadError { +impl From<(Note, Note, Note)> for NeoError { fn from((r, t, f): (Note, Note, Note)) -> Self { - TriadError::InvalidTriad(format!("({}, {}, {})", r, t, f)) + NeoError::InvalidTriad(format!("({}, {}, {})", r, t, f)) } } diff --git a/neo/src/lib.rs b/neo/src/lib.rs index dcfa06a..2f103b1 100644 --- a/neo/src/lib.rs +++ b/neo/src/lib.rs @@ -11,7 +11,7 @@ extern crate rstmt_core as rstmt; #[doc(inline)] pub use self::{ - error::{TriadError, TriadResult}, + error::{NeoError, TriadResult}, transform::LPR, triad::{ kinds::{Augmented, Diminished, Major, Minor}, @@ -40,7 +40,7 @@ pub(crate) mod impls { } pub mod prelude { - pub use crate::error::{TriadError, TriadResult}; + pub use crate::error::{NeoError, TriadResult}; pub use crate::tonnetz::prelude::*; pub use crate::transform::prelude::*; pub use crate::triad::prelude::*; diff --git a/neo/src/transform/lpr.rs b/neo/src/transform/lpr.rs index 4fd46dd..b4a2405 100644 --- a/neo/src/transform/lpr.rs +++ b/neo/src/transform/lpr.rs @@ -2,8 +2,10 @@ Appellation: lpr Contrib: FL03 */ +use crate::triad::{Triad, TriadKind}; +use crate::NeoError; /// # LPR Transformations -/// +/// /// The neo-Riemannian theory focuses on three primary transformations, namely: leading (L), /// parallel (P), and relative (R). Each transformation operates on a particular chord factor /// determined by the class of the triad. Additionally, each transformation is its own inverse @@ -67,4 +69,20 @@ impl LPR { pub fn relative() -> Self { LPR::R } + + pub fn apply(self, triad: Triad) -> Result, NeoError> + where + K: TriadKind, + { + use super::utils::{_leading, _parallel, _relative}; + let rt = triad.root_to_third()?; + let chord = triad.as_tuple(); + let (r, t, f) = match self { + LPR::L => _leading(chord, rt), + LPR::P => _parallel(chord, rt), + LPR::R => _relative(chord, rt), + }; + + Triad::try_from_notes(r, t, f) + } } diff --git a/neo/src/transform/mod.rs b/neo/src/transform/mod.rs index 1acf90e..70cd8f1 100644 --- a/neo/src/transform/mod.rs +++ b/neo/src/transform/mod.rs @@ -5,7 +5,6 @@ #[doc(inline)] pub use self::{lpr::LPR, transformer::Transformer}; - pub(crate) mod lpr; pub(crate) mod transformer; @@ -23,13 +22,13 @@ pub trait Transform { } pub(crate) mod utils { use super::LPR; - use crate::prelude::TriadError; + use crate::prelude::NeoError; use rstmt::{IntervalOps, Note, Third}; pub(crate) fn _transform( chord: (Note, Note, Note), dirac: LPR, - ) -> Result<(Note, Note, Note), TriadError> { + ) -> Result<(Note, Note, Note), NeoError> { let rt = Third::new(chord.0, chord.1)?; let (r, t, f) = match dirac { LPR::L => _leading(chord, rt), diff --git a/neo/src/transform/transformer.rs b/neo/src/transform/transformer.rs index 354aa17..3557d61 100644 --- a/neo/src/transform/transformer.rs +++ b/neo/src/transform/transformer.rs @@ -4,7 +4,7 @@ */ use super::{utils::*, LPR}; use crate::triad::{Triad, TriadKind}; -use crate::TriadError; +use crate::NeoError; pub struct Transformer { delta: Option, @@ -12,6 +12,7 @@ pub struct Transformer { } impl Transformer { + #[allow(dead_code)] pub(crate) fn new(triad: Triad) -> Self { Self { delta: None, triad } } @@ -23,22 +24,37 @@ impl Transformer { } } - pub fn transform(self) -> Result, TriadError> + pub fn leading(self) -> Self { + self.apply(LPR::L) + } + + pub fn parallel(self) -> Self { + self.apply(LPR::P) + } + + pub fn relative(self) -> Self { + self.apply(LPR::R) + } + /// Transforms a triad into another triad based on the specified transformation. + pub fn transform(self) -> Result, NeoError> where K: TriadKind, { let Transformer { triad, delta } = self; - let delta = delta.ok_or(TriadError::transformation_error( + let delta = delta.ok_or(NeoError::transformation_error( "No transformation specified...", ))?; - let rt = triad.root_to_third()?; + // compute the interval between the root and third factors + let rt = rstmt::Third::new(triad.root(), triad.third())?; + // convert the triad into a 3-tuple let chord = triad.as_tuple(); + // match the transformation let (r, t, f) = match delta { LPR::L => _leading(chord, rt), LPR::P => _parallel(chord, rt), LPR::R => _relative(chord, rt), }; - + // try constructing a new triad from the transformed notes Triad::try_from_notes(r, t, f) } } diff --git a/neo/src/triad/kinds.rs b/neo/src/triad/kinds.rs index 5f59ca3..d2e0c1f 100644 --- a/neo/src/triad/kinds.rs +++ b/neo/src/triad/kinds.rs @@ -7,7 +7,7 @@ pub use self::{classes::*, traits::*}; mod classes; mod traits; -use crate::TriadError; +use crate::NeoError; use core::marker::PhantomData; use rstmt::{Fifth, Note, Third}; @@ -58,14 +58,14 @@ impl Triads { Triads::Diminished } } - pub fn try_from_notes(root: Note, third: Note, fifth: Note) -> Result { + pub fn try_from_notes(root: Note, third: Note, fifth: Note) -> Result { use strum::IntoEnumIterator; for i in Self::iter() { if i.is_valid(root, third, fifth) { return Ok(i); } } - Err(TriadError::invalid_triad( + Err(NeoError::invalid_triad( "The given notes do not form a valid triad...", )) } diff --git a/neo/src/triad/kinds/classes.rs b/neo/src/triad/kinds/classes.rs index 3993796..0f982ce 100644 --- a/neo/src/triad/kinds/classes.rs +++ b/neo/src/triad/kinds/classes.rs @@ -2,7 +2,7 @@ Appellation: classes Contrib: FL03 */ -use super::{Class, Kind, Relative, TriadCls, TriadKind}; +use super::{Kind, Relative, TriadCls, TriadKind}; use crate::triad::Triads; macro_rules! class { @@ -38,8 +38,6 @@ macro_rules! class { } } - impl Class for $name {} - impl Kind for $name { type Class = Triads; @@ -52,8 +50,6 @@ macro_rules! class { } } - - impl TriadCls for $name { seal!(); @@ -105,9 +101,9 @@ macro_rules! class { }; } -class!( +class! { Augmented::augmented(relative: Diminished), Diminished::diminished(relative: Augmented), Major::major(relative: Minor), Minor::minor(relative: Major), -); +} diff --git a/neo/src/triad/kinds/traits.rs b/neo/src/triad/kinds/traits.rs index 190f614..2663b32 100644 --- a/neo/src/triad/kinds/traits.rs +++ b/neo/src/triad/kinds/traits.rs @@ -6,10 +6,12 @@ use crate::triad::Triads; use core::marker::PhantomData; use rstmt::{Fifth, Note, Third}; -pub trait Class {} +#[doc(hidden)] +pub trait Group {} +#[doc(hidden)] pub trait Kind { - type Class: Class; + type Class; fn class() -> Self::Class where @@ -23,7 +25,7 @@ pub trait Kind { /// This trait denotes privately declared instances of different classes of triads. /// Traditionally, triads have two primary classes: [major](Major) and [minor](Minor), however, there are /// two additional classes: [augmented](Augmented) and [diminished](Diminished). This trait is used to determine -pub trait TriadCls: Class { +pub trait TriadCls { private!(); fn named(&self) -> &'static str; @@ -43,9 +45,8 @@ pub trait TriadKind: Kind { type Rel: TriadKind; private!(); - /// Returns a new instance of [PhantomData](core::marker::PhantomData); - /// This method is the only possible constructor for these objects, - /// a charecteristic enfored with 0-variant enum declarations. + /// Returns a new instance of [PhantomData]; This method is the only possible constructor + /// for these objects, a charecteristic enfored with 0-variant enum declarations. fn phantom() -> core::marker::PhantomData where Self: Sized, @@ -128,7 +129,7 @@ pub trait TriadKind: Kind { /* ************* Implementations ************* */ -impl Class for Triads {} +impl Group for Triads {} impl TriadCls for Triads { seal!(); @@ -154,8 +155,6 @@ where } } -impl Class for PhantomData where K: Kind {} - impl Kind for PhantomData where K: Kind, diff --git a/neo/src/triad/mod.rs b/neo/src/triad/mod.rs index cbeacc2..db4bf45 100644 --- a/neo/src/triad/mod.rs +++ b/neo/src/triad/mod.rs @@ -16,11 +16,11 @@ pub(crate) mod prelude { } pub(crate) mod utils { - use crate::error::TriadError; + use crate::error::NeoError; use rstmt::{Fifth, Note, Third}; #[doc(hidden)] - pub fn try_from_arr(notes: [Note; 3]) -> Result<(Note, Note, Note), TriadError> { + pub fn try_from_arr(notes: [Note; 3]) -> Result<(Note, Note, Note), NeoError> { use itertools::Itertools; for (&a, &b, &c) in notes.iter().circular_tuple_windows() { if Third::new(a, b).is_ok() && Third::new(b, c).is_ok() && Fifth::new(a, c).is_ok() { @@ -29,13 +29,13 @@ pub(crate) mod utils { continue; } } - Err(TriadError::invalid_triad( + Err(NeoError::invalid_triad( "Failed to find the required relationships within the given notes...", )) } } -use crate::{Factors, TriadError}; +use crate::{Factors, NeoError}; /// [IntoTriad] converts a type into a [Triad]. pub trait IntoTriad { @@ -47,7 +47,7 @@ pub trait IntoTriad { pub trait TryIntoTriad { type Kind; - fn try_into_triad(self) -> Result, TriadError>; + fn try_into_triad(self) -> Result, NeoError>; } pub trait TriadData { diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index 688b0aa..f3899e9 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -3,8 +3,8 @@ Contrib: FL03 */ use super::{Major, TriadCls, TriadKind, Triads}; -use crate::transform::{Transformer, LPR}; -use crate::TriadError; +use crate::transform::LPR; +use crate::NeoError; use core::marker::PhantomData; use itertools::Itertools; use rstmt::{Fifth, Note, Third}; @@ -64,7 +64,7 @@ impl Triad { /// unlike [`try_from_notes`](Triad::try_from_notes), this function /// iterates through the given notes to discover __**any**__ configuration /// that is valid. If no valid configuration is found, an error is returned. - pub fn try_from_arr(notes: [Note; 3]) -> Result { + pub fn try_from_arr(notes: [Note; 3]) -> Result { for (&a, &b, &c) in notes.iter().circular_tuple_windows() { println!("{a} {b} {c}"); if let Ok(triad) = Triad::try_from_notes(a, b, c) { @@ -73,14 +73,14 @@ impl Triad { continue; } } - Err(TriadError::invalid_triad( + Err(NeoError::invalid_triad( "Failed to find the required relationships within the given notes...", )) } /// Returns a new instance of [Triad] from a root note and a classifying type; /// if the given notes do not form a valid triad, an [error](TriadError) is returned. /// This function is useful for quickly determining whether a set of notes form a valid triad. - pub fn try_from_notes(root: Note, third: Note, fifth: Note) -> Result { + pub fn try_from_notes(root: Note, third: Note, fifth: Note) -> Result { // compute the interval between the root and the third let a = Third::new(root, third); // compute the interval between the third and the fifth @@ -92,7 +92,7 @@ impl Triad { notes: [root, third, fifth], }) } else { - Err(TriadError::invalid_triad( + Err(NeoError::invalid_triad( "Failed to detect the required intervals...", )) } @@ -182,7 +182,7 @@ impl Triad { &mut self.notes[2] } /// - pub fn edges(&self) -> Result<(Third, Third, Fifth), TriadError> { + pub fn edges(&self) -> Result<(Third, Third, Fifth), NeoError> { Ok(( self.root_to_third()?, self.third_to_fifth()?, @@ -190,14 +190,14 @@ impl Triad { )) } /// Computes the interval between the root and the third factors - pub fn root_to_third(&self) -> Result { + pub fn root_to_third(&self) -> Result { Third::new(self.root(), self.third()) - .map_err(|_| TriadError::invalid_interval(self.root(), self.third())) + .map_err(|_| NeoError::invalid_interval(self.root(), self.third())) } /// Returns the distance (interval) between the root and the fifth - pub fn root_to_fifth(&self) -> Result { + pub fn root_to_fifth(&self) -> Result { Fifth::new(self.root(), self.fifth()) - .map_err(|_| TriadError::invalid_interval(self.root(), self.fifth())) + .map_err(|_| NeoError::invalid_interval(self.root(), self.fifth())) } /// Returns the distance (interval) between the third and the fifth pub fn third_to_fifth(&self) -> Result { @@ -221,14 +221,12 @@ impl Triad { } /// Applies the given [LPR] transformation onto the triad. /// - pub fn transform(self, lpr: LPR) -> Triad + pub fn transform(self, lpr: LPR) -> Result, NeoError> where K: TriadKind, { - match Transformer::new(self).apply(lpr).transform() { - Ok(triad) => triad, - Err(_) => panic!("Failed to transform the triad..."), - } + // Transformer::new(self).apply(lpr).transform() + lpr.apply(self) } /// Leading transformations make semitonal adjusments to the root of the triad; /// when applied to a major triad, the leading transformation decrements the root note by @@ -238,9 +236,9 @@ impl Triad { /// the root and third factors are shifted up by a factor. The fifth factor is then moved /// to the root. /// - pub fn leading(self) -> Triad + pub fn leading(self) -> Result, NeoError> where - K: TriadKind, + K: TriadKind, { self.transform(LPR::L) } @@ -263,16 +261,16 @@ impl Triad { /// assert_eq!(triad.parallel(), Triad::minor(Note::from_pitch(0))); /// assert_eq!(triad.parallel().parallel(), triad); /// ``` - pub fn parallel(self) -> Triad + pub fn parallel(self) -> Result, NeoError> where - K: TriadKind, + K: TriadKind, { self.transform(LPR::P) } /// Applies a relative transformation to the triad; - pub fn relative(self) -> Triad + pub fn relative(self) -> Result, NeoError> where - K: TriadKind, + K: TriadKind, { self.transform(LPR::R) } diff --git a/neo/tests/transform.rs b/neo/tests/transform.rs index 48812f8..77979bd 100644 --- a/neo/tests/transform.rs +++ b/neo/tests/transform.rs @@ -14,41 +14,51 @@ use LPR::*; #[test] fn test_leading() { let c_major = Triad::major(Note::from_pitch(0)); - let next = c_major.transform(L); + let next = c_major.transform(L).unwrap(); // Validate the resulting triad assert_eq!(next.root(), c_major.third()); assert_eq!(next.third(), c_major.fifth()); assert_eq!(next.fifth(), c_major.root().sub_semitone()); // validate the transformation is invertible - assert_eq!(c_major, next.transform(L)); + assert_eq!(Ok(c_major), next.transform(L)); } #[test] fn test_parallel() { let c_major = Triad::major(Note::from_pitch(0)); - let next = c_major.transform(P); + let next = c_major.transform(P).unwrap(); // Validate the resulting triad assert_eq!(next.root(), c_major.root()); assert_eq!(next.third(), c_major.third().sub_semitone()); assert_eq!(next.fifth(), c_major.fifth()); // validate the transformation is invertible - assert_eq!(c_major, next.transform(P)); + assert_eq!(Ok(c_major), next.transform(P)); } #[test] fn test_relative() { let c_major = Triad::major(Note::from_pitch(0)); - let next = c_major.transform(R); + let next = c_major.transform(R).unwrap(); // Validate the resulting triad assert_eq!(next.root(), c_major.fifth().add_tone()); assert_eq!(next.third(), c_major.root()); assert_eq!(next.fifth(), c_major.third()); // validate the transformation is invertible - assert_eq!(c_major, next.transform(R)); + assert_eq!(Ok(c_major), next.transform(R)); } #[test] -fn test_transformer() { +fn test_chain() { let c_major = Triad::major(Note::from_pitch(0)); - assert_eq!(c_major.leading().parallel().parallel().leading(), c_major); + assert_eq!( + c_major + .leading() + .unwrap() + .parallel() + .unwrap() + .parallel() + .unwrap() + .leading(), + Ok(c_major) + ); } diff --git a/rstmt/examples/triads.rs b/rstmt/examples/triads.rs index 693df9a..a578ed1 100644 --- a/rstmt/examples/triads.rs +++ b/rstmt/examples/triads.rs @@ -11,9 +11,9 @@ fn main() -> Result<(), Box> { // test the root of the triad assert_eq!(triad.root(), root); // test the parallel transformation - assert_eq!(dbg!(triad.parallel()), Triad::minor(root)); + assert_eq!(dbg!(triad.parallel()?), Triad::minor(root)); // assert the invertibility of the transformations - assert_eq!(dbg!(triad.parallel().parallel()), triad); + assert_eq!(dbg!(triad.parallel()?.parallel()?), triad); Ok(()) }