From 65079fb618ccd772a1a6f2d3315428d7750f2d7b Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Thu, 11 Jul 2024 14:09:11 -0500 Subject: [PATCH 01/21] update --- .gitignore | 3 +- Cargo.toml | 34 +++++--- core/Cargo.toml | 83 ++++++++++++++++++ core/build.rs | 8 ++ core/src/errors/err.rs | 76 +++++++++++++++++ core/src/errors/mod.rs | 15 ++++ core/src/lib.rs | 34 ++++++++ core/src/macros.rs | 13 +++ core/src/macros/errors.rs | 4 + core/src/macros/intervals.rs | 49 +++++++++++ core/src/macros/pitches.rs | 54 ++++++++++++ core/src/macros/seal.rs | 29 +++++++ core/src/notes/mod.rs | 12 +++ core/src/notes/note.rs | 11 +++ core/src/pitches/kinds.rs | 122 +++++++++++++++++++++++++++ core/src/pitches/mod.rs | 73 ++++++++++++++++ core/src/pitches/pitch.rs | 44 ++++++++++ core/src/primitives.rs | 18 ++++ core/src/traits/mod.rs | 11 +++ core/src/types/intervals.rs | 134 +++++++++++++++++++++++++++++ core/src/types/mod.rs | 11 +++ core/tests/default.rs | 17 ++++ core/tests/pitches.rs | 17 ++++ triad/Cargo.toml | 37 ++++---- triad/src/lib.rs | 13 ++- triad/src/triad.rs | 34 ++++++++ triad/src/triad/builder.rs | 10 +++ triad/src/triad/classes.rs | 56 ++++++++++++ triad/src/triad/factors.rs | 159 +++++++++++++++++++++++++++++++++++ triad/src/triad/state.rs | 52 ++++++++++++ 30 files changed, 1194 insertions(+), 39 deletions(-) create mode 100644 core/Cargo.toml create mode 100644 core/build.rs create mode 100644 core/src/errors/err.rs create mode 100644 core/src/errors/mod.rs create mode 100644 core/src/lib.rs create mode 100644 core/src/macros.rs create mode 100644 core/src/macros/errors.rs create mode 100644 core/src/macros/intervals.rs create mode 100644 core/src/macros/pitches.rs create mode 100644 core/src/macros/seal.rs create mode 100644 core/src/notes/mod.rs create mode 100644 core/src/notes/note.rs create mode 100644 core/src/pitches/kinds.rs create mode 100644 core/src/pitches/mod.rs create mode 100644 core/src/pitches/pitch.rs create mode 100644 core/src/primitives.rs create mode 100644 core/src/traits/mod.rs create mode 100644 core/src/types/intervals.rs create mode 100644 core/src/types/mod.rs create mode 100644 core/tests/default.rs create mode 100644 core/tests/pitches.rs create mode 100644 triad/src/triad.rs create mode 100644 triad/src/triad/builder.rs create mode 100644 triad/src/triad/classes.rs create mode 100644 triad/src/triad/factors.rs create mode 100644 triad/src/triad/state.rs diff --git a/.gitignore b/.gitignore index 4d116e4..e2ac0a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Artifacts -**/.artifacts/data/ +**/.artifacts/data/* +**/.artifacts/examples/* **/.docker/data/ diff --git a/Cargo.toml b/Cargo.toml index ac795fc..dedbe6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,3 @@ -[workspace.package] -authors = ["Joe McCain III ",] -categories = [ ] -description = "This project focuses on providing concrete abstractions of musical objects discussed within the neo-Riemannian theory." -edition = "2021" -homepage = "https://github.com/username/triad/wiki" -keywords = [ ] -license = "Apache-2.0" -readme = "README.md" -repository = "https://github.com/username/triad.git" -version = "0.0.0" - -[workspace.dependencies] - [workspace] default-members = [ "triad" @@ -22,11 +8,31 @@ exclude = [ ] members = [ + "core", "triad", ] resolver = "2" +[workspace.dependencies] +lazy_static = "1" +serde_json = "1" +smart-default = "0.7" +strum = { features = ["derive"], version = "0.26" } +thiserror = "1" + +[workspace.package] +authors = ["Joe McCain III ",] +categories = [ ] +description = "This project focuses on providing concrete abstractions of musical objects discussed within the neo-Riemannian theory." +edition = "2021" +homepage = "https://github.com/FL03/triad/wiki" +keywords = [ "music" ] +license = "Apache-2.0" +readme = "README.md" +repository = "https://github.com/FL03/triad.git" +version = "0.0.1" + [profile.dev] opt-level = 0 debug = true diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..b9de3a1 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,83 @@ +[package] +authors.workspace = true +build = "build.rs" +categories.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "triad-core" +readme.workspace = true +repository.workspace = true +version.workspace = true + +[features] +default = [ + "std", +] + +full = [ + "default", + "rand", + "serde", +] + +# [FF] Dependencies +alloc = [ + "num/alloc", + "serde?/alloc", +] + +rand = [ + "num/rand", +] + +serde = [ + "dep:serde", +] + +# ********* [FF] Environments ********* +std = [ + "alloc", + "num/std", + "serde?/std", + "strum/std", +] + +[lib] +bench = true +crate-type = ["cdylib", "rlib"] +doctest = false +test = true + +[build-dependencies] + +[dependencies] +smart-default.workspace = true + +[dependencies.num] +default-features = false +version = "0.4" + +[dependencies.serde] +default-features = false +features = ["derive"] +optional = true +version = "1" + +[dependencies.strum] +default-features = false +features = ["derive"] +version = "0.26" + +[dev-dependencies] +lazy_static.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustc-args = ["--cfg", "docsrs"] + +[target.wasm32-unknown-unknown.dependencies] + +[target.wasm32-wasi] diff --git a/core/build.rs b/core/build.rs new file mode 100644 index 0000000..940a4ce --- /dev/null +++ b/core/build.rs @@ -0,0 +1,8 @@ +/* + Appellation: build + Contrib: FL03 +*/ + +fn main() { + println!("cargo::rustc-check-cfg=cfg(no_std)"); +} diff --git a/core/src/errors/err.rs b/core/src/errors/err.rs new file mode 100644 index 0000000..2b5c8b5 --- /dev/null +++ b/core/src/errors/err.rs @@ -0,0 +1,76 @@ +/* + Appellation: err + Contrib: FL03 +*/ + +pub trait FluoError: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {} + +impl FluoError for T where T: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {} + +pub trait ErrorKind: Clone + core::str::FromStr + core::fmt::Debug + core::fmt::Display {} + +impl ErrorKind for T where T: Clone + core::str::FromStr + core::fmt::Debug + core::fmt::Display {} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "TitleCase"))] +pub enum MusicalError { + InvalidInterval, + InvalidPitch, +} + +#[cfg(feature = "std")] +impl std::error::Error for MusicalError {} + +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Error { + pub kind: K, + pub msg: String +} + +impl Error where K: ErrorKind { + pub fn new(kind: K, msg: impl ToString) -> Self { + Self { kind, msg: msg.to_string() } + } + + pub fn kind(&self) -> &K { + &self.kind + } + + pub fn msg(&self) -> &str { + &self.msg + } +} + +impl Error { + pub fn invalid_interval(msg: impl ToString) -> Self { + Self::new(MusicalError::InvalidInterval, msg) + } + + pub fn invalid_pitch(msg: impl ToString) -> Self { + Self::new(MusicalError::InvalidPitch, msg) + } + +} + +impl core::fmt::Display for Error where K: ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}: {}", self.kind, self.msg) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error where K: ErrorKind {} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_error() { + let err = Error::new(MusicalError::InvalidInterval, "Invalid interval".to_string()); + assert_eq!(err.kind(), &MusicalError::InvalidInterval); + assert_eq!(err.msg(), "Invalid interval"); + } +} \ No newline at end of file diff --git a/core/src/errors/mod.rs b/core/src/errors/mod.rs new file mode 100644 index 0000000..bfc960b --- /dev/null +++ b/core/src/errors/mod.rs @@ -0,0 +1,15 @@ +/* + Appellation: errors + Contrib: FL03 +*/ +pub use self::err::*; + +pub(crate) mod err; + +/// A type alias for `Result`. +pub type Result = core::result::Result; + +pub(crate) mod prelude { + pub use super::Result; + pub use super::err::*; +} \ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..e6d4118 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,34 @@ +/* + Appellation: triad-core + Contrib: FL03 +*/ +//! # triad-core +//! +//! This library provides the core abstractions for the triad project. +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +pub use self::errors::{Error, Result}; +pub use self::pitches::{Pitch, PitchClass}; +pub use self::{types::prelude::*, primitives::*}; + +#[macro_use] +pub(crate) mod macros; +pub(crate) mod primitives; + +pub mod errors; +pub mod notes; +pub mod pitches; +pub mod traits; +pub mod types; + +pub mod prelude { + pub use super::errors::prelude::*; + pub use super::notes::prelude::*; + pub use super::pitches::prelude::*; + pub use super::primitives::prelude::*; + pub use super::traits::prelude::*; + pub use super::types::prelude::*; +} diff --git a/core/src/macros.rs b/core/src/macros.rs new file mode 100644 index 0000000..54a53a3 --- /dev/null +++ b/core/src/macros.rs @@ -0,0 +1,13 @@ +/* + Appellation: macros + Contrib: FL03 +*/ + +#[macro_use] +pub(crate) mod errors; +#[macro_use] +pub(crate) mod intervals; +#[macro_use] +pub(crate) mod pitches; +#[macro_use] +pub(crate) mod seal; \ No newline at end of file diff --git a/core/src/macros/errors.rs b/core/src/macros/errors.rs new file mode 100644 index 0000000..d535d88 --- /dev/null +++ b/core/src/macros/errors.rs @@ -0,0 +1,4 @@ +/* + Appellation: errors + Contrib: FL03 +*/ \ No newline at end of file diff --git a/core/src/macros/intervals.rs b/core/src/macros/intervals.rs new file mode 100644 index 0000000..71dada8 --- /dev/null +++ b/core/src/macros/intervals.rs @@ -0,0 +1,49 @@ +/* + Appellation: intervals + Contrib: FL03 +*/ + +macro_rules! interval { + ($vis:vis enum $name:ident {$($key:ident = $val:literal),* $(,)?}) => { + #[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::EnumString, + strum::VariantArray, + strum::VariantNames, + )] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] + #[repr(u8)] + #[strum(serialize_all = "lowercase")] + $vis enum $name { + $($key = $val),* + } + + impl From<$name> for u8 { + fn from(interval: $name) -> u8 { + interval as u8 + } + } + + impl From for $name { + fn from(interval: u8) -> $name { + use strum::EnumCount; + match interval % Self::COUNT as u8 { + $($val => $name::$key),*, + _ => panic!("Invalid interval value: {}", interval), + } + } + } + }; +} diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs new file mode 100644 index 0000000..5e5237d --- /dev/null +++ b/core/src/macros/pitches.rs @@ -0,0 +1,54 @@ +/* + Appellation: pitches + Contrib: FL03 +*/ + +macro_rules! pitch { + ($vis:vis enum $class:ident {$($name:ident = $value:expr),* $(,)?}) => { + + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] + #[repr(i8)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] + #[strum(serialize_all = "lowercase")] + $vis enum $class { + $($name = $value),* + } + + impl $class { + pub fn new(value: $crate::pitches::PitchTy) -> $crate::Result { + match value.abs() % $crate::MODULUS as $crate::pitches::PitchTy { + $(x if x == $value => Ok(Self::$name),)* + _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) + } + } + + pub fn as_class(&self) -> $crate::pitches::Pitches { + $crate::pitches::Pitches::$class(*self) + } + + pub fn pitch(&self) -> $crate::pitches::PitchTy { + *self as $crate::pitches::PitchTy + } + } + + impl $crate::pitches::PitchClass for $class { + fn pitch(&self) -> $crate::pitches::PitchTy { + *self as $crate::pitches::PitchTy + } + } + + impl From<$class> for $crate::pitches::PitchTy { + fn from(pitch: $class) -> $crate::pitches::PitchTy { + pitch as $crate::pitches::PitchTy + } + } + + impl TryFrom<$crate::pitches::PitchTy> for $class { + type Error = $crate::Error; + + fn try_from(value: PitchTy) -> Result { + Self::new(value) + } + } + }; +} \ No newline at end of file diff --git a/core/src/macros/seal.rs b/core/src/macros/seal.rs new file mode 100644 index 0000000..e74f2bb --- /dev/null +++ b/core/src/macros/seal.rs @@ -0,0 +1,29 @@ +/* + Appellation: seal + Contrib: FL03 +*/ +//! The public parts of this private module are used to create traits +//! that cannot be implemented outside of our own crate. This way we +//! can feel free to extend those traits without worrying about it +//! being a breaking change for other implementations. + +/// If this type is pub but not publicly reachable, third parties +/// can't name it and can't implement traits using it. +pub struct Seal; + +macro_rules! private { + () => { + /// This trait is private to implement; this method exists to make it + /// impossible to implement outside the crate. + #[doc(hidden)] + fn __private__(&self) -> $crate::macros::seal::Seal; + }; +} + +macro_rules! seal { + () => { + fn __private__(&self) -> $crate::macros::seal::Seal { + $crate::macros::seal::Seal + } + }; +} diff --git a/core/src/notes/mod.rs b/core/src/notes/mod.rs new file mode 100644 index 0000000..3b1f1f8 --- /dev/null +++ b/core/src/notes/mod.rs @@ -0,0 +1,12 @@ +/* + Appellation: notes + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::note::*; + +pub(crate) mod note; + +pub(crate) mod prelude { + pub use super::note::*; +} \ No newline at end of file diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs new file mode 100644 index 0000000..58badc6 --- /dev/null +++ b/core/src/notes/note.rs @@ -0,0 +1,11 @@ +/* + Appellation: note + Contrib: FL03 +*/ +use crate::pitches::Pitch; + +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Note { + pub(crate) pitch: Pitch, +} \ No newline at end of file diff --git a/core/src/pitches/kinds.rs b/core/src/pitches/kinds.rs new file mode 100644 index 0000000..bdeebbc --- /dev/null +++ b/core/src/pitches/kinds.rs @@ -0,0 +1,122 @@ +/* + Appellation: pitches + Contrib: FL03 +*/ +use super::{PitchClass, PitchTy}; + +pitch! { + pub enum Natural { + C = 0, + D = 2, + E = 4, + F = 5, + G = 7, + A = 9, + B = 11, + } +} + +pitch! { + pub enum Sharp { + A = 10, + C = 1, + D = 3, + F = 6, + G = 8, + } +} + +pitch! { + pub enum Flat { + D = 1, + E = 3, + G = 6, + A = 8, + B = 10, + } +} + +pub trait AccidentalPitch: PitchClass { + private!(); +} + +impl AccidentalPitch for Sharp { + seal!(); +} + +impl AccidentalPitch for Flat { + seal!(); +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, smart_default::SmartDefault, strum::AsRefStr, strum::Display, strum::EnumCount, strum::EnumIs, strum::VariantNames)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] +#[repr(i8)] +#[strum(serialize_all = "lowercase")] +pub enum Pitches { + Flat(Flat), + #[default] + Natural(Natural), + Sharp(Sharp) +} + +impl Pitches { + pub fn from_value(value: PitchTy) -> crate::Result { + if let Ok(n) = Natural::new(value) { + Ok(n.as_class()) + } else if let Ok(s) = Sharp::new(value) { + Ok(s.as_class()) + } else if let Ok(f) = Flat::new(value) { + Ok(f.as_class()) + } else { + Err(crate::Error::invalid_pitch("Invalid pitch value.")) + } + } + + pub fn pitch(&self) -> PitchTy { + match self { + Pitches::Flat(f) => f.pitch(), + Pitches::Natural(n) => n.pitch(), + Pitches::Sharp(s) => s.pitch(), + } + } +} + +impl PitchClass for Pitches { + fn pitch(&self) -> PitchTy { + self.pitch() + } +} + +impl From for PitchTy { + fn from(pitch: Pitches) -> PitchTy { + pitch.pitch() + } +} + +impl From for Pitches { + fn from(value: PitchTy) -> Pitches { + Self::from_value(value).unwrap() + } +} + +mod impl_kinds { + use super::*; + + impl Default for Natural { + fn default() -> Self { + Natural::C + } + } + + impl Default for Sharp { + fn default() -> Self { + Sharp::C + } + } + + impl Default for Flat { + fn default() -> Self { + Flat::D + } + } +} \ No newline at end of file diff --git a/core/src/pitches/mod.rs b/core/src/pitches/mod.rs new file mode 100644 index 0000000..4cb0ad8 --- /dev/null +++ b/core/src/pitches/mod.rs @@ -0,0 +1,73 @@ +/* + Appellation: pitches + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::{kinds::*, pitch::Pitch, utils::*}; + +pub(crate) mod kinds; +pub(crate) mod pitch; + +/// A type alias for an integer representing a particular pitch of a note +pub type PitchTy = i8; + +pub trait PitchClass { + fn pitch(&self) -> PitchTy; +} + + +pub trait Pitched { + /// Classify the pitch into a pitch class + fn class(&self) -> Pitches { + self.pitch().into() + } + /// Find the modular index of the given pitch + fn pitch(&self) -> PitchTy; +} + +pub(crate) mod prelude { + pub use super::{PitchTy, PitchClass}; + pub use super::kinds::*; +} + +pub(crate) mod utils { + use num::traits::Signed; + + pub trait AbsMod { + type Output; + + fn absmod(&self, rhs: Rhs) -> Self::Output; + } + + impl AbsMod for A + where + A: Copy + core::ops::Rem + core::ops::Add, + B: Copy, + C: core::ops::Add + core::ops::Rem + Signed, + { + type Output = C; + + fn absmod(&self, rhs: B) -> Self::Output { + (((*self % rhs) + rhs) % rhs).abs() + } + } + /// [absmod] is short for the absolute value of a modular number; + pub fn absmod(a: i64, m: i64) -> i64 { + (((a % m) + m) % m).abs() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_absmod() { + assert_eq!(absmod(5, 12), 5.absmod(12)); + assert_eq!(absmod(-5, 12), (-5).absmod(12)); + assert_eq!(absmod(0, 12), 0.absmod(12)); + assert_eq!(absmod(12, 12), 24.absmod(12)); + assert_eq!(absmod(13, 12), 1.absmod(12)); + assert_eq!(absmod(-13, 12), 11.absmod(12)); + } +} \ No newline at end of file diff --git a/core/src/pitches/pitch.rs b/core/src/pitches/pitch.rs new file mode 100644 index 0000000..bc8dcac --- /dev/null +++ b/core/src/pitches/pitch.rs @@ -0,0 +1,44 @@ +/* + Appellation: pitch + Contrib: FL03 +*/ +use super::PitchTy; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Pitch(pub PitchTy); + + +impl AsRef for Pitch { + fn as_ref(&self) -> &PitchTy { + &self.0 + } +} + +impl AsMut for Pitch { + fn as_mut(&mut self) -> &mut PitchTy { + &mut self.0 + } +} + +impl Default for Pitch { + fn default() -> Self { + Self(super::Natural::default().pitch()) + } +} + +impl core::ops::Deref for Pitch { + type Target = PitchTy; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl core::ops::DerefMut for Pitch { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + + diff --git a/core/src/primitives.rs b/core/src/primitives.rs new file mode 100644 index 0000000..018a056 --- /dev/null +++ b/core/src/primitives.rs @@ -0,0 +1,18 @@ +/* + Appellation: primitives + Contrib: FL03 +*/ +pub use self::consts::*; + +pub(crate) mod prelude { + pub use super::consts::*; +} + +pub mod consts { + /// Used to describe the total number of notes considered + pub const MODULUS: i8 = 12; + /// A semitone is half of a tone + pub const SEMITONE: i8 = 1; + /// A tone is a difference of two + pub const TONE: i8 = 2; +} diff --git a/core/src/traits/mod.rs b/core/src/traits/mod.rs new file mode 100644 index 0000000..87f4dae --- /dev/null +++ b/core/src/traits/mod.rs @@ -0,0 +1,11 @@ +/* + Appellation: traits + Contrib: FL03 +*/ +#[allow(unused_imports)] +pub use self::prelude::*; + + + +pub(crate) mod prelude { +} \ No newline at end of file diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs new file mode 100644 index 0000000..6625c3b --- /dev/null +++ b/core/src/types/intervals.rs @@ -0,0 +1,134 @@ +/* + Appellation: intervals + Contrib: FL03 +*/ + +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] +#[repr(u8)] +#[strum(serialize_all = "lowercase")] +pub enum Intervals { + Semitone = 1, + Tone = 2, + Thirds(Third), + Fourths(Fourth), + Fifths(Fifth), + Sevenths(Seventh), +} + +impl Intervals { + pub fn from_semitone() -> Self { + Intervals::Semitone + } + + pub fn from_tone() -> Self { + Intervals::Tone + } + + pub fn from_thirds(third: Third) -> Self { + Intervals::Thirds(third) + } + + pub fn from_fourths(fourth: Fourth) -> Self { + Intervals::Fourths(fourth) + } + + pub fn from_fifths(fifth: Fifth) -> Self { + Intervals::Fifths(fifth) + } + + pub fn from_sevenths(seventh: Seventh) -> Self { + Intervals::Sevenths(seventh) + } + + pub fn name(&self) -> &str { + self.as_ref() + } + + pub fn value(&self) -> i8 { + match *self { + Intervals::Semitone => 1, + Intervals::Tone => 2, + Intervals::Thirds(third) => third as i8, + Intervals::Fourths(fourth) => fourth as i8, + Intervals::Fifths(fifth) => fifth as i8, + Intervals::Sevenths(seventh) => seventh as i8, + } + } +} + + + +impl From for Intervals { + fn from(third: Third) -> Self { + Intervals::Thirds(third) + } +} + +impl From for Intervals { + fn from(fourth: Fourth) -> Self { + Intervals::Fourths(fourth) + } +} + +impl From for Intervals { + fn from(fifth: Fifth) -> Self { + Intervals::Fifths(fifth) + } +} + +impl From for Intervals { + fn from(seventh: Seventh) -> Self { + Intervals::Sevenths(seventh) + } +} + + +interval! { + pub enum Third { + Minor = 3, + Major = 4, + } +} + +interval! { + pub enum Fourth { + Perfect = 5, + } +} + +interval! { + pub enum Fifth { + Augmented = 8, + Perfect = 7, + Diminished = 6, + } +} + + +interval! { + pub enum Seventh { + Augmented = 12, + Diminished = 9, + Major = 11, + Minor = 10, + } +} diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs new file mode 100644 index 0000000..543729e --- /dev/null +++ b/core/src/types/mod.rs @@ -0,0 +1,11 @@ +/* + Appellation: types + Contrib: FL03 +*/ +pub use self::prelude::*; + +pub mod intervals; + +pub(crate) mod prelude { + pub use super::intervals::*; +} diff --git a/core/tests/default.rs b/core/tests/default.rs new file mode 100644 index 0000000..233a07a --- /dev/null +++ b/core/tests/default.rs @@ -0,0 +1,17 @@ +/* + Appellation: default + Contrib: FL03 +*/ + +fn add(a: A, b: B) -> C +where + A: core::ops::Add, +{ + a + b +} + +#[test] +fn compiles() { + assert_eq!(add(10, 10), 20); + assert_ne!(add(1, 1), 3); +} diff --git a/core/tests/pitches.rs b/core/tests/pitches.rs new file mode 100644 index 0000000..69e5cd2 --- /dev/null +++ b/core/tests/pitches.rs @@ -0,0 +1,17 @@ +/* + Appellation: pitches + Contrib: FL03 +*/ +use triad_core::pitches::*; + +fn assert_ok(result: Result) -> T where E: core::fmt::Debug + core::fmt::Display { + assert!(result.is_ok()); + result.unwrap() +} + +#[test] +fn test_pitch() { + let pitch = assert_ok(Pitches::from_value(12)); + let rhs = Natural::C; + assert_eq!(pitch, rhs.as_class()); +} \ No newline at end of file diff --git a/triad/Cargo.toml b/triad/Cargo.toml index 53dacce..cb4b415 100644 --- a/triad/Cargo.toml +++ b/triad/Cargo.toml @@ -14,7 +14,7 @@ version.workspace = true [features] default = [ - "std", + ] full = [ @@ -24,25 +24,18 @@ full = [ ] # [FF] Dependencies -alloc = [ - "num/alloc", - "serde?/alloc", -] - rand = [ - "num/rand" + "num/rand", ] serde = [ - "dep:serde" + "dep:serde", + "petgraph/serde", + "triad-core/serde", ] # ********* [FF] Environments ********* -std = [ - "alloc", - "num/std", - "serde?/std", -] + wasm = [] @@ -56,19 +49,23 @@ test = true [build-dependencies] -[dev-dependencies] -lazy_static = "1" - -[dependencies.num] -default-features = false -version = "0.4" +[dependencies] +num = "0.4" +petgraph = "0.6" +strum.workspace = true [dependencies.serde] -default-features = false features = ["derive"] optional = true version = "1" +[dependencies.triad-core] +path = "../core" +version = "0.0.1" + +[dev-dependencies] +lazy_static.workspace = true + [package.metadata.docs.rs] all-features = true rustc-args = ["--cfg", "docsrs"] diff --git a/triad/src/lib.rs b/triad/src/lib.rs index 981b11c..86f75dc 100644 --- a/triad/src/lib.rs +++ b/triad/src/lib.rs @@ -5,11 +5,16 @@ //! # triad //! //! This project focuses on providing concrete abstractions of musical objects discussed within the neo-Riemannian theory. -#![cfg_attr(not(feature = "std"), no_std)] #![crate_name = "triad"] -#[cfg(feature = "alloc")] -extern crate alloc; +#[doc(inline)] +pub use triad_core::*; +pub use self::triad::Triad; -pub mod prelude {} +pub mod triad; + +pub mod prelude { + pub use crate::triad::{ChordFactor, Triad}; + pub use triad_core::prelude::*; +} diff --git a/triad/src/triad.rs b/triad/src/triad.rs new file mode 100644 index 0000000..e24622b --- /dev/null +++ b/triad/src/triad.rs @@ -0,0 +1,34 @@ +/* + Appellation: triad + Contrib: FL03 +*/ +pub use self::{builder::TriadBuilder, classes::*, factors::*, state::*}; + +pub(crate) mod builder; +pub(crate) mod classes; +pub(crate) mod factors; +pub(crate) mod state; + +pub type TriadGraph = petgraph::Graph; + +pub type TriadId = String; + + + + +#[derive(Clone)] +pub struct Triad { + pub(crate) id: TriadId, + pub(crate) state: BinaryState, + pub(crate) store: TriadGraph, +} + +impl Triad { + pub fn new(id: TriadId) -> Self { + Self { + id, + state: BinaryState::default(), + store: TriadGraph::new(), + } + } +} diff --git a/triad/src/triad/builder.rs b/triad/src/triad/builder.rs new file mode 100644 index 0000000..b3f934e --- /dev/null +++ b/triad/src/triad/builder.rs @@ -0,0 +1,10 @@ +/* + Appellation: builder + Contrib: FL03 +*/ + +pub struct TriadBuilder { + pub root: String, + pub third: Option, + pub fifth: Option, +} diff --git a/triad/src/triad/classes.rs b/triad/src/triad/classes.rs new file mode 100644 index 0000000..99b7410 --- /dev/null +++ b/triad/src/triad/classes.rs @@ -0,0 +1,56 @@ +/* + Appellation: classes + Contrib: FL03 +*/ + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::EnumString, + strum::VariantArray, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] +#[repr(u8)] +#[strum(serialize_all = "lowercase")] +pub enum Triads { + Augmented, + Diminished, + #[default] + Major, + Minor, +} + +impl Triads { + pub fn augmented() -> Self { + Triads::Augmented + } + + pub fn diminished() -> Self { + Triads::Diminished + } + + pub fn major() -> Self { + Triads::Major + } + + pub fn minor() -> Self { + Triads::Minor + } +} diff --git a/triad/src/triad/factors.rs b/triad/src/triad/factors.rs new file mode 100644 index 0000000..98b57c6 --- /dev/null +++ b/triad/src/triad/factors.rs @@ -0,0 +1,159 @@ +/* + Appellation: factors + Contrib: FL03 +*/ +use strum::IntoEnumIterator; + +/// A [chord factor](ChordFactor) describes the position of a note within a [triad](crate::Triad). +/// The `root` factor is the first note of the triad, the `third` factor is the +/// second note of the triad, and the `fifth` factor is the third note of the triad. +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumDiscriminants, + strum::EnumIs, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase"), + strum_discriminants( + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") + ) +)] +#[strum_discriminants( + name(Factors), + derive( + Hash, + Ord, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIter, + strum::EnumString, + strum::VariantNames + ) +)] +#[repr(u8)] +#[strum(serialize_all = "lowercase")] +pub enum ChordFactor { + #[strum(serialize = "r", serialize = "root")] + Root(T) = 0, + #[strum(serialize = "t", serialize = "third")] + Third(T) = 1, + #[strum(serialize = "f", serialize = "fifth")] + Fifth(T) = 2, +} + +impl ChordFactor { + pub fn fifth(data: T) -> Self { + Self::Fifth(data) + } + + pub fn root(data: T) -> Self { + Self::Root(data) + } + + pub fn third(data: T) -> Self { + Self::Third(data) + } +} + + + +mod impl_factors { + use super::*; + + impl Factors { + pub fn root() -> Self { + Self::Root + } + + pub fn third() -> Self { + Self::Third + } + + pub fn fifth() -> Self { + Self::Fifth + } + + pub fn factors() -> [Self; 3] { + use Factors::*; + [Root, Third, Fifth] + } + + pub fn others(&self) -> Vec { + Self::iter().filter(|x| x != self).collect() + } + } + + impl Default for Factors { + fn default() -> Self { + Factors::Root + } + } + + impl From for Factors { + fn from(x: usize) -> Self { + match x % 3 { + 0 => Factors::Root, + 1 => Factors::Third, + _ => Factors::Fifth, + } + } + } + + impl From for usize { + fn from(x: Factors) -> Self { + x as usize + } + } + + unsafe impl petgraph::graph::IndexType for Factors { + fn new(x: usize) -> Self { + Self::from(x) + } + + fn index(&self) -> usize { + *self as usize + } + + fn max() -> Self { + Self::Fifth + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chord_factors() { + + // let triad = + } + + #[test] + fn chord_factors_iter() { + use Factors::*; + + let factors = Factors::factors(); + assert_eq!(factors.len(), 3); + assert_eq!(factors[0], Root); + assert_eq!(factors[1], Third); + assert_eq!(factors[2], Fifth); + } +} diff --git a/triad/src/triad/state.rs b/triad/src/triad/state.rs new file mode 100644 index 0000000..14a3c91 --- /dev/null +++ b/triad/src/triad/state.rs @@ -0,0 +1,52 @@ +/* + Appellation: state + Contrib: FL03 +*/ +use crate::triad::Triads; + + +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct TriadState { + pub(crate) class: Triads, + pub(crate) hash: String, + +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(C)] +pub enum BinaryState { + Invalid(Q), + Valid(Q), +} + +impl AsRef for BinaryState { + fn as_ref(&self) -> &Q { + match self { + Self::Invalid(q) => q, + Self::Valid(q) => q, + } + } +} + +impl AsMut for BinaryState { + fn as_mut(&mut self) -> &mut Q { + match self { + Self::Invalid(q) => q, + Self::Valid(q) => q, + } + } +} + +impl Default for BinaryState { + fn default() -> Self { + Self::Invalid(Q::default()) + } +} + +impl core::ops::Deref for BinaryState { + type Target = Q; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} \ No newline at end of file From f3d9437b23779557cdd55a22f5b6e1b8679c6b7e Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Thu, 11 Jul 2024 14:09:35 -0500 Subject: [PATCH 02/21] update --- core/src/errors/err.rs | 48 +++++++++++++++++++++++++++++-------- core/src/errors/mod.rs | 4 ++-- core/src/lib.rs | 2 +- core/src/macros.rs | 2 +- core/src/macros/errors.rs | 2 +- core/src/macros/pitches.rs | 6 ++--- core/src/notes/mod.rs | 2 +- core/src/notes/note.rs | 2 +- core/src/pitches/kinds.rs | 27 +++++++++++++++++---- core/src/pitches/mod.rs | 5 ++-- core/src/pitches/pitch.rs | 3 --- core/src/traits/mod.rs | 5 +--- core/src/types/intervals.rs | 4 ---- core/tests/pitches.rs | 7 ++++-- triad/src/triad.rs | 3 --- triad/src/triad/factors.rs | 18 +++++++------- triad/src/triad/state.rs | 4 +--- 17 files changed, 88 insertions(+), 56 deletions(-) diff --git a/core/src/errors/err.rs b/core/src/errors/err.rs index 2b5c8b5..15459f7 100644 --- a/core/src/errors/err.rs +++ b/core/src/errors/err.rs @@ -11,8 +11,26 @@ pub trait ErrorKind: Clone + core::str::FromStr + core::fmt::Debug + core::fmt:: impl ErrorKind for T where T: Clone + core::str::FromStr + core::fmt::Debug + core::fmt::Display {} -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "TitleCase"))] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumIs, + strum::EnumString, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "TitleCase") +)] pub enum MusicalError { InvalidInterval, InvalidPitch, @@ -25,12 +43,18 @@ impl std::error::Error for MusicalError {} #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Error { pub kind: K, - pub msg: String + pub msg: String, } -impl Error where K: ErrorKind { +impl Error +where + K: ErrorKind, +{ pub fn new(kind: K, msg: impl ToString) -> Self { - Self { kind, msg: msg.to_string() } + Self { + kind, + msg: msg.to_string(), + } } pub fn kind(&self) -> &K { @@ -50,10 +74,12 @@ impl Error { pub fn invalid_pitch(msg: impl ToString) -> Self { Self::new(MusicalError::InvalidPitch, msg) } - } -impl core::fmt::Display for Error where K: ErrorKind { +impl core::fmt::Display for Error +where + K: ErrorKind, +{ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}: {}", self.kind, self.msg) } @@ -62,15 +88,17 @@ impl core::fmt::Display for Error where K: ErrorKind { #[cfg(feature = "std")] impl std::error::Error for Error where K: ErrorKind {} - #[cfg(test)] mod tests { use super::*; #[test] fn test_error() { - let err = Error::new(MusicalError::InvalidInterval, "Invalid interval".to_string()); + let err = Error::new( + MusicalError::InvalidInterval, + "Invalid interval".to_string(), + ); assert_eq!(err.kind(), &MusicalError::InvalidInterval); assert_eq!(err.msg(), "Invalid interval"); } -} \ No newline at end of file +} diff --git a/core/src/errors/mod.rs b/core/src/errors/mod.rs index bfc960b..ff6dfa3 100644 --- a/core/src/errors/mod.rs +++ b/core/src/errors/mod.rs @@ -10,6 +10,6 @@ pub(crate) mod err; pub type Result = core::result::Result; pub(crate) mod prelude { - pub use super::Result; pub use super::err::*; -} \ No newline at end of file + pub use super::Result; +} diff --git a/core/src/lib.rs b/core/src/lib.rs index e6d4118..5d5debb 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -12,7 +12,7 @@ extern crate alloc; pub use self::errors::{Error, Result}; pub use self::pitches::{Pitch, PitchClass}; -pub use self::{types::prelude::*, primitives::*}; +pub use self::{primitives::*, types::prelude::*}; #[macro_use] pub(crate) mod macros; diff --git a/core/src/macros.rs b/core/src/macros.rs index 54a53a3..1bbc57a 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -10,4 +10,4 @@ pub(crate) mod intervals; #[macro_use] pub(crate) mod pitches; #[macro_use] -pub(crate) mod seal; \ No newline at end of file +pub(crate) mod seal; diff --git a/core/src/macros/errors.rs b/core/src/macros/errors.rs index d535d88..5c82fbf 100644 --- a/core/src/macros/errors.rs +++ b/core/src/macros/errors.rs @@ -1,4 +1,4 @@ /* Appellation: errors Contrib: FL03 -*/ \ No newline at end of file +*/ diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index 5e5237d..886b6dc 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -5,7 +5,7 @@ macro_rules! pitch { ($vis:vis enum $class:ident {$($name:ident = $value:expr),* $(,)?}) => { - + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] #[repr(i8)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] @@ -45,10 +45,10 @@ macro_rules! pitch { impl TryFrom<$crate::pitches::PitchTy> for $class { type Error = $crate::Error; - + fn try_from(value: PitchTy) -> Result { Self::new(value) } } }; -} \ No newline at end of file +} diff --git a/core/src/notes/mod.rs b/core/src/notes/mod.rs index 3b1f1f8..d9f569c 100644 --- a/core/src/notes/mod.rs +++ b/core/src/notes/mod.rs @@ -9,4 +9,4 @@ pub(crate) mod note; pub(crate) mod prelude { pub use super::note::*; -} \ No newline at end of file +} diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index 58badc6..eb9c70d 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -8,4 +8,4 @@ use crate::pitches::Pitch; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Note { pub(crate) pitch: Pitch, -} \ No newline at end of file +} diff --git a/core/src/pitches/kinds.rs b/core/src/pitches/kinds.rs index bdeebbc..bb0e7a2 100644 --- a/core/src/pitches/kinds.rs +++ b/core/src/pitches/kinds.rs @@ -48,15 +48,34 @@ impl AccidentalPitch for Flat { seal!(); } -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, smart_default::SmartDefault, strum::AsRefStr, strum::Display, strum::EnumCount, strum::EnumIs, strum::VariantNames)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + smart_default::SmartDefault, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] #[repr(i8)] #[strum(serialize_all = "lowercase")] pub enum Pitches { Flat(Flat), #[default] Natural(Natural), - Sharp(Sharp) + Sharp(Sharp), } impl Pitches { @@ -119,4 +138,4 @@ mod impl_kinds { Flat::D } } -} \ No newline at end of file +} diff --git a/core/src/pitches/mod.rs b/core/src/pitches/mod.rs index 4cb0ad8..971768b 100644 --- a/core/src/pitches/mod.rs +++ b/core/src/pitches/mod.rs @@ -15,7 +15,6 @@ pub trait PitchClass { fn pitch(&self) -> PitchTy; } - pub trait Pitched { /// Classify the pitch into a pitch class fn class(&self) -> Pitches { @@ -26,8 +25,8 @@ pub trait Pitched { } pub(crate) mod prelude { - pub use super::{PitchTy, PitchClass}; pub use super::kinds::*; + pub use super::{PitchClass, PitchTy}; } pub(crate) mod utils { @@ -70,4 +69,4 @@ mod tests { assert_eq!(absmod(13, 12), 1.absmod(12)); assert_eq!(absmod(-13, 12), 11.absmod(12)); } -} \ No newline at end of file +} diff --git a/core/src/pitches/pitch.rs b/core/src/pitches/pitch.rs index bc8dcac..7730920 100644 --- a/core/src/pitches/pitch.rs +++ b/core/src/pitches/pitch.rs @@ -8,7 +8,6 @@ use super::PitchTy; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Pitch(pub PitchTy); - impl AsRef for Pitch { fn as_ref(&self) -> &PitchTy { &self.0 @@ -40,5 +39,3 @@ impl core::ops::DerefMut for Pitch { self.as_mut() } } - - diff --git a/core/src/traits/mod.rs b/core/src/traits/mod.rs index 87f4dae..d07e4ea 100644 --- a/core/src/traits/mod.rs +++ b/core/src/traits/mod.rs @@ -5,7 +5,4 @@ #[allow(unused_imports)] pub use self::prelude::*; - - -pub(crate) mod prelude { -} \ No newline at end of file +pub(crate) mod prelude {} diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs index 6625c3b..11f3d2f 100644 --- a/core/src/types/intervals.rs +++ b/core/src/types/intervals.rs @@ -75,8 +75,6 @@ impl Intervals { } } - - impl From for Intervals { fn from(third: Third) -> Self { Intervals::Thirds(third) @@ -101,7 +99,6 @@ impl From for Intervals { } } - interval! { pub enum Third { Minor = 3, @@ -123,7 +120,6 @@ interval! { } } - interval! { pub enum Seventh { Augmented = 12, diff --git a/core/tests/pitches.rs b/core/tests/pitches.rs index 69e5cd2..ca5b4fa 100644 --- a/core/tests/pitches.rs +++ b/core/tests/pitches.rs @@ -4,7 +4,10 @@ */ use triad_core::pitches::*; -fn assert_ok(result: Result) -> T where E: core::fmt::Debug + core::fmt::Display { +fn assert_ok(result: Result) -> T +where + E: core::fmt::Debug + core::fmt::Display, +{ assert!(result.is_ok()); result.unwrap() } @@ -14,4 +17,4 @@ fn test_pitch() { let pitch = assert_ok(Pitches::from_value(12)); let rhs = Natural::C; assert_eq!(pitch, rhs.as_class()); -} \ No newline at end of file +} diff --git a/triad/src/triad.rs b/triad/src/triad.rs index e24622b..fe3aaae 100644 --- a/triad/src/triad.rs +++ b/triad/src/triad.rs @@ -13,9 +13,6 @@ pub type TriadGraph = petgraph::Graph; pub type TriadId = String; - - - #[derive(Clone)] pub struct Triad { pub(crate) id: TriadId, diff --git a/triad/src/triad/factors.rs b/triad/src/triad/factors.rs index 98b57c6..0cf02e5 100644 --- a/triad/src/triad/factors.rs +++ b/triad/src/triad/factors.rs @@ -71,11 +71,9 @@ impl ChordFactor { } } - - mod impl_factors { use super::*; - + impl Factors { pub fn root() -> Self { Self::Root @@ -93,18 +91,18 @@ mod impl_factors { use Factors::*; [Root, Third, Fifth] } - + pub fn others(&self) -> Vec { Self::iter().filter(|x| x != self).collect() } } - + impl Default for Factors { fn default() -> Self { Factors::Root } } - + impl From for Factors { fn from(x: usize) -> Self { match x % 3 { @@ -114,22 +112,22 @@ mod impl_factors { } } } - + impl From for usize { fn from(x: Factors) -> Self { x as usize } } - + unsafe impl petgraph::graph::IndexType for Factors { fn new(x: usize) -> Self { Self::from(x) } - + fn index(&self) -> usize { *self as usize } - + fn max() -> Self { Self::Fifth } diff --git a/triad/src/triad/state.rs b/triad/src/triad/state.rs index 14a3c91..ca7e215 100644 --- a/triad/src/triad/state.rs +++ b/triad/src/triad/state.rs @@ -4,12 +4,10 @@ */ use crate::triad::Triads; - #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct TriadState { pub(crate) class: Triads, pub(crate) hash: String, - } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -49,4 +47,4 @@ impl core::ops::Deref for BinaryState { fn deref(&self) -> &Self::Target { self.as_ref() } -} \ No newline at end of file +} From 07995300e1623de0f589a887f336796eeceef16a Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Thu, 11 Jul 2024 14:21:15 -0500 Subject: [PATCH 03/21] update --- triad/src/triad/state.rs | 54 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/triad/src/triad/state.rs b/triad/src/triad/state.rs index ca7e215..64bcb33 100644 --- a/triad/src/triad/state.rs +++ b/triad/src/triad/state.rs @@ -10,13 +10,47 @@ pub struct TriadState { pub(crate) hash: String, } -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +impl TriadState { +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::EnumIs, strum::VariantNames)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] #[repr(C)] -pub enum BinaryState { +#[strum(serialize_all = "lowercase")] +pub enum BinaryState { Invalid(Q), Valid(Q), } +impl BinaryState { + pub fn invalid(state: Q) -> Self { + Self::Invalid(state) + } + + pub fn valid(state: Q) -> Self { + Self::Valid(state) + } + + pub fn kind(&self) -> &str { + match self { + Self::Invalid(_) => "invalid", + Self::Valid(_) => "valid", + } + } + + pub fn into_inner(self) -> Q { + match self { + Self::Invalid(q) => q, + Self::Valid(q) => q, + } + } + + pub fn invalidate(self) -> Self { + Self::Invalid(self.into_inner()) + } +} + + impl AsRef for BinaryState { fn as_ref(&self) -> &Q { match self { @@ -48,3 +82,19 @@ impl core::ops::Deref for BinaryState { self.as_ref() } } + +impl core::ops::DerefMut for BinaryState { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +impl core::fmt::Display for BinaryState +where + Q: core::fmt::Display, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + + write!(f, "{}", *self) + } +} From c339c2057943146de8588f4e57795980872f2129 Mon Sep 17 00:00:00 2001 From: Joe McCain III <92560746+FL03@users.noreply.github.com> Date: Fri, 12 Jul 2024 00:56:26 -0500 Subject: [PATCH 04/21] Update triad.rs --- triad/src/triad.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/triad/src/triad.rs b/triad/src/triad.rs index fe3aaae..a5973f0 100644 --- a/triad/src/triad.rs +++ b/triad/src/triad.rs @@ -17,15 +17,18 @@ pub type TriadId = String; pub struct Triad { pub(crate) id: TriadId, pub(crate) state: BinaryState, - pub(crate) store: TriadGraph, + pub(crate) shape: TriadGraph, } impl Triad { pub fn new(id: TriadId) -> Self { + let shape = TriadGraph::new(); + let state = BinaryState::default(); Self { id, - state: BinaryState::default(), - store: TriadGraph::new(), + shape, + state, + } } } From 9ab36dff1d2707a5c2c07e9f7cc35dd6c1b911b1 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 09:48:13 -0500 Subject: [PATCH 05/21] update --- core/src/errors/err.rs | 4 +- core/src/lib.rs | 6 +- core/src/macros.rs | 34 ++++++ core/src/macros/intervals.rs | 76 +++++++----- core/src/macros/pitches.rs | 34 +++--- core/src/notes/note.rs | 52 +++++++- core/src/{pitches => pitch}/kinds.rs | 38 +++++- core/src/pitch/mod.rs | 71 +++++++++++ core/src/pitch/pitch.rs | 69 +++++++++++ core/src/pitches/mod.rs | 72 ------------ core/src/pitches/pitch.rs | 41 ------- core/src/traits/absmod.rs | 37 ++++++ core/src/traits/distance.rs | 17 +++ core/src/traits/mod.rs | 23 +++- core/src/types/intervals.rs | 72 ++++++------ core/src/types/mod.rs | 7 +- core/tests/pitches.rs | 4 +- triad/src/triad/state.rs | 170 +++++++++++++++++---------- 18 files changed, 552 insertions(+), 275 deletions(-) rename core/src/{pitches => pitch}/kinds.rs (78%) create mode 100644 core/src/pitch/mod.rs create mode 100644 core/src/pitch/pitch.rs delete mode 100644 core/src/pitches/mod.rs delete mode 100644 core/src/pitches/pitch.rs create mode 100644 core/src/traits/absmod.rs create mode 100644 core/src/traits/distance.rs diff --git a/core/src/errors/err.rs b/core/src/errors/err.rs index 15459f7..848615f 100644 --- a/core/src/errors/err.rs +++ b/core/src/errors/err.rs @@ -29,8 +29,9 @@ impl ErrorKind for T where T: Clone + core::str::FromStr + core::fmt::Debug + #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "TitleCase") + serde(rename_all = "PascalCase") )] +#[strum(serialize_all = "PascalCase")] pub enum MusicalError { InvalidInterval, InvalidPitch, @@ -41,6 +42,7 @@ impl std::error::Error for MusicalError {} #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + pub struct Error { pub kind: K, pub msg: String, diff --git a/core/src/lib.rs b/core/src/lib.rs index 5d5debb..e84d496 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -11,7 +11,7 @@ extern crate alloc; pub use self::errors::{Error, Result}; -pub use self::pitches::{Pitch, PitchClass}; +pub use self::pitch::{Pitch, PitchClass, PitchTy}; pub use self::{primitives::*, types::prelude::*}; #[macro_use] @@ -20,14 +20,14 @@ pub(crate) mod primitives; pub mod errors; pub mod notes; -pub mod pitches; +pub mod pitch; pub mod traits; pub mod types; pub mod prelude { pub use super::errors::prelude::*; pub use super::notes::prelude::*; - pub use super::pitches::prelude::*; + pub use super::pitch::prelude::*; pub use super::primitives::prelude::*; pub use super::traits::prelude::*; pub use super::types::prelude::*; diff --git a/core/src/macros.rs b/core/src/macros.rs index 1bbc57a..d7f5d78 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -11,3 +11,37 @@ pub(crate) mod intervals; pub(crate) mod pitches; #[macro_use] pub(crate) mod seal; + +macro_rules! unit_enum { + ($(#[derive($($der:ident),*$(,)?)])? $vis:vis enum $class:ident $($rest:tt)*) => { + unit_enum!( + rename: "lowercase"; + $(#[derive($($der),*)])? + $vis enum $class $($rest)* + ); + }; + (rename: $rename:literal; $(#[derive($($der:ident),*$(,)?)])? $vis:vis enum $class:ident $($rest:tt)*) => { + + #[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::VariantNames, + $($($der),*)? + )] + #[repr(i8)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = $rename))] + #[strum(serialize_all = $rename)] + $vis enum $class $($rest)* + }; + +} diff --git a/core/src/macros/intervals.rs b/core/src/macros/intervals.rs index 71dada8..a648c68 100644 --- a/core/src/macros/intervals.rs +++ b/core/src/macros/intervals.rs @@ -4,46 +4,58 @@ */ macro_rules! interval { - ($vis:vis enum $name:ident {$($key:ident = $val:literal),* $(,)?}) => { - #[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - strum::AsRefStr, - strum::Display, - strum::EnumCount, - strum::EnumIs, - strum::EnumIter, - strum::EnumString, - strum::VariantArray, - strum::VariantNames, - )] - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] - #[repr(u8)] - #[strum(serialize_all = "lowercase")] - $vis enum $name { - $($key = $val),* - } - - impl From<$name> for u8 { - fn from(interval: $name) -> u8 { - interval as u8 + ($(default: $variant:ident$(,)?)?; $vis:vis enum $name:ident {$($key:ident = $val:literal),* $(,)?}) => { + unit_enum! { + $vis enum $name { + $($key = $val),* } } - impl From for $name { - fn from(interval: u8) -> $name { + enum_as!($name: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + + impl From for $name where { + fn from(interval: i8) -> $name { use strum::EnumCount; - match interval % Self::COUNT as u8 { + match interval % Self::COUNT as i8 { $($val => $name::$key),*, _ => panic!("Invalid interval value: {}", interval), } } } + enum_from_value!(u8 => $name {$($key: $val),*}); + $( + impl Default for $name { + fn default() -> Self { + $name::$variant + } + } + )? + }; +} + +macro_rules! enum_from_value { + ($T:ty => $name:ident {$($key:ident: $val:expr),* $(,)?}) => { + + impl From<$T> for $name { + fn from(value: $T) -> Self { + use strum::EnumCount; + match value % Self::COUNT as $T { + $(x if x == $val => Self::$key,)* + _ => panic!("Invalid value {}", value), + } + } + } + }; +} + +macro_rules! enum_as { + ($name:ident: $($T:ty),* $(,)?) => { + $( + impl From<$name> for $T { + fn from(interval: $name) -> $T { + interval as $T + } + } + )* }; } diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index 886b6dc..1d3e510 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -8,46 +8,46 @@ macro_rules! pitch { #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] #[repr(i8)] - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] - #[strum(serialize_all = "lowercase")] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "UPPERCASE"))] + #[strum(serialize_all = "UPPERCASE")] $vis enum $class { $($name = $value),* } impl $class { - pub fn new(value: $crate::pitches::PitchTy) -> $crate::Result { - match value.abs() % $crate::MODULUS as $crate::pitches::PitchTy { + pub fn try_from_value(value: $crate::PitchTy) -> $crate::Result { + match $crate::traits::PitchMod::pitchmod(&value) { $(x if x == $value => Ok(Self::$name),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } } - pub fn as_class(&self) -> $crate::pitches::Pitches { - $crate::pitches::Pitches::$class(*self) + pub fn as_class(&self) -> $crate::pitch::Pitches { + $crate::pitch::Pitches::$class(*self) } - pub fn pitch(&self) -> $crate::pitches::PitchTy { - *self as $crate::pitches::PitchTy + pub fn pitch(&self) -> $crate::PitchTy { + *self as $crate::PitchTy } } - impl $crate::pitches::PitchClass for $class { - fn pitch(&self) -> $crate::pitches::PitchTy { - *self as $crate::pitches::PitchTy + impl $crate::pitch::PitchClass for $class { + fn pitch(&self) -> $crate::PitchTy { + *self as $crate::PitchTy } } - impl From<$class> for $crate::pitches::PitchTy { - fn from(pitch: $class) -> $crate::pitches::PitchTy { - pitch as $crate::pitches::PitchTy + impl From<$class> for $crate::PitchTy { + fn from(pitch: $class) -> $crate::PitchTy { + pitch as $crate::PitchTy } } - impl TryFrom<$crate::pitches::PitchTy> for $class { + impl TryFrom<$crate::PitchTy> for $class { type Error = $crate::Error; - fn try_from(value: PitchTy) -> Result { - Self::new(value) + fn try_from(value: $crate::PitchTy) -> Result { + Self::try_from_value(value) } } }; diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index eb9c70d..223f803 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -2,10 +2,56 @@ Appellation: note Contrib: FL03 */ -use crate::pitches::Pitch; +use crate::pitch::{PitchT, PitchTy}; +use crate::Octave; #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Note { - pub(crate) pitch: Pitch, +pub struct Note

{ + pub(crate) octave: Octave, + pub(crate) pitch: P, +} + +impl

Note

+where + P: PitchT, +{ + pub fn new(octave: Octave, pitch: P) -> Self { + Self { octave, pitch } + } + /// Returns an owned instance of the note's octave + pub const fn octave(&self) -> &Octave { + &self.octave + } + + pub const fn pitch(&self) -> &P { + &self.pitch + } + + pub fn set_octave(&mut self, octave: Octave) { + self.octave = octave; + } + + pub fn with_octave(self, octave: Octave) -> Self { + Self { octave, ..self } + } + + pub fn with_pitch(self, pitch: P2) -> Note + where + P2: PitchT, + { + Note { + octave: self.octave, + pitch, + } + } +} + +impl

core::fmt::Display for Note

+where + P: PitchT, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}.{}", self.pitch, self.octave) + } } diff --git a/core/src/pitches/kinds.rs b/core/src/pitch/kinds.rs similarity index 78% rename from core/src/pitches/kinds.rs rename to core/src/pitch/kinds.rs index bb0e7a2..f98509d 100644 --- a/core/src/pitches/kinds.rs +++ b/core/src/pitch/kinds.rs @@ -3,6 +3,7 @@ Contrib: FL03 */ use super::{PitchClass, PitchTy}; +use crate::Octave; pitch! { pub enum Natural { @@ -18,11 +19,11 @@ pitch! { pitch! { pub enum Sharp { - A = 10, C = 1, D = 3, F = 6, G = 8, + A = 10, } } @@ -36,6 +37,31 @@ pitch! { } } +pub struct A { + pub(crate) octave: Octave, + pub(crate) pitch: PitchTy, +} + +pub trait SharpPitch { + private!(); +} + +pub trait FlatPitch { + private!(); +} + +impl FlatPitch for A { + seal!(); +} + +impl FlatPitch for Flat { + seal!(); +} + +impl SharpPitch for Sharp { + seal!(); +} + pub trait AccidentalPitch: PitchClass { private!(); } @@ -79,12 +105,12 @@ pub enum Pitches { } impl Pitches { - pub fn from_value(value: PitchTy) -> crate::Result { - if let Ok(n) = Natural::new(value) { + pub fn try_from_value(value: PitchTy) -> crate::Result { + if let Ok(n) = Natural::try_from_value(value) { Ok(n.as_class()) - } else if let Ok(s) = Sharp::new(value) { + } else if let Ok(s) = Sharp::try_from_value(value) { Ok(s.as_class()) - } else if let Ok(f) = Flat::new(value) { + } else if let Ok(f) = Flat::try_from_value(value) { Ok(f.as_class()) } else { Err(crate::Error::invalid_pitch("Invalid pitch value.")) @@ -114,7 +140,7 @@ impl From for PitchTy { impl From for Pitches { fn from(value: PitchTy) -> Pitches { - Self::from_value(value).unwrap() + Self::try_from_value(value).unwrap() } } diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs new file mode 100644 index 0000000..bed255f --- /dev/null +++ b/core/src/pitch/mod.rs @@ -0,0 +1,71 @@ +/* + Appellation: pitches + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::{kinds::*, pitch::Pitch, utils::*}; + +pub(crate) mod kinds; +pub(crate) mod pitch; + +/// A type alias for an integer representing a particular pitch of a note +pub type PitchTy = i8; + +pub trait PitchClass { + fn pitch(&self) -> PitchTy; +} + +pub trait PitchT: Copy + Sized + core::fmt::Display { + /// Classify the pitch into a pitch class + fn class(&self) -> Pitches { + self.value().into() + } + /// Find the modular index of the given pitch + fn value(&self) -> PitchTy; +} + +mod impl_pitches { + use super::*; + + impl PitchT for Pitch { + fn class(&self) -> Pitches { + self.as_class() + } + + fn value(&self) -> PitchTy { + self.value() + } + } + + impl PitchT for Pitches { + fn class(&self) -> Pitches { + self.clone() + } + + fn value(&self) -> PitchTy { + self.class().into() + } + } + + impl PitchT for PitchTy { + fn class(&self) -> Pitches { + Pitches::try_from_value(*self).unwrap() + } + + fn value(&self) -> PitchTy { + *self + } + } +} +pub(crate) mod prelude { + pub use super::kinds::*; + pub use super::{PitchClass, PitchTy}; +} + +pub(crate) mod utils { + + /// [absmod] is short for the absolute value of a modular number; + pub fn absmod(a: i64, m: i64) -> i64 { + (((a % m) + m) % m).abs() + } +} diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs new file mode 100644 index 0000000..c163571 --- /dev/null +++ b/core/src/pitch/pitch.rs @@ -0,0 +1,69 @@ +/* + Appellation: pitch + Contrib: FL03 +*/ +use super::{PitchTy, Pitches}; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Pitch(pub PitchTy); + +impl Pitch { + pub fn new(pitch: PitchTy) -> Self { + Self(pitch) + } + /// Returns a new instance of the class representing the given pitch. + pub fn as_class(&self) -> Pitches { + Pitches::try_from_value(self.0).unwrap() + } + /// Consumes the pitch; returns a new instance of the class representing the given pitch. + pub fn into_class(self) -> Pitches { + Pitches::try_from_value(self.0).unwrap() + } + /// Consumes the object, returning the assigned pitch value. + pub fn into_inner(self) -> PitchTy { + self.0 + } + /// A functional accessor for the pitch value. + pub fn value(&self) -> PitchTy { + self.0 + } +} + +impl AsRef for Pitch { + fn as_ref(&self) -> &PitchTy { + &self.0 + } +} + +impl AsMut for Pitch { + fn as_mut(&mut self) -> &mut PitchTy { + &mut self.0 + } +} + +impl Default for Pitch { + fn default() -> Self { + Self(super::Natural::default().pitch()) + } +} + +impl core::ops::Deref for Pitch { + type Target = PitchTy; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl core::ops::DerefMut for Pitch { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +impl core::fmt::Display for Pitch { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/core/src/pitches/mod.rs b/core/src/pitches/mod.rs deleted file mode 100644 index 971768b..0000000 --- a/core/src/pitches/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -/* - Appellation: pitches - Contrib: FL03 -*/ -#[doc(inline)] -pub use self::{kinds::*, pitch::Pitch, utils::*}; - -pub(crate) mod kinds; -pub(crate) mod pitch; - -/// A type alias for an integer representing a particular pitch of a note -pub type PitchTy = i8; - -pub trait PitchClass { - fn pitch(&self) -> PitchTy; -} - -pub trait Pitched { - /// Classify the pitch into a pitch class - fn class(&self) -> Pitches { - self.pitch().into() - } - /// Find the modular index of the given pitch - fn pitch(&self) -> PitchTy; -} - -pub(crate) mod prelude { - pub use super::kinds::*; - pub use super::{PitchClass, PitchTy}; -} - -pub(crate) mod utils { - use num::traits::Signed; - - pub trait AbsMod { - type Output; - - fn absmod(&self, rhs: Rhs) -> Self::Output; - } - - impl AbsMod for A - where - A: Copy + core::ops::Rem + core::ops::Add, - B: Copy, - C: core::ops::Add + core::ops::Rem + Signed, - { - type Output = C; - - fn absmod(&self, rhs: B) -> Self::Output { - (((*self % rhs) + rhs) % rhs).abs() - } - } - /// [absmod] is short for the absolute value of a modular number; - pub fn absmod(a: i64, m: i64) -> i64 { - (((a % m) + m) % m).abs() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_absmod() { - assert_eq!(absmod(5, 12), 5.absmod(12)); - assert_eq!(absmod(-5, 12), (-5).absmod(12)); - assert_eq!(absmod(0, 12), 0.absmod(12)); - assert_eq!(absmod(12, 12), 24.absmod(12)); - assert_eq!(absmod(13, 12), 1.absmod(12)); - assert_eq!(absmod(-13, 12), 11.absmod(12)); - } -} diff --git a/core/src/pitches/pitch.rs b/core/src/pitches/pitch.rs deleted file mode 100644 index 7730920..0000000 --- a/core/src/pitches/pitch.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* - Appellation: pitch - Contrib: FL03 -*/ -use super::PitchTy; - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Pitch(pub PitchTy); - -impl AsRef for Pitch { - fn as_ref(&self) -> &PitchTy { - &self.0 - } -} - -impl AsMut for Pitch { - fn as_mut(&mut self) -> &mut PitchTy { - &mut self.0 - } -} - -impl Default for Pitch { - fn default() -> Self { - Self(super::Natural::default().pitch()) - } -} - -impl core::ops::Deref for Pitch { - type Target = PitchTy; - - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl core::ops::DerefMut for Pitch { - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut() - } -} diff --git a/core/src/traits/absmod.rs b/core/src/traits/absmod.rs new file mode 100644 index 0000000..42d8e1a --- /dev/null +++ b/core/src/traits/absmod.rs @@ -0,0 +1,37 @@ +/* + Appellation: absmod + Contrib: FL03 +*/ +use crate::pitch::PitchTy; +use num::traits::Signed; + +pub trait AbsMod { + type Output; + + fn absmod(&self, rhs: Rhs) -> Self::Output; +} + +pub trait PitchMod: AbsMod { + fn pitchmod(&self) -> Self::Output { + self.absmod(crate::MODULUS as PitchTy) + } +} + +impl PitchMod for S where S: AbsMod {} + +/* + ************* Implementations ************* +*/ + +impl AbsMod for A +where + A: Copy + core::ops::Rem + core::ops::Add, + B: Copy, + C: core::ops::Add + core::ops::Rem + Signed, +{ + type Output = C; + + fn absmod(&self, rhs: B) -> Self::Output { + (((*self % rhs) + rhs) % rhs).abs() + } +} diff --git a/core/src/traits/distance.rs b/core/src/traits/distance.rs new file mode 100644 index 0000000..4305dd5 --- /dev/null +++ b/core/src/traits/distance.rs @@ -0,0 +1,17 @@ +/* + Appellation: distance + Contrib: FL03 +*/ + +/// This trait is used for computing the distance between two objects; +/// The generality of the trait enables developers to implement it for +/// their algorithms of choice operating on two objects within the same +/// space. +/// +/// Musically, the distance between two pitches is considered to be an interval. +/// Intervals are typically measured in semitones (+/- 1) or tones (+/- 2). +pub trait Distance { + type Output; + + fn distance(self, rhs: Rhs) -> Self::Output; +} diff --git a/core/src/traits/mod.rs b/core/src/traits/mod.rs index d07e4ea..89e659b 100644 --- a/core/src/traits/mod.rs +++ b/core/src/traits/mod.rs @@ -2,7 +2,26 @@ Appellation: traits Contrib: FL03 */ -#[allow(unused_imports)] +#[doc(inline)] pub use self::prelude::*; -pub(crate) mod prelude {} +pub mod absmod; +pub mod distance; + +pub(crate) mod prelude { + pub use super::absmod::*; + pub use super::distance::*; +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_absmod() { + use super::absmod::{AbsMod, PitchMod}; + let pairs = [(5, 12), (-5, 12), (0, 12), (12, 12), (13, 12), (-13, 12)]; + for (i, j) in pairs.iter() { + assert_eq!(i.absmod(*j), i.pitchmod()); + } + } +} diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs index 11f3d2f..a4bc889 100644 --- a/core/src/types/intervals.rs +++ b/core/src/types/intervals.rs @@ -2,39 +2,39 @@ Appellation: intervals Contrib: FL03 */ +use crate::PitchTy; -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - strum::AsRefStr, - strum::Display, - strum::EnumCount, - strum::EnumIs, - strum::VariantNames, -)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "lowercase") -)] -#[repr(u8)] -#[strum(serialize_all = "lowercase")] -pub enum Intervals { - Semitone = 1, - Tone = 2, - Thirds(Third), - Fourths(Fourth), - Fifths(Fifth), - Sevenths(Seventh), +use num::traits::NumOps; + +pub trait IntervalOps: NumOps {} + +unit_enum! { + rename: "lowercase"; + #[derive(Default)] + pub enum Intervals { + #[default] + Semitone = 1, + Tone = 2, + Thirds(Third), + Fourths(Fourth), + Fifths(Fifth), + Sevenths(Seventh), + } } impl Intervals { + pub fn from_value(value: PitchTy) -> Self { + use Intervals::*; + match value { + 1 => Semitone, + 2 => Tone, + 3..=4 => Thirds(Third::from(value)), + 5 => Fourths(Fourth::from(value)), + 6..=8 => Fifths(Fifth::from(value)), + 9..=12 => Sevenths(Seventh::from(value)), + _ => panic!("Invalid interval value: {}", value), + } + } pub fn from_semitone() -> Self { Intervals::Semitone } @@ -63,7 +63,7 @@ impl Intervals { self.as_ref() } - pub fn value(&self) -> i8 { + pub fn value(&self) -> PitchTy { match *self { Intervals::Semitone => 1, Intervals::Tone => 2, @@ -100,6 +100,7 @@ impl From for Intervals { } interval! { + default: Major; pub enum Third { Minor = 3, Major = 4, @@ -107,24 +108,27 @@ interval! { } interval! { + default: Perfect; pub enum Fourth { Perfect = 5, } } interval! { + default: Perfect; pub enum Fifth { - Augmented = 8, - Perfect = 7, Diminished = 6, + Perfect = 7, + Augmented = 8, } } interval! { + default: Diminished; pub enum Seventh { - Augmented = 12, Diminished = 9, - Major = 11, Minor = 10, + Major = 11, + Augmented = 12, } } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 543729e..b9b480e 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,10 +2,15 @@ Appellation: types Contrib: FL03 */ -pub use self::prelude::*; +pub use self::intervals::*; pub mod intervals; +/// A type alias for an `octave`; Musically speaking, an octave is the interval (distance) between one musical pitch and another +/// with either half or double its frequency. +pub type Octave = i8; + pub(crate) mod prelude { pub use super::intervals::*; + pub use super::Octave; } diff --git a/core/tests/pitches.rs b/core/tests/pitches.rs index ca5b4fa..3a98ee2 100644 --- a/core/tests/pitches.rs +++ b/core/tests/pitches.rs @@ -2,7 +2,7 @@ Appellation: pitches Contrib: FL03 */ -use triad_core::pitches::*; +use triad_core::pitch::*; fn assert_ok(result: Result) -> T where @@ -14,7 +14,7 @@ where #[test] fn test_pitch() { - let pitch = assert_ok(Pitches::from_value(12)); + let pitch = assert_ok(Pitches::try_from_value(12)); let rhs = Natural::C; assert_eq!(pitch, rhs.as_class()); } diff --git a/triad/src/triad/state.rs b/triad/src/triad/state.rs index 64bcb33..27869a1 100644 --- a/triad/src/triad/state.rs +++ b/triad/src/triad/state.rs @@ -2,99 +2,147 @@ Appellation: state Contrib: FL03 */ +pub use self::binary::*; + use crate::triad::Triads; #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TriadState { pub(crate) class: Triads, pub(crate) hash: String, } impl TriadState { + pub fn new(class: Triads) -> Self { + Self { + class, + hash: String::new(), + } + } } -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::EnumIs, strum::VariantNames)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] -#[repr(C)] -#[strum(serialize_all = "lowercase")] -pub enum BinaryState { - Invalid(Q), - Valid(Q), -} - -impl BinaryState { - pub fn invalid(state: Q) -> Self { - Self::Invalid(state) +pub(crate) mod binary { + + #[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::EnumDiscriminants, + strum::EnumIs, + strum::VariantNames, + )] + #[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase"), + strum_discriminants( + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") + ) + )] + #[repr(C)] + #[strum(serialize_all = "lowercase")] + #[strum_discriminants( + name(BinaryStates), + derive( + Ord, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::EnumString, + strum::VariantNames + ) + )] + pub enum BinaryState { + Invalid(Q), + Valid(Q), } - pub fn valid(state: Q) -> Self { - Self::Valid(state) - } + impl BinaryState { + pub fn invalid(state: Q) -> Self { + Self::Invalid(state) + } - pub fn kind(&self) -> &str { - match self { - Self::Invalid(_) => "invalid", - Self::Valid(_) => "valid", + pub fn valid(state: Q) -> Self { + Self::Valid(state) } - } - pub fn into_inner(self) -> Q { - match self { - Self::Invalid(q) => q, - Self::Valid(q) => q, + pub fn into_inner(self) -> Q { + match self { + Self::Invalid(q) => q, + Self::Valid(q) => q, + } } - } - pub fn invalidate(self) -> Self { - Self::Invalid(self.into_inner()) - } -} + pub fn invalidate(self) -> Self { + Self::Invalid(self.into_inner()) + } + pub fn kind(&self) -> BinaryStates { + match self { + Self::Invalid(_) => BinaryStates::Invalid, + Self::Valid(_) => BinaryStates::Valid, + } + } -impl AsRef for BinaryState { - fn as_ref(&self) -> &Q { - match self { - Self::Invalid(q) => q, - Self::Valid(q) => q, + pub fn state(&self) -> (BinaryStates, &Q) { + (self.kind(), self.as_ref()) } } -} -impl AsMut for BinaryState { - fn as_mut(&mut self) -> &mut Q { - match self { - Self::Invalid(q) => q, - Self::Valid(q) => q, + impl AsRef for BinaryState { + fn as_ref(&self) -> &Q { + match self { + Self::Invalid(q) => q, + Self::Valid(q) => q, + } } } -} -impl Default for BinaryState { - fn default() -> Self { - Self::Invalid(Q::default()) + impl AsMut for BinaryState { + fn as_mut(&mut self) -> &mut Q { + match self { + Self::Invalid(q) => q, + Self::Valid(q) => q, + } + } + } + + impl Default for BinaryState { + fn default() -> Self { + Self::Invalid(Q::default()) + } } -} -impl core::ops::Deref for BinaryState { - type Target = Q; + impl core::ops::Deref for BinaryState { + type Target = Q; - fn deref(&self) -> &Self::Target { - self.as_ref() + fn deref(&self) -> &Self::Target { + self.as_ref() + } } -} -impl core::ops::DerefMut for BinaryState { - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut() + impl core::ops::DerefMut for BinaryState { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } } -} -impl core::fmt::Display for BinaryState -where - Q: core::fmt::Display, -{ - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - - write!(f, "{}", *self) + impl core::fmt::Display for BinaryState + where + Q: core::fmt::Display, + { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", *self) + } } } From 3090dda4660f8177da453af148d8ace13e7eb10c Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 12:54:52 -0500 Subject: [PATCH 06/21] update --- .github/dependabot.yml | 10 +++- .github/workflows/crates.yml | 26 ++++++++++- Cargo.toml | 5 +- README.md | 25 ++++------ core/Cargo.toml | 2 +- core/tests/pitches.rs | 4 +- {triad => neo}/Cargo.toml | 18 ++------ {triad => neo}/build.rs | 0 {triad => neo}/src/lib.rs | 8 +--- {triad => neo}/src/triad.rs | 0 {triad => neo}/src/triad/builder.rs | 0 {triad => neo}/src/triad/classes.rs | 0 {triad => neo}/src/triad/factors.rs | 0 {triad => neo}/src/triad/state.rs | 0 {triad => neo}/tests/default.rs | 0 rstmt/Cargo.toml | 71 +++++++++++++++++++++++++++++ rstmt/build.rs | 8 ++++ rstmt/src/lib.rs | 20 ++++++++ rstmt/tests/default.rs | 17 +++++++ 19 files changed, 173 insertions(+), 41 deletions(-) rename {triad => neo}/Cargo.toml (86%) rename {triad => neo}/build.rs (100%) rename {triad => neo}/src/lib.rs (74%) rename {triad => neo}/src/triad.rs (100%) rename {triad => neo}/src/triad/builder.rs (100%) rename {triad => neo}/src/triad/classes.rs (100%) rename {triad => neo}/src/triad/factors.rs (100%) rename {triad => neo}/src/triad/state.rs (100%) rename {triad => neo}/tests/default.rs (100%) create mode 100644 rstmt/Cargo.toml create mode 100644 rstmt/build.rs create mode 100644 rstmt/src/lib.rs create mode 100644 rstmt/tests/default.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index df86eb7..78e80a3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,14 @@ updates: schedule: interval: weekly - package-ecosystem: cargo - directory: /triad + directory: /core + schedule: + interval: weekly + - package-ecosystem: cargo + directory: /neo + schedule: + interval: weekly + - package-ecosystem: cargo + directory: /rstmt schedule: interval: weekly diff --git a/.github/workflows/crates.yml b/.github/workflows/crates.yml index 2f5a882..21931f9 100644 --- a/.github/workflows/crates.yml +++ b/.github/workflows/crates.yml @@ -6,6 +6,7 @@ concurrency: env: BASENAME: ${{ github.event.repository.name }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} CARGO_TERM_COLOR: always on: @@ -16,9 +17,32 @@ on: workflow_dispatch: jobs: + core: + name: Publish (${{ github.event.repository.name }}) + runs-on: ubuntu-latest + strategy: + matrix: + features: [ core ] + steps: + - uses: actions/checkout@v4 + - name: Publish (${{ matrix.features }}) + run: cargo publish --all-features -v -p ${{ github.event.repository.name }}-${{ matrix.features }} + features: + name: Publish (${{ github.event.repository.name }}) + needs: [ core ] + runs-on: ubuntu-latest + strategy: + matrix: + features: [ neo ] + steps: + - uses: actions/checkout@v4 + - name: Publish (${{ matrix.features }}) + run: cargo publish --all-features -v -p ${{ github.event.repository.name }}-${{ matrix.features }} publish: name: Publish (${{ github.event.repository.name }}) + needs: [ features ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: cargo publish --all-features -v -p ${{ github.event.repository.name }} --token ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file + - name: Publish (${{ github.event.repository.name }}) + run: cargo publish --all-features -v -p ${{ github.event.repository.name }} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dedbe6f..6d7c821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] default-members = [ - "triad" + "rstmt" ] exclude = [ @@ -9,7 +9,8 @@ exclude = [ members = [ "core", - "triad", + "neo", + "rstmt", ] resolver = "2" diff --git a/README.md b/README.md index e7291dc..8de8c22 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# triad +# rstmt -[![crates.io](https://img.shields.io/crates/v/concision.svg)](https://crates.io/crates/triad) -[![docs.rs](https://docs.rs/concision/badge.svg)](https://docs.rs/triad) +[![crates.io](https://img.shields.io/crates/v/rstmt.svg)](https://crates.io/crates/rstmt) +[![docs.rs](https://docs.rs/rstmt/badge.svg)](https://docs.rs/rstmt) -[![clippy](https://github.com/FL03/triad/actions/workflows/clippy.yml/badge.svg)](https://github.com/FL03/triad/actions/workflows/clippy.yml) -[![rust](https://github.com/FL03/triad/actions/workflows/rust.yml/badge.svg)](https://github.com/FL03/triad/actions/workflows/rust.yml) +[![clippy](https://github.com/FL03/rstmt/actions/workflows/clippy.yml/badge.svg)](https://github.com/FL03/rstmt/actions/workflows/clippy.yml) +[![rust](https://github.com/FL03/rstmt/actions/workflows/rust.yml/badge.svg)](https://github.com/FL03/rstmt/actions/workflows/rust.yml) *** @@ -14,7 +14,7 @@ This project focuses on providing concrete abstractions of musical objects discu ## Features - +- [] The Neo-Riemannian Theory ## Getting Started @@ -23,7 +23,7 @@ This project focuses on providing concrete abstractions of musical objects discu Start by cloning the repository ```bash -git clone https://github.com/FL03/triad.git +git clone https://github.com/FL03/rstmt.git cd triad ``` @@ -33,18 +33,11 @@ cargo build --features full -r --workspace ## Usage -### Example: Linear Model (biased) +### Example ```rust - extern crate triad; - - fn main() -> anyhow::Result<()> { - tracing_subscriber::fmt::init(); - tracing::info!("Starting linear model example"); - + extern crate rstmt; - Ok(()) - } ``` ## Contributing diff --git a/core/Cargo.toml b/core/Cargo.toml index b9de3a1..e0ec847 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true -name = "triad-core" +name = "rstmt-core" readme.workspace = true repository.workspace = true version.workspace = true diff --git a/core/tests/pitches.rs b/core/tests/pitches.rs index 3a98ee2..4b25d68 100644 --- a/core/tests/pitches.rs +++ b/core/tests/pitches.rs @@ -2,7 +2,9 @@ Appellation: pitches Contrib: FL03 */ -use triad_core::pitch::*; +extern crate rstmt_core as rstmt; + +use rstmt::pitch::*; fn assert_ok(result: Result) -> T where diff --git a/triad/Cargo.toml b/neo/Cargo.toml similarity index 86% rename from triad/Cargo.toml rename to neo/Cargo.toml index cb4b415..91b5367 100644 --- a/triad/Cargo.toml +++ b/neo/Cargo.toml @@ -7,15 +7,13 @@ edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true -name = "triad" +name = "rstmt-neo" readme.workspace = true repository.workspace = true version.workspace = true [features] -default = [ - -] +default = [] full = [ "default", @@ -26,21 +24,15 @@ full = [ # [FF] Dependencies rand = [ "num/rand", + "rstmt-core/rand", ] serde = [ "dep:serde", "petgraph/serde", - "triad-core/serde", + "rstmt-core/serde", ] -# ********* [FF] Environments ********* - - -wasm = [] - -wasi = [] - [lib] bench = true crate-type = ["cdylib", "rlib"] @@ -59,7 +51,7 @@ features = ["derive"] optional = true version = "1" -[dependencies.triad-core] +[dependencies.rstmt-core] path = "../core" version = "0.0.1" diff --git a/triad/build.rs b/neo/build.rs similarity index 100% rename from triad/build.rs rename to neo/build.rs diff --git a/triad/src/lib.rs b/neo/src/lib.rs similarity index 74% rename from triad/src/lib.rs rename to neo/src/lib.rs index 86f75dc..3645ab9 100644 --- a/triad/src/lib.rs +++ b/neo/src/lib.rs @@ -2,13 +2,10 @@ Appellation: rstopo Contrib: FL03 */ -//! # triad +//! # rstmt-neo //! //! This project focuses on providing concrete abstractions of musical objects discussed within the neo-Riemannian theory. -#![crate_name = "triad"] - -#[doc(inline)] -pub use triad_core::*; +extern crate rstmt_core as rstmt; pub use self::triad::Triad; @@ -16,5 +13,4 @@ pub mod triad; pub mod prelude { pub use crate::triad::{ChordFactor, Triad}; - pub use triad_core::prelude::*; } diff --git a/triad/src/triad.rs b/neo/src/triad.rs similarity index 100% rename from triad/src/triad.rs rename to neo/src/triad.rs diff --git a/triad/src/triad/builder.rs b/neo/src/triad/builder.rs similarity index 100% rename from triad/src/triad/builder.rs rename to neo/src/triad/builder.rs diff --git a/triad/src/triad/classes.rs b/neo/src/triad/classes.rs similarity index 100% rename from triad/src/triad/classes.rs rename to neo/src/triad/classes.rs diff --git a/triad/src/triad/factors.rs b/neo/src/triad/factors.rs similarity index 100% rename from triad/src/triad/factors.rs rename to neo/src/triad/factors.rs diff --git a/triad/src/triad/state.rs b/neo/src/triad/state.rs similarity index 100% rename from triad/src/triad/state.rs rename to neo/src/triad/state.rs diff --git a/triad/tests/default.rs b/neo/tests/default.rs similarity index 100% rename from triad/tests/default.rs rename to neo/tests/default.rs diff --git a/rstmt/Cargo.toml b/rstmt/Cargo.toml new file mode 100644 index 0000000..5afb4c1 --- /dev/null +++ b/rstmt/Cargo.toml @@ -0,0 +1,71 @@ +[package] +authors.workspace = true +build = "build.rs" +categories.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "rstmt" +readme.workspace = true +repository.workspace = true +version.workspace = true + +[features] +default = [ + "std", +] + +full = [ + "default", + "neo", + "rand", + "serde", +] + +# ********* [FF] Features ********* +neo = [ + "dep:rstmt-neo", +] + +# ********* [FF] Dependencies ********* +alloc = [ + "rstmt-core/alloc", +] + +rand = [ + "rstmt-core/rand", + "rstmt-neo?/rand", +] + +serde = [ + "rstmt-core/serde", + "rstmt-neo?/serde", +] + +# ********* [FF] Environments ********* +std = [ + "rstmt-core/std", +] + +[lib] +bench = true +crate-type = ["cdylib", "rlib"] +doctest = false +test = true + +[dependencies.rstmt-core] +default-features = false +path = "../core" +version = "0.0.1" + +[dependencies.rstmt-neo] +default-features = false +optional = true +path = "../neo" +version = "0.0.1" + +[package.metadata.docs.rs] +all-features = true +rustc-args = ["--cfg", "docsrs"] diff --git a/rstmt/build.rs b/rstmt/build.rs new file mode 100644 index 0000000..940a4ce --- /dev/null +++ b/rstmt/build.rs @@ -0,0 +1,8 @@ +/* + Appellation: build + Contrib: FL03 +*/ + +fn main() { + println!("cargo::rustc-check-cfg=cfg(no_std)"); +} diff --git a/rstmt/src/lib.rs b/rstmt/src/lib.rs new file mode 100644 index 0000000..fc75838 --- /dev/null +++ b/rstmt/src/lib.rs @@ -0,0 +1,20 @@ +/* + Appellation: rstmt + Contrib: FL03 +*/ +//! # rstmt +//! +//! This crate focuses on implementing various musical objects and operations. +#![crate_name = "rstmt"] + +#[doc(inline)] +pub use rstmt_core::*; +#[doc(inline)] +#[cfg(feature = "neo")] +pub use rstmt_neo as neo; + +pub mod prelude { + pub use rstmt_core::prelude::*; + #[cfg(feature = "neo")] + pub use rstmt_neo::prelude::*; +} diff --git a/rstmt/tests/default.rs b/rstmt/tests/default.rs new file mode 100644 index 0000000..233a07a --- /dev/null +++ b/rstmt/tests/default.rs @@ -0,0 +1,17 @@ +/* + Appellation: default + Contrib: FL03 +*/ + +fn add(a: A, b: B) -> C +where + A: core::ops::Add, +{ + a + b +} + +#[test] +fn compiles() { + assert_eq!(add(10, 10), 20); + assert_ne!(add(1, 1), 3); +} From b5c19e37bb24258a034da60179c2b2e48bef6fdb Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 14:00:21 -0500 Subject: [PATCH 07/21] update --- core/src/macros/pitches.rs | 3 +++ neo/src/lib.rs | 2 +- neo/src/triad.rs | 34 ---------------------------- neo/src/triad/mod.rs | 27 ++++++++++++++++++++++ neo/src/triad/triad.rs | 46 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 35 deletions(-) delete mode 100644 neo/src/triad.rs create mode 100644 neo/src/triad/mod.rs create mode 100644 neo/src/triad/triad.rs diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index 1d3e510..524364d 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -4,6 +4,9 @@ */ macro_rules! pitch { + ($name:ident: $($class:ident = $val:expr),* $(,)?) => { + + }; ($vis:vis enum $class:ident {$($name:ident = $value:expr),* $(,)?}) => { #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] diff --git a/neo/src/lib.rs b/neo/src/lib.rs index 3645ab9..aab5fd3 100644 --- a/neo/src/lib.rs +++ b/neo/src/lib.rs @@ -12,5 +12,5 @@ pub use self::triad::Triad; pub mod triad; pub mod prelude { - pub use crate::triad::{ChordFactor, Triad}; + pub use crate::triad::prelude::*; } diff --git a/neo/src/triad.rs b/neo/src/triad.rs deleted file mode 100644 index a5973f0..0000000 --- a/neo/src/triad.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Appellation: triad - Contrib: FL03 -*/ -pub use self::{builder::TriadBuilder, classes::*, factors::*, state::*}; - -pub(crate) mod builder; -pub(crate) mod classes; -pub(crate) mod factors; -pub(crate) mod state; - -pub type TriadGraph = petgraph::Graph; - -pub type TriadId = String; - -#[derive(Clone)] -pub struct Triad { - pub(crate) id: TriadId, - pub(crate) state: BinaryState, - pub(crate) shape: TriadGraph, -} - -impl Triad { - pub fn new(id: TriadId) -> Self { - let shape = TriadGraph::new(); - let state = BinaryState::default(); - Self { - id, - shape, - state, - - } - } -} diff --git a/neo/src/triad/mod.rs b/neo/src/triad/mod.rs new file mode 100644 index 0000000..0e8ee1d --- /dev/null +++ b/neo/src/triad/mod.rs @@ -0,0 +1,27 @@ +/* + Appellation: triad + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::{builder::TriadBuilder, classes::Triads, factors::ChordFactor, state::*, triad::Triad}; + +pub(crate) mod builder; +pub(crate) mod triad; + +pub mod classes; +pub mod factors; +pub mod state; + +pub type TriadGraph = petgraph::Graph; + +pub type TriadId = String; + +pub(crate) mod prelude { + pub use super::builder::TriadBuilder; + pub use super::classes::*; + pub use super::factors::ChordFactor; + pub use super::state::{BinaryState, BinaryStates, TriadState}; + pub use super::triad::Triad; + pub use super::{TriadGraph, TriadId}; + +} \ No newline at end of file diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs new file mode 100644 index 0000000..0501bc3 --- /dev/null +++ b/neo/src/triad/triad.rs @@ -0,0 +1,46 @@ +/* + Appellation: triad + Contrib: FL03 +*/ +use super::{TriadId, TriadGraph,}; +use super::state::{BinaryState, TriadState}; + +#[derive(Clone)] +pub struct Triad { + pub(crate) id: TriadId, + pub(crate) chord: TriadGraph, + pub(crate) state: BinaryState, + +} + +impl Triad { + pub fn new(id: TriadId) -> Self { + let chord = TriadGraph::new(); + let state = BinaryState::default(); + Self { + id, + chord, + state, + + } + } + + pub fn id(&self) -> &TriadId { + &self.id + } + + pub fn is_valid(&self) -> bool { + self.state().is_valid() + } + + pub fn chord(&self) -> &TriadGraph { + &self.chord + } + + pub const fn state(&self) -> &BinaryState { + &self.state + } + + + +} From 5b99fa02e488656918a53fe3428c828bea9e9f9a Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 14:36:58 -0500 Subject: [PATCH 08/21] update --- core/src/macros/pitches.rs | 74 ++++++++++++++++++++++++++++---------- core/src/pitch/kinds.rs | 69 +++++------------------------------ core/src/pitch/mod.rs | 49 ++++++++++++++++++------- 3 files changed, 99 insertions(+), 93 deletions(-) diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index 524364d..cd15faa 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -3,30 +3,63 @@ Contrib: FL03 */ -macro_rules! pitch { - ($name:ident: $($class:ident = $val:expr),* $(,)?) => { +macro_rules! pitch_class { + ($(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? $(#[rename($rename:literal)])? $vis:vis enum $name:ident $($rest:tt)*) => { + pitch_class!(@impl $(#[derive($($derive),*)])? $(#[default($default)])? $(#[rename($rename)])? $vis enum $name $($rest)*); + }; + (@impl $(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? $vis:vis enum $name:ident $($rest:tt)*) => { + pitch_class!($(#[derive($($derive),*)])? $(#[default($default)])? #[rename("UPPERCASE")] $vis enum $name $($rest)*); + }; + (@impl $(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? #[rename($rename:literal)] $vis:vis enum $name:ident $($rest:tt)*) => { + + #[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::VariantNames, + $($($derive),*)? + )] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = $rename))] + #[strum(serialize_all = $rename)] + pub enum $name $($rest)* + + impl_pitch!($name $($rest)*); + + $( + impl Default for $name { + fn default() -> Self { + Self::$default + } + } + )? }; - ($vis:vis enum $class:ident {$($name:ident = $value:expr),* $(,)?}) => { - - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumIs, strum::EnumString, strum::VariantNames)] - #[repr(i8)] - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "UPPERCASE"))] - #[strum(serialize_all = "UPPERCASE")] - $vis enum $class { - $($name = $value),* - } +} - impl $class { +macro_rules! impl_pitch { + ($group:ident {$($class:ident = $value:expr),* $(,)?}) => { + impl $group { + pub fn new(value: $crate::PitchTy) -> Option { + $group::try_from(value).ok() + } pub fn try_from_value(value: $crate::PitchTy) -> $crate::Result { match $crate::traits::PitchMod::pitchmod(&value) { - $(x if x == $value => Ok(Self::$name),)* + $(x if x == $value => Ok(Self::$class),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } } pub fn as_class(&self) -> $crate::pitch::Pitches { - $crate::pitch::Pitches::$class(*self) + $crate::pitch::Pitches::$group(*self) } pub fn pitch(&self) -> $crate::PitchTy { @@ -34,23 +67,26 @@ macro_rules! pitch { } } - impl $crate::pitch::PitchClass for $class { + impl $crate::pitch::PitchClass for $group { fn pitch(&self) -> $crate::PitchTy { *self as $crate::PitchTy } } - impl From<$class> for $crate::PitchTy { - fn from(pitch: $class) -> $crate::PitchTy { + impl From<$group> for $crate::PitchTy { + fn from(pitch: $group) -> $crate::PitchTy { pitch as $crate::PitchTy } } - impl TryFrom<$crate::PitchTy> for $class { + impl TryFrom<$crate::PitchTy> for $group { type Error = $crate::Error; fn try_from(value: $crate::PitchTy) -> Result { - Self::try_from_value(value) + match $crate::traits::PitchMod::pitchmod(&value) { + $(x if x == $value => Ok(Self::$class),)* + _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) + } } } }; diff --git a/core/src/pitch/kinds.rs b/core/src/pitch/kinds.rs index f98509d..a2b872e 100644 --- a/core/src/pitch/kinds.rs +++ b/core/src/pitch/kinds.rs @@ -3,9 +3,10 @@ Contrib: FL03 */ use super::{PitchClass, PitchTy}; -use crate::Octave; -pitch! { +pitch_class! { + #[default(C)] + #[rename("lowercase")] pub enum Natural { C = 0, D = 2, @@ -17,7 +18,8 @@ pitch! { } } -pitch! { +pitch_class! { + #[default(C)] pub enum Sharp { C = 1, D = 3, @@ -27,7 +29,8 @@ pitch! { } } -pitch! { +pitch_class! { + #[default(D)] pub enum Flat { D = 1, E = 3, @@ -37,42 +40,7 @@ pitch! { } } -pub struct A { - pub(crate) octave: Octave, - pub(crate) pitch: PitchTy, -} - -pub trait SharpPitch { - private!(); -} - -pub trait FlatPitch { - private!(); -} - -impl FlatPitch for A { - seal!(); -} - -impl FlatPitch for Flat { - seal!(); -} - -impl SharpPitch for Sharp { - seal!(); -} - -pub trait AccidentalPitch: PitchClass { - private!(); -} -impl AccidentalPitch for Sharp { - seal!(); -} - -impl AccidentalPitch for Flat { - seal!(); -} #[derive( Clone, @@ -140,28 +108,7 @@ impl From for PitchTy { impl From for Pitches { fn from(value: PitchTy) -> Pitches { - Self::try_from_value(value).unwrap() + Self::try_from_value(value).unwrap_or_default() } } -mod impl_kinds { - use super::*; - - impl Default for Natural { - fn default() -> Self { - Natural::C - } - } - - impl Default for Sharp { - fn default() -> Self { - Sharp::C - } - } - - impl Default for Flat { - fn default() -> Self { - Flat::D - } - } -} diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index bed255f..27e1da3 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -3,11 +3,17 @@ Contrib: FL03 */ #[doc(inline)] -pub use self::{kinds::*, pitch::Pitch, utils::*}; +pub use self::{kinds::*, pitch::Pitch}; pub(crate) mod kinds; pub(crate) mod pitch; +pub(crate) mod prelude { + pub use super::kinds::*; + pub use super::{PitchClass, PitchTy}; +} + + /// A type alias for an integer representing a particular pitch of a note pub type PitchTy = i8; @@ -24,6 +30,35 @@ pub trait PitchT: Copy + Sized + core::fmt::Display { fn value(&self) -> PitchTy; } + +pub trait SharpPitch { + private!(); +} + +pub trait FlatPitch { + private!(); +} + +pub trait AccidentalPitch: PitchClass { + private!(); +} + + +impl FlatPitch for Flat { + seal!(); +} + +impl SharpPitch for Sharp { + seal!(); +} +impl AccidentalPitch for Sharp { + seal!(); +} + +impl AccidentalPitch for Flat { + seal!(); +} + mod impl_pitches { use super::*; @@ -57,15 +92,3 @@ mod impl_pitches { } } } -pub(crate) mod prelude { - pub use super::kinds::*; - pub use super::{PitchClass, PitchTy}; -} - -pub(crate) mod utils { - - /// [absmod] is short for the absolute value of a modular number; - pub fn absmod(a: i64, m: i64) -> i64 { - (((a % m) + m) % m).abs() - } -} From d10ab00ee15abf3bf53c3558fad4aef9af16e85c Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 14:43:59 -0500 Subject: [PATCH 09/21] update --- core/src/pitch/mod.rs | 2 +- core/src/pitch/pitch.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index 27e1da3..bda041f 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -64,7 +64,7 @@ mod impl_pitches { impl PitchT for Pitch { fn class(&self) -> Pitches { - self.as_class() + self.class() } fn value(&self) -> PitchTy { diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index c163571..7612e73 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -13,7 +13,7 @@ impl Pitch { Self(pitch) } /// Returns a new instance of the class representing the given pitch. - pub fn as_class(&self) -> Pitches { + pub fn class(&self) -> Pitches { Pitches::try_from_value(self.0).unwrap() } /// Consumes the pitch; returns a new instance of the class representing the given pitch. From d1e4a058f96286176cad035ef066679d0fabf3be Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 16:14:17 -0500 Subject: [PATCH 10/21] update --- core/src/lib.rs | 14 +++++++-- core/src/macros/pitches.rs | 6 ++-- core/src/notes/note.rs | 35 ++++++++++++++++++---- core/src/{traits => ops}/absmod.rs | 0 core/src/{traits => ops}/distance.rs | 0 core/src/ops/mod.rs | 14 +++++++++ core/src/pitch/kinds.rs | 3 -- core/src/pitch/mod.rs | 37 +++++++----------------- core/src/pitch/pitch.rs | 32 +++++++++++++++++++++ core/src/traits/mod.rs | 21 ++------------ core/src/traits/notable.rs | 14 +++++++++ core/src/types/intervals.rs | 13 ++++++++- core/src/utils.rs | 5 ++++ core/tests/ops.rs | 15 ++++++++++ neo/src/lib.rs | 19 +++++++++++- neo/src/macros.rs | 7 +++++ neo/src/macros/seal.rs | 29 +++++++++++++++++++ neo/src/space/mod.rs | 24 ++++++++++++++++ neo/src/{triad => space}/state.rs | 0 neo/src/space/venv.rs | 37 ++++++++++++++++++++++++ neo/src/tonnetz/mod.rs | 10 +++++++ neo/src/transform/lpr.rs | 10 +++++++ neo/src/transform/mod.rs | 12 ++++++++ neo/src/triad/classes.rs | 2 ++ neo/src/triad/mod.rs | 32 ++++++++++++++------- neo/src/triad/triad.rs | 43 +++------------------------- neo/src/{triad => types}/factors.rs | 0 neo/src/types/mod.rs | 12 ++++++++ neo/src/utils.rs | 13 +++++++++ 29 files changed, 348 insertions(+), 111 deletions(-) rename core/src/{traits => ops}/absmod.rs (100%) rename core/src/{traits => ops}/distance.rs (100%) create mode 100644 core/src/ops/mod.rs create mode 100644 core/src/traits/notable.rs create mode 100644 core/src/utils.rs create mode 100644 core/tests/ops.rs create mode 100644 neo/src/macros.rs create mode 100644 neo/src/macros/seal.rs create mode 100644 neo/src/space/mod.rs rename neo/src/{triad => space}/state.rs (100%) create mode 100644 neo/src/space/venv.rs create mode 100644 neo/src/tonnetz/mod.rs create mode 100644 neo/src/transform/lpr.rs create mode 100644 neo/src/transform/mod.rs rename neo/src/{triad => types}/factors.rs (100%) create mode 100644 neo/src/types/mod.rs create mode 100644 neo/src/utils.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index e84d496..02cfa21 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -10,9 +10,15 @@ #[cfg(feature = "alloc")] extern crate alloc; -pub use self::errors::{Error, Result}; -pub use self::pitch::{Pitch, PitchClass, PitchTy}; -pub use self::{primitives::*, types::prelude::*}; +#[doc(inline)] +pub use self::{ + errors::{Error, Result}, + notes::Note, + pitch::{Pitch, PitchClass, PitchTy}, + primitives::*, +}; +#[doc(inline)] +pub use self::{ops::prelude::*, traits::prelude::*, types::prelude::*}; #[macro_use] pub(crate) mod macros; @@ -20,6 +26,7 @@ pub(crate) mod primitives; pub mod errors; pub mod notes; +pub mod ops; pub mod pitch; pub mod traits; pub mod types; @@ -27,6 +34,7 @@ pub mod types; pub mod prelude { pub use super::errors::prelude::*; pub use super::notes::prelude::*; + pub use super::ops::prelude::*; pub use super::pitch::prelude::*; pub use super::primitives::prelude::*; pub use super::traits::prelude::*; diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index cd15faa..a1746d9 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -11,7 +11,7 @@ macro_rules! pitch_class { pitch_class!($(#[derive($($derive),*)])? $(#[default($default)])? #[rename("UPPERCASE")] $vis enum $name $($rest)*); }; (@impl $(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? #[rename($rename:literal)] $vis:vis enum $name:ident $($rest:tt)*) => { - + #[derive( Clone, Copy, @@ -52,7 +52,7 @@ macro_rules! impl_pitch { $group::try_from(value).ok() } pub fn try_from_value(value: $crate::PitchTy) -> $crate::Result { - match $crate::traits::PitchMod::pitchmod(&value) { + match $crate::PitchMod::pitchmod(&value) { $(x if x == $value => Ok(Self::$class),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } @@ -83,7 +83,7 @@ macro_rules! impl_pitch { type Error = $crate::Error; fn try_from(value: $crate::PitchTy) -> Result { - match $crate::traits::PitchMod::pitchmod(&value) { + match $crate::PitchMod::pitchmod(&value) { $(x if x == $value => Ok(Self::$class),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index 223f803..0b64600 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -2,19 +2,18 @@ Appellation: note Contrib: FL03 */ -use crate::pitch::{PitchT, PitchTy}; -use crate::Octave; +use crate::{Notable, Octave, Pitch}; #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Note

{ +pub struct Note

{ pub(crate) octave: Octave, pub(crate) pitch: P, } impl

Note

where - P: PitchT, + P: Notable, { pub fn new(octave: Octave, pitch: P) -> Self { Self { octave, pitch } @@ -38,7 +37,7 @@ where pub fn with_pitch(self, pitch: P2) -> Note where - P2: PitchT, + P2: Notable, { Note { octave: self.octave, @@ -49,9 +48,33 @@ where impl

core::fmt::Display for Note

where - P: PitchT, + P: Notable, { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}.{}", self.pitch, self.octave) } } + +macro_rules! impl_ops { + (@impl $name:ident$(<$($gen:ident),+>)?, $trait:ident, $fn:ident, $op:tt) => { + impl

core::ops::$trait for Note

+ where + P: $crate::pitch::Notable, + { + type Output = Note

; + fn $fn(self, rhs: Self) -> Self::Output { + Note { + octave: self.octave $op rhs.octave, + pitch: self.pitch $op rhs.pitch, + } + } + } + }; + (@impl $name:ident$(<$($gen:ident),+>)?, $trait:ident, $fn:ident, $op:tt, $($rest:tt)*) => { + impl_ops!(@impl $name$(<$($gen),+>)?, $trait, $fn, $op); + impl_ops!(@impl $name$(<$($gen),+>)?, $($rest)*); + }; + ($($rest:tt)*) => { + impl_ops!(@impl Note, $($rest)*); + }; +} diff --git a/core/src/traits/absmod.rs b/core/src/ops/absmod.rs similarity index 100% rename from core/src/traits/absmod.rs rename to core/src/ops/absmod.rs diff --git a/core/src/traits/distance.rs b/core/src/ops/distance.rs similarity index 100% rename from core/src/traits/distance.rs rename to core/src/ops/distance.rs diff --git a/core/src/ops/mod.rs b/core/src/ops/mod.rs new file mode 100644 index 0000000..1c59191 --- /dev/null +++ b/core/src/ops/mod.rs @@ -0,0 +1,14 @@ +/* + Appellation: ops + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::prelude::*; + +pub mod absmod; +pub mod distance; + +pub(crate) mod prelude { + pub use super::absmod::*; + pub use super::distance::*; +} diff --git a/core/src/pitch/kinds.rs b/core/src/pitch/kinds.rs index a2b872e..cbd9555 100644 --- a/core/src/pitch/kinds.rs +++ b/core/src/pitch/kinds.rs @@ -40,8 +40,6 @@ pitch_class! { } } - - #[derive( Clone, Copy, @@ -111,4 +109,3 @@ impl From for Pitches { Self::try_from_value(value).unwrap_or_default() } } - diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index bda041f..a2929b1 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -13,7 +13,6 @@ pub(crate) mod prelude { pub use super::{PitchClass, PitchTy}; } - /// A type alias for an integer representing a particular pitch of a note pub type PitchTy = i8; @@ -21,16 +20,6 @@ pub trait PitchClass { fn pitch(&self) -> PitchTy; } -pub trait PitchT: Copy + Sized + core::fmt::Display { - /// Classify the pitch into a pitch class - fn class(&self) -> Pitches { - self.value().into() - } - /// Find the modular index of the given pitch - fn value(&self) -> PitchTy; -} - - pub trait SharpPitch { private!(); } @@ -43,7 +32,6 @@ pub trait AccidentalPitch: PitchClass { private!(); } - impl FlatPitch for Flat { seal!(); } @@ -61,34 +49,31 @@ impl AccidentalPitch for Flat { mod impl_pitches { use super::*; + use crate::Notable; - impl PitchT for Pitch { + impl Notable for Pitch { fn class(&self) -> Pitches { self.class() } - fn value(&self) -> PitchTy { - self.value() + fn pitch(&self) -> Pitch { + *self } } - impl PitchT for Pitches { + impl Notable for Pitches { fn class(&self) -> Pitches { - self.clone() + *self } - fn value(&self) -> PitchTy { - self.class().into() + fn pitch(&self) -> Pitch { + Pitch(self.class().pitch()) } } - impl PitchT for PitchTy { - fn class(&self) -> Pitches { - Pitches::try_from_value(*self).unwrap() - } - - fn value(&self) -> PitchTy { - *self + impl Notable for PitchTy { + fn pitch(&self) -> Pitch { + Pitch(*self) } } } diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 7612e73..6f5d923 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -67,3 +67,35 @@ impl core::fmt::Display for Pitch { write!(f, "{}", self.0) } } + +macro_rules! impl_ops { + (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?$(=> $out:ty)? $(where $($rest:tt)*)?) => { + impl_ops!(@impl $name impls $trait.$call($name)$(<$T>)? $(=> $out)? $(where $($rest)*)?); + }; + (@impl $name:ident impls $trait:ident.$call:ident($rhs:ident)$(<$T:ident>)? $(where $($rest:tt)*)?) => { + impl_ops!(@impl $name impls $trait.$call($name)$(<$T>)? => Self $(where $($rest)*)?); + }; + (@impl $name:ident impls $trait:ident.$call:ident($rhs:ident)$(<$T:ident>)? => $out:ty $(where $($rest:tt)*)?) => { + impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($rest)*)? { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + ::core::ops::$trait::$call(self.0, rhs.0) + } + } + }; + ($name:ident impls $($rest:tt)*) => { + impl_ops!(@impl $name impls $($rest)*); + }; + (numops: $($name:ident$(<$T:ident>)? $(=> $out:ty)?),* $(,)?) => { + $( + impl_ops!(@impl $name impls Add.add$(<$T>)? $(=> $out)?); + impl_ops!(@impl $name impls Div.div$(<$T>)? $(=> $out)?); + impl_ops!(@impl $name impls Mul.mul$(<$T>)? $(=> $out)?); + impl_ops!(@impl $name impls Rem.rem$(<$T>)? $(=> $out)?); + impl_ops!(@impl $name impls Sub.sub$(<$T>)? $(=> $out)?); + )* + }; +} + +impl_ops!(numops: Pitch => i8); diff --git a/core/src/traits/mod.rs b/core/src/traits/mod.rs index 89e659b..4f87ab3 100644 --- a/core/src/traits/mod.rs +++ b/core/src/traits/mod.rs @@ -3,25 +3,10 @@ Contrib: FL03 */ #[doc(inline)] -pub use self::prelude::*; +pub use self::notable::Notable; -pub mod absmod; -pub mod distance; +pub mod notable; pub(crate) mod prelude { - pub use super::absmod::*; - pub use super::distance::*; -} - -#[cfg(test)] -mod tests { - - #[test] - fn test_absmod() { - use super::absmod::{AbsMod, PitchMod}; - let pairs = [(5, 12), (-5, 12), (0, 12), (12, 12), (13, 12), (-13, 12)]; - for (i, j) in pairs.iter() { - assert_eq!(i.absmod(*j), i.pitchmod()); - } - } + pub use super::notable::*; } diff --git a/core/src/traits/notable.rs b/core/src/traits/notable.rs new file mode 100644 index 0000000..307ba93 --- /dev/null +++ b/core/src/traits/notable.rs @@ -0,0 +1,14 @@ +/* + Appellation: notable + Contrib: FL03 +*/ +use crate::pitch::{Pitch, Pitches}; + +pub trait Notable: Copy + Sized + core::fmt::Display { + /// Classify the pitch into a pitch class + fn class(&self) -> Pitches { + self.pitch().class() + } + /// Find the modular index of the given pitch + fn pitch(&self) -> Pitch; +} diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs index a4bc889..dd6e230 100644 --- a/core/src/types/intervals.rs +++ b/core/src/types/intervals.rs @@ -2,12 +2,23 @@ Appellation: intervals Contrib: FL03 */ -use crate::PitchTy; +use crate::{notes::Note, PitchTy}; use num::traits::NumOps; pub trait IntervalOps: NumOps {} +pub struct Interval { + pub lhs: A, + pub rhs: B, +} + +impl Interval { + pub fn new(lhs: A, rhs: B) -> Self { + Self { lhs, rhs } + } +} + unit_enum! { rename: "lowercase"; #[derive(Default)] diff --git a/core/src/utils.rs b/core/src/utils.rs new file mode 100644 index 0000000..602b6be --- /dev/null +++ b/core/src/utils.rs @@ -0,0 +1,5 @@ +/* + Appellation: utils + Contrib: FL03 +*/ + diff --git a/core/tests/ops.rs b/core/tests/ops.rs new file mode 100644 index 0000000..db15c2d --- /dev/null +++ b/core/tests/ops.rs @@ -0,0 +1,15 @@ +/* + Appellation: ops + Contrib: FL03 +*/ +extern crate rstmt_core as rstmt; + +use rstmt::ops::{AbsMod, PitchMod}; + +#[test] +fn test_absmod() { + let pairs = [(5, 12), (-5, 12), (0, 12), (12, 12), (13, 12), (-13, 12)]; + for (i, j) in pairs.iter() { + assert_eq!(i.absmod(*j), i.pitchmod()); + } +} diff --git a/neo/src/lib.rs b/neo/src/lib.rs index aab5fd3..2128ccd 100644 --- a/neo/src/lib.rs +++ b/neo/src/lib.rs @@ -7,10 +7,27 @@ //! This project focuses on providing concrete abstractions of musical objects discussed within the neo-Riemannian theory. extern crate rstmt_core as rstmt; -pub use self::triad::Triad; +#[doc(inline)] +pub use self::{ + triad::{Triad, TriadBuilder}, + types::*, + utils::*, +}; +#[macro_use] +pub(crate) mod macros; +pub(crate) mod utils; + +pub mod space; +pub mod tonnetz; +pub mod transform; pub mod triad; +pub mod types; pub mod prelude { + pub use crate::space::prelude::*; + pub use crate::tonnetz::prelude::*; + pub use crate::transform::prelude::*; pub use crate::triad::prelude::*; + pub use crate::types::prelude::*; } diff --git a/neo/src/macros.rs b/neo/src/macros.rs new file mode 100644 index 0000000..5ce5346 --- /dev/null +++ b/neo/src/macros.rs @@ -0,0 +1,7 @@ +/* + Appellation: macros + Contrib: FL03 +*/ + +#[macro_use] +pub(crate) mod seal; diff --git a/neo/src/macros/seal.rs b/neo/src/macros/seal.rs new file mode 100644 index 0000000..e74f2bb --- /dev/null +++ b/neo/src/macros/seal.rs @@ -0,0 +1,29 @@ +/* + Appellation: seal + Contrib: FL03 +*/ +//! The public parts of this private module are used to create traits +//! that cannot be implemented outside of our own crate. This way we +//! can feel free to extend those traits without worrying about it +//! being a breaking change for other implementations. + +/// If this type is pub but not publicly reachable, third parties +/// can't name it and can't implement traits using it. +pub struct Seal; + +macro_rules! private { + () => { + /// This trait is private to implement; this method exists to make it + /// impossible to implement outside the crate. + #[doc(hidden)] + fn __private__(&self) -> $crate::macros::seal::Seal; + }; +} + +macro_rules! seal { + () => { + fn __private__(&self) -> $crate::macros::seal::Seal { + $crate::macros::seal::Seal + } + }; +} diff --git a/neo/src/space/mod.rs b/neo/src/space/mod.rs new file mode 100644 index 0000000..48e7325 --- /dev/null +++ b/neo/src/space/mod.rs @@ -0,0 +1,24 @@ +/* + Appellation: space + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::{ + state::{BinaryState, BinaryStates, TriadState}, + venv::TopoVenv, +}; + +pub mod state; +pub mod venv; + +/// +pub type TriadGraph = petgraph::Graph; +/// +pub type TriadId = String; + +pub(crate) mod prelude { + pub use super::state::{BinaryState, BinaryStates, TriadState}; + pub use super::venv::TopoVenv; + pub use super::TriadGraph; + pub use super::TriadId; +} diff --git a/neo/src/triad/state.rs b/neo/src/space/state.rs similarity index 100% rename from neo/src/triad/state.rs rename to neo/src/space/state.rs diff --git a/neo/src/space/venv.rs b/neo/src/space/venv.rs new file mode 100644 index 0000000..eef8825 --- /dev/null +++ b/neo/src/space/venv.rs @@ -0,0 +1,37 @@ +/* + Appellation: store + Contrib: FL03 +*/ +use super::state::{BinaryState, TriadState}; +use super::{TriadGraph, TriadId}; + +#[derive(Clone)] +pub struct TopoVenv { + pub(crate) id: TriadId, + pub(crate) chord: TriadGraph, + pub(crate) state: BinaryState, +} + +impl TopoVenv { + pub fn new(id: TriadId) -> Self { + let chord = TriadGraph::new(); + let state = BinaryState::default(); + Self { id, chord, state } + } + + pub fn id(&self) -> &TriadId { + &self.id + } + + pub fn is_valid(&self) -> bool { + self.state().is_valid() + } + + pub fn chord(&self) -> &TriadGraph { + &self.chord + } + + pub const fn state(&self) -> &BinaryState { + &self.state + } +} diff --git a/neo/src/tonnetz/mod.rs b/neo/src/tonnetz/mod.rs new file mode 100644 index 0000000..18ba0d8 --- /dev/null +++ b/neo/src/tonnetz/mod.rs @@ -0,0 +1,10 @@ +/* + Appellation: tonnetz + Contrib: FL03 +*/ + +pub(crate) mod prelude { + pub use super::Tonnetz; +} + +pub trait Tonnetz {} diff --git a/neo/src/transform/lpr.rs b/neo/src/transform/lpr.rs new file mode 100644 index 0000000..f1dfa01 --- /dev/null +++ b/neo/src/transform/lpr.rs @@ -0,0 +1,10 @@ +/* + Appellation: lpr + Contrib: FL03 +*/ + +pub enum LPR { + L, + P, + R, +} diff --git a/neo/src/transform/mod.rs b/neo/src/transform/mod.rs new file mode 100644 index 0000000..9f28a60 --- /dev/null +++ b/neo/src/transform/mod.rs @@ -0,0 +1,12 @@ +/* + Appellation: transform + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::lpr::LPR; + +pub(crate) mod lpr; + +pub(crate) mod prelude { + pub use super::lpr::LPR; +} diff --git a/neo/src/triad/classes.rs b/neo/src/triad/classes.rs index 99b7410..53bb1ac 100644 --- a/neo/src/triad/classes.rs +++ b/neo/src/triad/classes.rs @@ -3,6 +3,8 @@ Contrib: FL03 */ +pub trait TriadTy {} + #[derive( Clone, Copy, diff --git a/neo/src/triad/mod.rs b/neo/src/triad/mod.rs index 0e8ee1d..26fc999 100644 --- a/neo/src/triad/mod.rs +++ b/neo/src/triad/mod.rs @@ -3,25 +3,35 @@ Contrib: FL03 */ #[doc(inline)] -pub use self::{builder::TriadBuilder, classes::Triads, factors::ChordFactor, state::*, triad::Triad}; +pub use self::{builder::TriadBuilder, classes::Triads, triad::Triad}; pub(crate) mod builder; pub(crate) mod triad; pub mod classes; -pub mod factors; -pub mod state; - -pub type TriadGraph = petgraph::Graph; - -pub type TriadId = String; pub(crate) mod prelude { pub use super::builder::TriadBuilder; pub use super::classes::*; - pub use super::factors::ChordFactor; - pub use super::state::{BinaryState, BinaryStates, TriadState}; pub use super::triad::Triad; - pub use super::{TriadGraph, TriadId}; +} + +use rstmt::Intervals; + +pub trait Triadic { + fn intervals(&self) -> impl Iterator; + + fn kind(&self) -> Triads; + + fn notes(&self) -> (N, N, N); +} + +pub trait TriadData { + type Elem; + + fn root(&self) -> &Self::Elem; + + fn third(&self) -> &Self::Elem; -} \ No newline at end of file + fn fifth(&self) -> &Self::Elem; +} diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index 0501bc3..348381d 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -2,45 +2,10 @@ Appellation: triad Contrib: FL03 */ -use super::{TriadId, TriadGraph,}; -use super::state::{BinaryState, TriadState}; - -#[derive(Clone)] -pub struct Triad { - pub(crate) id: TriadId, - pub(crate) chord: TriadGraph, - pub(crate) state: BinaryState, - -} - -impl Triad { - pub fn new(id: TriadId) -> Self { - let chord = TriadGraph::new(); - let state = BinaryState::default(); - Self { - id, - chord, - state, - - } - } - - pub fn id(&self) -> &TriadId { - &self.id - } - - pub fn is_valid(&self) -> bool { - self.state().is_valid() - } - - pub fn chord(&self) -> &TriadGraph { - &self.chord - } - - pub const fn state(&self) -> &BinaryState { - &self.state - } - +pub type Tuple3 = (A, B, C); +pub struct Triad { + pub kind: super::Triads, + pub notes: [u8; 3], } diff --git a/neo/src/triad/factors.rs b/neo/src/types/factors.rs similarity index 100% rename from neo/src/triad/factors.rs rename to neo/src/types/factors.rs diff --git a/neo/src/types/mod.rs b/neo/src/types/mod.rs new file mode 100644 index 0000000..05ee1cf --- /dev/null +++ b/neo/src/types/mod.rs @@ -0,0 +1,12 @@ +/* + Appellation: types + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::factors::ChordFactor; + +pub mod factors; + +pub(crate) mod prelude { + pub use super::factors::ChordFactor; +} diff --git a/neo/src/utils.rs b/neo/src/utils.rs new file mode 100644 index 0000000..de49bb0 --- /dev/null +++ b/neo/src/utils.rs @@ -0,0 +1,13 @@ +/* + Appellation: utils + Contrib: FL03 +*/ +use num::traits::NumOps; +use rstmt::Intervals; + +pub fn interval(lhs: A, rhs: B) -> Intervals +where + A: NumOps, +{ + Intervals::from_value(lhs - rhs) +} From 2c589e86a1f4f313ef20076faa270427675ee53c Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Sat, 13 Jul 2024 16:47:53 -0500 Subject: [PATCH 11/21] update --- core/src/pitch/pitch.rs | 52 ++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 6f5d923..24cd11d 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -69,33 +69,57 @@ impl core::fmt::Display for Pitch { } macro_rules! impl_ops { - (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?$(=> $out:ty)? $(where $($rest:tt)*)?) => { - impl_ops!(@impl $name impls $trait.$call($name)$(<$T>)? $(=> $out)? $(where $($rest)*)?); - }; - (@impl $name:ident impls $trait:ident.$call:ident($rhs:ident)$(<$T:ident>)? $(where $($rest:tt)*)?) => { - impl_ops!(@impl $name impls $trait.$call($name)$(<$T>)? => Self $(where $($rest)*)?); + + (@base $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?($rhs:ident) -> $out:ty $(where $($w:tt)*)? {$($rest:tt)*}) => { + impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($w)*)? { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + $($rest)* + } + } }; - (@impl $name:ident impls $trait:ident.$call:ident($rhs:ident)$(<$T:ident>)? => $out:ty $(where $($rest:tt)*)?) => { + + (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?($rhs:ident) -> $out:ty $(where $($rest:tt)*)?) => { impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($rest)*)? { type Output = $out; fn $call(self, rhs: $rhs) -> Self::Output { - ::core::ops::$trait::$call(self.0, rhs.0) + ::core::ops::$trait::$call(self.0, rhs.0).into() } } }; + (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)? -> $out:ty $(where $($rest:tt)*)?) => { + impl_ops!(@impl $name impls $trait.$call$(<$T>)?($name) -> $out $(where $($rest)*)?); + }; + (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?$(($rhs:ident))? $(where $($rest:tt)*)?) => { + impl_ops!(@impl $name impls $trait.$call$(<$T>)?$(($rhs))? -> Self $(where $($rest)*)?); + }; ($name:ident impls $($rest:tt)*) => { impl_ops!(@impl $name impls $($rest)*); }; - (numops: $($name:ident$(<$T:ident>)? $(=> $out:ty)?),* $(,)?) => { + +} + +macro_rules! impl_pitch_ops { + ($name:ident -> $($out:ty),* $(,)?) => { + $( + impl_ops!($name impls Add.add -> $out); + impl_ops!($name impls Div.div -> $out); + impl_ops!($name impls Mul.mul -> $out); + impl_ops!($name impls Rem.rem -> $out); + impl_ops!($name impls Sub.sub -> $out); + )* + }; + ($name:ident => -> $($out:ty $(where $(rest:tt)*)?),* $(,)?) => { $( - impl_ops!(@impl $name impls Add.add$(<$T>)? $(=> $out)?); - impl_ops!(@impl $name impls Div.div$(<$T>)? $(=> $out)?); - impl_ops!(@impl $name impls Mul.mul$(<$T>)? $(=> $out)?); - impl_ops!(@impl $name impls Rem.rem$(<$T>)? $(=> $out)?); - impl_ops!(@impl $name impls Sub.sub$(<$T>)? $(=> $out)?); + impl_ops!($name impls Add.add -> $out $(where $(rest)*)?); + impl_ops!($name impls Div.div -> $out $(where $(rest)*)?); + impl_ops!($name impls Mul.mul -> $out $(where $(rest)*)?); + impl_ops!($name impls Rem.rem -> $out $(where $(rest)*)?); + impl_ops!($name impls Sub.sub -> $out $(where $(rest)*)?); )* }; } -impl_ops!(numops: Pitch => i8); +impl_pitch_ops!(Pitch -> i8); From 23b99e2e51686c7a6080419707e0afcbed97dd80 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 01:02:40 -0500 Subject: [PATCH 12/21] update --- core/src/macros/pitches.rs | 20 ++++++-- core/src/notes/note.rs | 99 ++++++++++++++++++++++++++++++------- core/src/ops/absmod.rs | 18 +++++-- core/src/pitch/mod.rs | 39 +++------------ core/src/pitch/pitch.rs | 8 +-- core/src/traits/notable.rs | 31 +++++++++++- core/src/types/intervals.rs | 27 +++++----- core/src/types/mod.rs | 4 +- neo/src/triad/triad.rs | 2 - 9 files changed, 170 insertions(+), 78 deletions(-) diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index a1746d9..cf33b56 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -79,15 +79,29 @@ macro_rules! impl_pitch { } } - impl TryFrom<$crate::PitchTy> for $group { + impl From<$group> for $crate::pitch::Pitch { + fn from(pitch: $group) -> $crate::pitch::Pitch { + $crate::pitch::Pitch::new(pitch as $crate::PitchTy) + } + } + + impl TryFrom<$crate::pitch::PitchTy> for $group { type Error = $crate::Error; - fn try_from(value: $crate::PitchTy) -> Result { - match $crate::PitchMod::pitchmod(&value) { + fn try_from(p: $crate::pitch::PitchTy) -> Result { + match $crate::PitchMod::pitchmod(&p) { $(x if x == $value => Ok(Self::$class),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } } } + + impl TryFrom<$crate::pitch::Pitch> for $group { + type Error = $crate::Error; + + fn try_from(p: $crate::pitch::Pitch) -> Result { + Self::try_from(p.value()) + } + } }; } diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index 0b64600..8f78abf 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -22,19 +22,31 @@ where pub const fn octave(&self) -> &Octave { &self.octave } - + /// Returns a mutable reference to the note's octave + pub fn octave_mut(&mut self) -> &mut Octave { + &mut self.octave + } + /// Returns an owned instance of the note's pitch pub const fn pitch(&self) -> &P { &self.pitch } - + /// Returns a mutable reference to the note's pitch + pub fn pitch_mut(&mut self) -> &mut P { + &mut self.pitch + } + /// Sets the note's octave pub fn set_octave(&mut self, octave: Octave) { self.octave = octave; } - + /// Sets the note's pitch + pub fn set_pitch(&mut self, pitch: P) { + self.pitch = pitch; + } + /// Returns a new instance of the note with the given octave pub fn with_octave(self, octave: Octave) -> Self { Self { octave, ..self } } - + /// Returns a new instance of the note with the given pitch pub fn with_pitch(self, pitch: P2) -> Note where P2: Notable, @@ -55,26 +67,79 @@ where } } -macro_rules! impl_ops { - (@impl $name:ident$(<$($gen:ident),+>)?, $trait:ident, $fn:ident, $op:tt) => { - impl

core::ops::$trait for Note

+unsafe impl

Send for Note

where P: Send {} + +unsafe impl

Sync for Note

where P: Sync {} + +macro_rules! impl_std_ops { + (@impl $trait:ident.$call:ident($rhs:ty) -> $out:ty) => { + impl

core::ops::$trait<$rhs> for Note

+ where + P: $crate::Notable + core::ops::$trait, + { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + Note { + octave: ::core::ops::$trait::$call(self.octave, rhs.octave), + pitch: ::core::ops::$trait::$call(self.pitch, rhs.pitch) + } + } + } + + impl<'a, P> core::ops::$trait<&'a $rhs> for Note

+ where + P: $crate::Notable + core::ops::$trait, + { + type Output = $out; + + fn $call(self, rhs: &'a $rhs) -> Self::Output { + Note { + octave: ::core::ops::$trait::$call(self.octave, rhs.octave), + pitch: ::core::ops::$trait::$call(self.pitch, rhs.pitch) + } + } + } + + impl<'a, P> core::ops::$trait<$rhs> for &'a Note

+ where + P: $crate::Notable + core::ops::$trait, + { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + Note { + octave: ::core::ops::$trait::$call(self.octave, rhs.octave), + pitch: ::core::ops::$trait::$call(self.pitch, rhs.pitch) + } + } + } + + impl<'a, P> core::ops::$trait<&'a $rhs> for &'a Note

where - P: $crate::pitch::Notable, + P: $crate::Notable + core::ops::$trait, { - type Output = Note

; - fn $fn(self, rhs: Self) -> Self::Output { + type Output = $out; + + fn $call(self, rhs: &'a $rhs) -> Self::Output { Note { - octave: self.octave $op rhs.octave, - pitch: self.pitch $op rhs.pitch, + octave: ::core::ops::$trait::$call(self.octave, rhs.octave), + pitch: ::core::ops::$trait::$call(self.pitch, rhs.pitch) } } } }; - (@impl $name:ident$(<$($gen:ident),+>)?, $trait:ident, $fn:ident, $op:tt, $($rest:tt)*) => { - impl_ops!(@impl $name$(<$($gen),+>)?, $trait, $fn, $op); - impl_ops!(@impl $name$(<$($gen),+>)?, $($rest)*); + (@impl $trait:ident.$call:ident($rhs:ty)) => { + impl_std_ops!(@impl $trait.$call($rhs) -> Note

); }; - ($($rest:tt)*) => { - impl_ops!(@impl Note, $($rest)*); + (@impl $trait:ident.$call:ident $(-> $out:ty)?) => { + impl_std_ops!(@impl $trait.$call(Note

) $(-> $out)?); + }; + ($($trait:ident.$call:ident$(($rhs:ty))? $(-> $out:ty)?),* $(,)?) => { + $( + impl_std_ops!(@impl $trait.$call$(($rhs))? $(-> $out)?); + )* }; } + +impl_std_ops!(Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); diff --git a/core/src/ops/absmod.rs b/core/src/ops/absmod.rs index 42d8e1a..23847ce 100644 --- a/core/src/ops/absmod.rs +++ b/core/src/ops/absmod.rs @@ -11,14 +11,24 @@ pub trait AbsMod { fn absmod(&self, rhs: Rhs) -> Self::Output; } -pub trait PitchMod: AbsMod { +pub trait PitchMod { + const MOD: PitchTy = crate::MODULUS; + type Output; + + fn pitchmod(&self) -> Self::Output; +} + +impl PitchMod for S +where + S: AbsMod, +{ + type Output = >::Output; + fn pitchmod(&self) -> Self::Output { - self.absmod(crate::MODULUS as PitchTy) + self.absmod(Self::MOD) } } -impl PitchMod for S where S: AbsMod {} - /* ************* Implementations ************* */ diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index a2929b1..b8b9f5a 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -9,8 +9,9 @@ pub(crate) mod kinds; pub(crate) mod pitch; pub(crate) mod prelude { - pub use super::kinds::*; - pub use super::{PitchClass, PitchTy}; + pub use super::kinds::{Flat, Natural, Pitches, Sharp}; + pub use super::pitch::Pitch; + pub use super::{AccidentalPitch, PitchClass, PitchTy}; } /// A type alias for an integer representing a particular pitch of a note @@ -32,6 +33,9 @@ pub trait AccidentalPitch: PitchClass { private!(); } +/* + ************* Implementations ************* +*/ impl FlatPitch for Flat { seal!(); } @@ -46,34 +50,3 @@ impl AccidentalPitch for Sharp { impl AccidentalPitch for Flat { seal!(); } - -mod impl_pitches { - use super::*; - use crate::Notable; - - impl Notable for Pitch { - fn class(&self) -> Pitches { - self.class() - } - - fn pitch(&self) -> Pitch { - *self - } - } - - impl Notable for Pitches { - fn class(&self) -> Pitches { - *self - } - - fn pitch(&self) -> Pitch { - Pitch(self.class().pitch()) - } - } - - impl Notable for PitchTy { - fn pitch(&self) -> Pitch { - Pitch(*self) - } - } -} diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 24cd11d..184bb9e 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -69,7 +69,7 @@ impl core::fmt::Display for Pitch { } macro_rules! impl_ops { - + (@base $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?($rhs:ident) -> $out:ty $(where $($w:tt)*)? {$($rest:tt)*}) => { impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($w)*)? { type Output = $out; @@ -79,7 +79,7 @@ macro_rules! impl_ops { } } }; - + (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?($rhs:ident) -> $out:ty $(where $($rest:tt)*)?) => { impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($rest)*)? { type Output = $out; @@ -98,7 +98,7 @@ macro_rules! impl_ops { ($name:ident impls $($rest:tt)*) => { impl_ops!(@impl $name impls $($rest)*); }; - + } macro_rules! impl_pitch_ops { @@ -111,7 +111,7 @@ macro_rules! impl_pitch_ops { impl_ops!($name impls Sub.sub -> $out); )* }; - ($name:ident => -> $($out:ty $(where $(rest:tt)*)?),* $(,)?) => { + ($name:ident -> $($out:ty $(where $(rest:tt)*)?),* $(,)?) => { $( impl_ops!($name impls Add.add -> $out $(where $(rest)*)?); impl_ops!($name impls Div.div -> $out $(where $(rest)*)?); diff --git a/core/src/traits/notable.rs b/core/src/traits/notable.rs index 307ba93..c083a5f 100644 --- a/core/src/traits/notable.rs +++ b/core/src/traits/notable.rs @@ -2,7 +2,7 @@ Appellation: notable Contrib: FL03 */ -use crate::pitch::{Pitch, Pitches}; +use crate::pitch::{Pitch, PitchTy, Pitches}; pub trait Notable: Copy + Sized + core::fmt::Display { /// Classify the pitch into a pitch class @@ -12,3 +12,32 @@ pub trait Notable: Copy + Sized + core::fmt::Display { /// Find the modular index of the given pitch fn pitch(&self) -> Pitch; } + +/* + ************* Implementations ************* +*/ +impl Notable for Pitch { + fn class(&self) -> Pitches { + self.class() + } + + fn pitch(&self) -> Pitch { + *self + } +} + +impl Notable for Pitches { + fn class(&self) -> Pitches { + *self + } + + fn pitch(&self) -> Pitch { + Pitch(self.class().pitch()) + } +} + +impl Notable for PitchTy { + fn pitch(&self) -> Pitch { + Pitch(*self) + } +} diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs index dd6e230..375b77e 100644 --- a/core/src/types/intervals.rs +++ b/core/src/types/intervals.rs @@ -2,11 +2,10 @@ Appellation: intervals Contrib: FL03 */ -use crate::{notes::Note, PitchTy}; - +use crate::{Notable, Pitch}; use num::traits::NumOps; -pub trait IntervalOps: NumOps {} +pub trait IntervalOps: NumOps {} pub struct Interval { pub lhs: A, @@ -34,16 +33,17 @@ unit_enum! { } impl Intervals { - pub fn from_value(value: PitchTy) -> Self { + pub fn from_value(value: impl Notable) -> Self { use Intervals::*; - match value { + let pitch = value.pitch(); + match *pitch { 1 => Semitone, 2 => Tone, - 3..=4 => Thirds(Third::from(value)), - 5 => Fourths(Fourth::from(value)), - 6..=8 => Fifths(Fifth::from(value)), - 9..=12 => Sevenths(Seventh::from(value)), - _ => panic!("Invalid interval value: {}", value), + 3..=4 => Thirds(Third::from(pitch.value())), + 5 => Fourths(Fourth::from(pitch.value())), + 6..=8 => Fifths(Fifth::from(pitch.value())), + 9..=12 => Sevenths(Seventh::from(pitch.value())), + _ => panic!("Invalid interval value: {}", pitch.value()), } } pub fn from_semitone() -> Self { @@ -74,15 +74,16 @@ impl Intervals { self.as_ref() } - pub fn value(&self) -> PitchTy { - match *self { + pub fn value(&self) -> Pitch { + let p = match *self { Intervals::Semitone => 1, Intervals::Tone => 2, Intervals::Thirds(third) => third as i8, Intervals::Fourths(fourth) => fourth as i8, Intervals::Fifths(fifth) => fifth as i8, Intervals::Sevenths(seventh) => seventh as i8, - } + }; + Pitch(p) } } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index b9b480e..1c6c979 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -9,8 +9,10 @@ pub mod intervals; /// A type alias for an `octave`; Musically speaking, an octave is the interval (distance) between one musical pitch and another /// with either half or double its frequency. pub type Octave = i8; +/// +pub type Tuple3 = (A, B, C); pub(crate) mod prelude { pub use super::intervals::*; - pub use super::Octave; + pub use super::{Octave, Tuple3}; } diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index 348381d..0116019 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -3,8 +3,6 @@ Contrib: FL03 */ -pub type Tuple3 = (A, B, C); - pub struct Triad { pub kind: super::Triads, pub notes: [u8; 3], From 1e353310ab75a67df9b085763870bc806c3fb32c Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 01:14:13 -0500 Subject: [PATCH 13/21] update --- core/src/pitch/pitch.rs | 35 +++++++++++++++++++++++++++++++++++ core/tests/pitches.rs | 10 ++++++++++ 2 files changed, 45 insertions(+) diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 184bb9e..da7e640 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -122,4 +122,39 @@ macro_rules! impl_pitch_ops { }; } +macro_rules! impl_std_ops { + (@impl $trait:ident.$call:ident) => { + impl

core::ops::$trait

for Pitch + where + PitchTy: core::ops::$trait, + { + type Output = Pitch; + + fn $call(self, rhs: P) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs); + Pitch(p) + } + } + + impl<'a, P> core::ops::$trait

for &'a Pitch + where + PitchTy: core::ops::$trait, + { + type Output = Pitch; + + fn $call(self, rhs: P) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs); + Pitch(p) + } + } + }; + ($($trait:ident.$call:ident),* $(,)?) => { + $( + impl_std_ops!(@impl $trait.$call); + )* + }; +} + +impl_std_ops!(Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); + impl_pitch_ops!(Pitch -> i8); diff --git a/core/tests/pitches.rs b/core/tests/pitches.rs index 4b25d68..b9e6e03 100644 --- a/core/tests/pitches.rs +++ b/core/tests/pitches.rs @@ -16,7 +16,17 @@ where #[test] fn test_pitch() { + let pitch = Pitch::new(12); + let b = pitch + 1; + + assert_ne!(pitch, b); + assert_eq!(b, Pitch::new(1)); +} + +#[test] +fn test_pitch_class() { let pitch = assert_ok(Pitches::try_from_value(12)); let rhs = Natural::C; assert_eq!(pitch, rhs.as_class()); + assert_eq!(pitch.pitch(), 0); } From 22c1734945a84b31820a067e71b37363b94511a1 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 03:05:23 -0500 Subject: [PATCH 14/21] update --- Cargo.toml | 2 + core/Cargo.toml | 2 + core/src/macros.rs | 2 + core/src/macros/ops.rs | 100 +++++++++++++++++++++++++++ core/src/pitch/pitch.rs | 119 ++++++++++----------------------- core/src/types/intervals.rs | 7 ++ core/src/types/mod.rs | 6 +- core/src/types/octave.rs | 98 +++++++++++++++++++++++++++ core/tests/pitches.rs | 3 +- neo/Cargo.toml | 1 + neo/src/triad/classes.rs | 130 +++++++++++++++++++++++++++++++++++- neo/src/triad/mod.rs | 31 +++++++-- neo/src/triad/triad.rs | 18 ++++- rstmt/Cargo.toml | 4 ++ rstmt/examples/misc.rs | 34 ++++++++++ 15 files changed, 460 insertions(+), 97 deletions(-) create mode 100644 core/src/macros/ops.rs create mode 100644 core/src/types/octave.rs create mode 100644 rstmt/examples/misc.rs diff --git a/Cargo.toml b/Cargo.toml index 6d7c821..fbb4a8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,9 @@ members = [ resolver = "2" [workspace.dependencies] +anyhow = "1" lazy_static = "1" +paste = "1" serde_json = "1" smart-default = "0.7" strum = { features = ["derive"], version = "0.26" } diff --git a/core/Cargo.toml b/core/Cargo.toml index e0ec847..9322bc1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -54,6 +54,8 @@ test = true [build-dependencies] [dependencies] +lazy_static.workspace = true +paste.workspace = true smart-default.workspace = true [dependencies.num] diff --git a/core/src/macros.rs b/core/src/macros.rs index d7f5d78..8f44f9e 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -8,6 +8,8 @@ pub(crate) mod errors; #[macro_use] pub(crate) mod intervals; #[macro_use] +pub(crate) mod ops; +#[macro_use] pub(crate) mod pitches; #[macro_use] pub(crate) mod seal; diff --git a/core/src/macros/ops.rs b/core/src/macros/ops.rs new file mode 100644 index 0000000..4d1f440 --- /dev/null +++ b/core/src/macros/ops.rs @@ -0,0 +1,100 @@ +/* + Appellation: ops + Contrib: FL03 +*/ + +macro_rules! wrapper_ops { + (@impl $name:ident::<$type:ty>::$trait:ident.$call:ident) => { + impl core::ops::$trait<$name> for $name { + type Output = $name; + + fn $call(self, rhs: $name) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs.0); + $name(p) + } + } + + impl<'a> core::ops::$trait<&'a $name> for $name { + type Output = $name; + + fn $call(self, rhs: &'a $name) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs.0); + $name(p) + } + } + + impl<'a> core::ops::$trait<$name> for &'a $name { + type Output = $name; + + fn $call(self, rhs: $name) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs.0); + $name(p) + } + } + + impl<'a> core::ops::$trait<&'a $name> for &'a $name { + type Output = $name; + + fn $call(self, rhs: &'a $name) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs.0); + $name(p) + } + } + + paste::paste! { + impl core::ops::$trait for $name + where + $type: core::ops::$trait, + { + type Output = $name; + + fn $call(self, rhs: T) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs); + $name(p) + } + } + + impl<'a, T> core::ops::$trait for &'a $name + where + $type: core::ops::$trait, + { + type Output = $name; + + fn $call(self, rhs: T) -> Self::Output { + let p = ::core::ops::$trait::$call(self.0, rhs); + $name(p) + } + } + } + }; + ($name:ident::<$type:ty>: $($trait:ident.$call:ident),* $(,)?) => { + $( + wrapper_ops!(@impl $name::<$type>::$trait.$call); + )* + }; +} + +macro_rules! wrapper_unop { + (@impl $name:ident impls $trait:ident.$call:ident) => { + impl ::core::ops::$trait for $name { + type Output = $name; + + fn $call(self) -> Self::Output { + $name::new(::core::ops::$trait::$call(self.0)) + } + } + + impl<'a> ::core::ops::$trait for &'a $name { + type Output = $name; + + fn $call(self) -> Self::Output { + $name::new(::core::ops::$trait::$call(self.0)) + } + } + }; + + ($name:ident impls $($trait:ident.$call:ident),* $(,)?) => { + $(wrapper_unop!(@impl $name impls $trait.$call);)* + }; + +} diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index da7e640..ed1abdd 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -3,14 +3,19 @@ Contrib: FL03 */ use super::{PitchTy, Pitches}; +use crate::PitchMod; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Pitch(pub PitchTy); +pub struct Pitch(pub(crate) PitchTy); impl Pitch { - pub fn new(pitch: PitchTy) -> Self { - Self(pitch) + pub fn new(pitch: impl Into) -> Self { + Self(pitch.into().pitchmod()) + } + /// Returns the absolute value of the remainder of the pitch divided by the modulus. + pub fn modulo(&self) -> Self { + self.pitchmod() } /// Returns a new instance of the class representing the given pitch. pub fn class(&self) -> Pitches { @@ -68,93 +73,37 @@ impl core::fmt::Display for Pitch { } } -macro_rules! impl_ops { - - (@base $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?($rhs:ident) -> $out:ty $(where $($w:tt)*)? {$($rest:tt)*}) => { - impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($w)*)? { - type Output = $out; - - fn $call(self, rhs: $rhs) -> Self::Output { - $($rest)* - } - } - }; - - (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?($rhs:ident) -> $out:ty $(where $($rest:tt)*)?) => { - impl$(<$T>)? core::ops::$trait<$rhs> for $name $(where $($rest)*)? { - type Output = $out; - - fn $call(self, rhs: $rhs) -> Self::Output { - ::core::ops::$trait::$call(self.0, rhs.0).into() - } - } - }; - (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)? -> $out:ty $(where $($rest:tt)*)?) => { - impl_ops!(@impl $name impls $trait.$call$(<$T>)?($name) -> $out $(where $($rest)*)?); - }; - (@impl $name:ident impls $trait:ident.$call:ident$(<$T:ident>)?$(($rhs:ident))? $(where $($rest:tt)*)?) => { - impl_ops!(@impl $name impls $trait.$call$(<$T>)?$(($rhs))? -> Self $(where $($rest)*)?); - }; - ($name:ident impls $($rest:tt)*) => { - impl_ops!(@impl $name impls $($rest)*); - }; +impl PitchMod for Pitch { + type Output = Self; + fn pitchmod(&self) -> Self::Output { + Self(self.0.pitchmod()) + } } -macro_rules! impl_pitch_ops { - ($name:ident -> $($out:ty),* $(,)?) => { - $( - impl_ops!($name impls Add.add -> $out); - impl_ops!($name impls Div.div -> $out); - impl_ops!($name impls Mul.mul -> $out); - impl_ops!($name impls Rem.rem -> $out); - impl_ops!($name impls Sub.sub -> $out); - )* - }; - ($name:ident -> $($out:ty $(where $(rest:tt)*)?),* $(,)?) => { - $( - impl_ops!($name impls Add.add -> $out $(where $(rest)*)?); - impl_ops!($name impls Div.div -> $out $(where $(rest)*)?); - impl_ops!($name impls Mul.mul -> $out $(where $(rest)*)?); - impl_ops!($name impls Rem.rem -> $out $(where $(rest)*)?); - impl_ops!($name impls Sub.sub -> $out $(where $(rest)*)?); - )* - }; +wrapper_ops!(Pitch::: Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); +wrapper_unop!(Pitch impls Neg.neg, Not.not); + +impl num::One for Pitch { + fn one() -> Self { + Self(PitchTy::one()) + } } -macro_rules! impl_std_ops { - (@impl $trait:ident.$call:ident) => { - impl

core::ops::$trait

for Pitch - where - PitchTy: core::ops::$trait, - { - type Output = Pitch; - - fn $call(self, rhs: P) -> Self::Output { - let p = ::core::ops::$trait::$call(self.0, rhs); - Pitch(p) - } - } - - impl<'a, P> core::ops::$trait

for &'a Pitch - where - PitchTy: core::ops::$trait, - { - type Output = Pitch; - - fn $call(self, rhs: P) -> Self::Output { - let p = ::core::ops::$trait::$call(self.0, rhs); - Pitch(p) - } - } - }; - ($($trait:ident.$call:ident),* $(,)?) => { - $( - impl_std_ops!(@impl $trait.$call); - )* - }; +impl num::Zero for Pitch { + fn zero() -> Self { + Self(PitchTy::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } } -impl_std_ops!(Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); +impl num::Num for Pitch { + type FromStrRadixErr = ::FromStrRadixErr; -impl_pitch_ops!(Pitch -> i8); + fn from_str_radix(s: &str, radix: u32) -> Result { + PitchTy::from_str_radix(s, radix).map(Self) + } +} diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs index 375b77e..e30ba90 100644 --- a/core/src/types/intervals.rs +++ b/core/src/types/intervals.rs @@ -144,3 +144,10 @@ interval! { Augmented = 12, } } + +impl Fifth { + pub fn from_thirds(lhs: Third, rhs: Third) -> Self { + let value = lhs as i8 + rhs as i8; + Self::from(value) + } +} diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 1c6c979..fd67c4c 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,13 +2,11 @@ Appellation: types Contrib: FL03 */ -pub use self::intervals::*; +pub use self::{intervals::*, octave::*}; pub mod intervals; +pub mod octave; -/// A type alias for an `octave`; Musically speaking, an octave is the interval (distance) between one musical pitch and another -/// with either half or double its frequency. -pub type Octave = i8; /// pub type Tuple3 = (A, B, C); diff --git a/core/src/types/octave.rs b/core/src/types/octave.rs new file mode 100644 index 0000000..88a9536 --- /dev/null +++ b/core/src/types/octave.rs @@ -0,0 +1,98 @@ +/* + Appellation: octave + Contrib: FL03 +*/ + +/// A type alias for an `octave`; Musically speaking, an octave is the interval (distance) between one musical pitch and another +/// with either half or double its frequency. +pub(crate) type OctaveTy = i8; + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Octave(pub(crate) OctaveTy); + +impl Octave { + pub fn new(octave: OctaveTy) -> Self { + Self(octave) + } + + pub fn into_inner(self) -> OctaveTy { + self.0 + } + + pub fn value(&self) -> OctaveTy { + self.0 + } +} + +impl AsRef for Octave { + fn as_ref(&self) -> &OctaveTy { + &self.0 + } +} + +impl AsMut for Octave { + fn as_mut(&mut self) -> &mut OctaveTy { + &mut self.0 + } +} + +impl core::borrow::Borrow for Octave { + fn borrow(&self) -> &OctaveTy { + &self.0 + } +} + +impl core::borrow::BorrowMut for Octave { + fn borrow_mut(&mut self) -> &mut OctaveTy { + &mut self.0 + } +} + +impl core::ops::Deref for Octave { + type Target = OctaveTy; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for Octave { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl core::fmt::Display for Octave { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +wrapper_ops!(Octave::: Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); + +wrapper_unop!(Octave impls Neg.neg, Not.not); + +impl num::One for Octave { + fn one() -> Self { + Self(OctaveTy::one()) + } +} + +impl num::Zero for Octave { + fn zero() -> Self { + Self(OctaveTy::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +impl num::Num for Octave { + type FromStrRadixErr = ::FromStrRadixErr; + + fn from_str_radix(s: &str, radix: u32) -> Result { + OctaveTy::from_str_radix(s, radix).map(Self) + } +} diff --git a/core/tests/pitches.rs b/core/tests/pitches.rs index b9e6e03..f9c2b9b 100644 --- a/core/tests/pitches.rs +++ b/core/tests/pitches.rs @@ -16,7 +16,8 @@ where #[test] fn test_pitch() { - let pitch = Pitch::new(12); + let pitch = Pitch::new(0); + assert_eq!(pitch.class(), Pitch::new(12).class()); let b = pitch + 1; assert_ne!(pitch, b); diff --git a/neo/Cargo.toml b/neo/Cargo.toml index 91b5367..8e54099 100644 --- a/neo/Cargo.toml +++ b/neo/Cargo.toml @@ -43,6 +43,7 @@ test = true [dependencies] num = "0.4" +paste.workspace = true petgraph = "0.6" strum.workspace = true diff --git a/neo/src/triad/classes.rs b/neo/src/triad/classes.rs index 53bb1ac..4bac1a8 100644 --- a/neo/src/triad/classes.rs +++ b/neo/src/triad/classes.rs @@ -2,8 +2,109 @@ Appellation: classes Contrib: FL03 */ +use rstmt::{Fifth, Third}; -pub trait TriadTy {} +/// 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 TriadKind { + private!(); + + fn class(&self) -> Triads { + if self.is_augmented() { + Triads::Augmented + } else if self.is_diminished() { + Triads::Diminished + } else if self.is_major() { + Triads::Major + } else { + Triads::Minor + } + } + + fn thirds(&self) -> (Third, Third) { + use Third::*; + match self.class() { + Triads::Augmented => (Major, Major), + Triads::Diminished => (Minor, Minor), + Triads::Major => (Major, Minor), + Triads::Minor => (Minor, Major), + } + } + + fn root_to_third(&self) -> Third { + self.thirds().0 + } + + fn third_to_fifth(&self) -> Third { + self.thirds().1 + } + + fn root_to_fifth(&self) -> Fifth { + use Fifth::*; + match self.class() { + Triads::Augmented => Augmented, + Triads::Diminished => Diminished, + Triads::Major | Triads::Minor => Perfect, + } + } + + fn name(&self) -> &str; + + fn is_augmented(&self) -> bool { + false + } + + fn is_diminished(&self) -> bool { + false + } + + fn is_major(&self) -> bool { + false + } + + fn is_minor(&self) -> bool { + false + } +} + +macro_rules! class { + (@impl $name:ident::$call:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize),)] + pub enum $name {} + + impl TriadKind for $name { + seal!(); + + fn name(&self) -> &str { + stringify!($call) + } + + paste::paste! { + fn [](&self) -> bool { + true + } + } + } + + unsafe impl Send for $name {} + + unsafe impl Sync for $name {} + }; + ($($name:ident::$call:ident),* $(,)?) => { + $( + class!(@impl $name::$call); + )* + }; +} + +class!( + Augmented::augmented, + Diminished::diminished, + Major::major, + Minor::minor +); #[derive( Clone, @@ -55,4 +156,31 @@ impl Triads { pub fn minor() -> Self { Triads::Minor } + + pub fn thirds(&self) -> (Third, Third) { + use Third::*; + match self { + Triads::Augmented => (Major, Major), + Triads::Diminished => (Minor, Minor), + Triads::Major => (Major, Minor), + Triads::Minor => (Minor, Major), + } + } + + pub fn root_to_third(&self) -> Third { + self.thirds().0 + } + + pub fn third_to_fifth(&self) -> Third { + self.thirds().1 + } + + pub fn root_to_fifth(&self) -> Fifth { + use Fifth::*; + match self { + Triads::Augmented => Augmented, + Triads::Diminished => Diminished, + Triads::Major | Triads::Minor => Perfect, + } + } } diff --git a/neo/src/triad/mod.rs b/neo/src/triad/mod.rs index 26fc999..b2da4c8 100644 --- a/neo/src/triad/mod.rs +++ b/neo/src/triad/mod.rs @@ -3,13 +3,12 @@ Contrib: FL03 */ #[doc(inline)] -pub use self::{builder::TriadBuilder, classes::Triads, triad::Triad}; +pub use self::{builder::TriadBuilder, classes::*, triad::Triad}; pub(crate) mod builder; +pub(crate) mod classes; pub(crate) mod triad; -pub mod classes; - pub(crate) mod prelude { pub use super::builder::TriadBuilder; pub use super::classes::*; @@ -18,12 +17,30 @@ pub(crate) mod prelude { use rstmt::Intervals; +pub trait IntoTriad { + fn into_triad(self) -> Triad; +} + pub trait Triadic { + type Data: TriadData; + fn intervals(&self) -> impl Iterator; fn kind(&self) -> Triads; - fn notes(&self) -> (N, N, N); + fn notes(&self) -> &Self::Data; + + fn root(&self) -> &N { + self.notes().root() + } + + fn third(&self) -> &N { + self.notes().third() + } + + fn fifth(&self) -> &N { + self.notes().fifth() + } } pub trait TriadData { @@ -35,3 +52,9 @@ pub trait TriadData { fn fifth(&self) -> &Self::Elem; } + +impl IntoTriad for [u8; 3] { + fn into_triad(self) -> Triad { + Triad::from_slice(self) + } +} diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index 0116019..800a89a 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -2,8 +2,22 @@ Appellation: triad Contrib: FL03 */ +use super::{Major, TriadKind}; +use core::marker::PhantomData; -pub struct Triad { - pub kind: super::Triads, +pub struct Triad { + pub(crate) kind: PhantomData, pub notes: [u8; 3], } + +impl Triad +where + K: TriadKind, +{ + pub fn from_slice(notes: [u8; 3]) -> Self { + Self { + kind: PhantomData::, + notes, + } + } +} diff --git a/rstmt/Cargo.toml b/rstmt/Cargo.toml index 5afb4c1..f411d8d 100644 --- a/rstmt/Cargo.toml +++ b/rstmt/Cargo.toml @@ -55,6 +55,10 @@ crate-type = ["cdylib", "rlib"] doctest = false test = true +[dev-dependencies] +anyhow.workspace = true +num = "0.4" + [dependencies.rstmt-core] default-features = false path = "../core" diff --git a/rstmt/examples/misc.rs b/rstmt/examples/misc.rs new file mode 100644 index 0000000..344ed74 --- /dev/null +++ b/rstmt/examples/misc.rs @@ -0,0 +1,34 @@ +/* + Appellation: misc + Contrib: FL03 +*/ +use utils::pmod; + +fn main() -> Result<(), Box> { + assert_eq!(11, utils::absmod(11, 12)); + assert_ne!(dbg!(pmod(-17)), dbg!(-17 % 12)); + + Ok(()) +} + +mod utils { + use num::traits::{FromPrimitive, Num, Signed}; + + pub fn absmod(value: T, m: T) -> T + where + T: Copy + Num + PartialOrd + Signed, + { + let val = value % m; + if val >= T::zero() { + return val; + } + ((val + m) % m).abs() + } + + pub fn pmod(value: T) -> T + where + T: Copy + FromPrimitive + Num + PartialOrd + Signed, + { + absmod(value, T::from_i8(12).unwrap()) + } +} From 3c9f36ddbed16d9d96978c35c1beeac8947de271 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 04:05:11 -0500 Subject: [PATCH 15/21] update --- core/Cargo.toml | 9 ++++ core/src/{errors => error}/err.rs | 65 +++++++++++++++++++++++++++- core/src/{errors => error}/mod.rs | 0 core/src/lib.rs | 6 +-- core/src/macros.rs | 1 + core/src/macros/intervals.rs | 12 ++++++ core/src/macros/pitches.rs | 24 +++++++---- core/src/pitch/kinds.rs | 5 ++- core/src/pitch/mod.rs | 30 +++++++++---- core/src/pitch/pitch.rs | 55 +++++++++++++++++++++++ core/src/primitives.rs | 9 ++++ core/src/types/octave.rs | 30 +++++++++++++ neo/src/triad/classes.rs | 72 ++++++++++++++++--------------- neo/src/triad/mod.rs | 37 +++++++++++++++- neo/src/triad/triad.rs | 18 ++++++-- 15 files changed, 311 insertions(+), 62 deletions(-) rename core/src/{errors => error}/err.rs (59%) rename core/src/{errors => error}/mod.rs (100%) diff --git a/core/Cargo.toml b/core/Cargo.toml index 9322bc1..caf6c80 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -41,6 +41,7 @@ serde = [ std = [ "alloc", "num/std", + "regex/std", "serde?/std", "strum/std", ] @@ -62,6 +63,14 @@ smart-default.workspace = true default-features = false version = "0.4" +[dependencies.regex] +default-features = false +features = [ + "perf", + "unicode", +] +version = "1" + [dependencies.serde] default-features = false features = ["derive"] diff --git a/core/src/errors/err.rs b/core/src/error/err.rs similarity index 59% rename from core/src/errors/err.rs rename to core/src/error/err.rs index 848615f..a1f349f 100644 --- a/core/src/errors/err.rs +++ b/core/src/error/err.rs @@ -7,9 +7,9 @@ pub trait FluoError: core::fmt::Debug + core::fmt::Display + Send + Sync + 'stat impl FluoError for T where T: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {} -pub trait ErrorKind: Clone + core::str::FromStr + core::fmt::Debug + core::fmt::Display {} +pub trait ErrorKind: core::fmt::Debug + core::fmt::Display {} -impl ErrorKind for T where T: Clone + core::str::FromStr + core::fmt::Debug + core::fmt::Display {} +impl ErrorKind for T where T: core::fmt::Debug + core::fmt::Display {} #[derive( Clone, @@ -40,6 +40,28 @@ pub enum MusicalError { #[cfg(feature = "std")] impl std::error::Error for MusicalError {} +unsafe impl Send for MusicalError {} + +unsafe impl Sync for MusicalError {} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[repr(transparent)] +pub struct UnknownError; + +impl core::fmt::Display for UnknownError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Unknown error") + } +} + +unsafe impl Send for UnknownError {} + +unsafe impl Sync for UnknownError {} + +#[cfg(feature = "std")] +impl std::error::Error for UnknownError {} + #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -48,6 +70,12 @@ pub struct Error { pub msg: String, } +impl Error { + pub fn unknown(msg: impl ToString) -> Self { + Self::new(UnknownError, msg) + } +} + impl Error where K: ErrorKind, @@ -59,6 +87,8 @@ where } } + + pub fn kind(&self) -> &K { &self.kind } @@ -87,6 +117,37 @@ where } } +impl From> for Error { + fn from(err: Box) -> Self { + Self::unknown(err.to_string()) + } +} + +impl From<&str> for Error { + fn from(msg: &str) -> Self { + Self::unknown(msg) + } +} + +impl From for Error { + fn from(msg: String) -> Self { + Self::unknown(msg) + } +} + +impl From for Error +where + K: ErrorKind, +{ + fn from(kind: K) -> Self { + Self::new(kind, "") + } +} + +unsafe impl Send for Error where K: ErrorKind {} + +unsafe impl Sync for Error where K: ErrorKind {} + #[cfg(feature = "std")] impl std::error::Error for Error where K: ErrorKind {} diff --git a/core/src/errors/mod.rs b/core/src/error/mod.rs similarity index 100% rename from core/src/errors/mod.rs rename to core/src/error/mod.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 02cfa21..1e93ea8 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -12,7 +12,7 @@ extern crate alloc; #[doc(inline)] pub use self::{ - errors::{Error, Result}, + error::{Error, Result}, notes::Note, pitch::{Pitch, PitchClass, PitchTy}, primitives::*, @@ -24,7 +24,7 @@ pub use self::{ops::prelude::*, traits::prelude::*, types::prelude::*}; pub(crate) mod macros; pub(crate) mod primitives; -pub mod errors; +pub mod error; pub mod notes; pub mod ops; pub mod pitch; @@ -32,7 +32,7 @@ pub mod traits; pub mod types; pub mod prelude { - pub use super::errors::prelude::*; + pub use super::error::prelude::*; pub use super::notes::prelude::*; pub use super::ops::prelude::*; pub use super::pitch::prelude::*; diff --git a/core/src/macros.rs b/core/src/macros.rs index 8f44f9e..dffe948 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -2,6 +2,7 @@ Appellation: macros Contrib: FL03 */ +#![allow(unused_macros)] #[macro_use] pub(crate) mod errors; diff --git a/core/src/macros/intervals.rs b/core/src/macros/intervals.rs index a648c68..630660b 100644 --- a/core/src/macros/intervals.rs +++ b/core/src/macros/intervals.rs @@ -11,6 +11,18 @@ macro_rules! interval { } } + impl $name { + pub fn value(&self) -> i8 { + *self as i8 + } + + pub fn validate(value: i8) -> bool { + Self::try_from(value).is_ok() + } + + + } + enum_as!($name: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); impl From for $name where { diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index cf33b56..e3f501b 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -3,6 +3,14 @@ Contrib: FL03 */ +macro_rules! pitch { + (@impl $class:ident($mud:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { + pub struct $class; + + + }; +} + macro_rules! pitch_class { ($(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? $(#[rename($rename:literal)])? $vis:vis enum $name:ident $($rest:tt)*) => { pitch_class!(@impl $(#[derive($($derive),*)])? $(#[default($default)])? $(#[rename($rename)])? $vis enum $name $($rest)*); @@ -51,9 +59,10 @@ macro_rules! impl_pitch { pub fn new(value: $crate::PitchTy) -> Option { $group::try_from(value).ok() } - pub fn try_from_value(value: $crate::PitchTy) -> $crate::Result { - match $crate::PitchMod::pitchmod(&value) { - $(x if x == $value => Ok(Self::$class),)* + + pub fn try_from_value(value: impl $crate::pitch::IntoPitch) -> Result> { + match $crate::PitchMod::pitchmod(&value.into_pitch()) { + $(x if *x == $value => Ok(Self::$class),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } } @@ -68,6 +77,8 @@ macro_rules! impl_pitch { } impl $crate::pitch::PitchClass for $group { + seal!(); + fn pitch(&self) -> $crate::PitchTy { *self as $crate::PitchTy } @@ -89,10 +100,7 @@ macro_rules! impl_pitch { type Error = $crate::Error; fn try_from(p: $crate::pitch::PitchTy) -> Result { - match $crate::PitchMod::pitchmod(&p) { - $(x if x == $value => Ok(Self::$class),)* - _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) - } + Self::try_from_value(p) } } @@ -100,7 +108,7 @@ macro_rules! impl_pitch { type Error = $crate::Error; fn try_from(p: $crate::pitch::Pitch) -> Result { - Self::try_from(p.value()) + Self::try_from_value(p) } } }; diff --git a/core/src/pitch/kinds.rs b/core/src/pitch/kinds.rs index cbd9555..c6c4491 100644 --- a/core/src/pitch/kinds.rs +++ b/core/src/pitch/kinds.rs @@ -3,6 +3,7 @@ Contrib: FL03 */ use super::{PitchClass, PitchTy}; +use crate::error::{Error, MusicalError}; pitch_class! { #[default(C)] @@ -71,7 +72,7 @@ pub enum Pitches { } impl Pitches { - pub fn try_from_value(value: PitchTy) -> crate::Result { + pub fn try_from_value(value: PitchTy) -> Result> { if let Ok(n) = Natural::try_from_value(value) { Ok(n.as_class()) } else if let Ok(s) = Sharp::try_from_value(value) { @@ -93,6 +94,8 @@ impl Pitches { } impl PitchClass for Pitches { + seal!(); + fn pitch(&self) -> PitchTy { self.pitch() } diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index b8b9f5a..bbaed48 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -11,42 +11,56 @@ pub(crate) mod pitch; pub(crate) mod prelude { pub use super::kinds::{Flat, Natural, Pitches, Sharp}; pub use super::pitch::Pitch; - pub use super::{AccidentalPitch, PitchClass, PitchTy}; + pub use super::{Accidentals, PitchClass, PitchTy}; } /// A type alias for an integer representing a particular pitch of a note pub type PitchTy = i8; +pub trait IntoPitch { + fn into_pitch(self) -> Pitch; +} + +/// Used to denote a particular pitch class; pitch classes are symbolic +/// representations of pre-defined frequencies. pub trait PitchClass { + private!(); + fn pitch(&self) -> PitchTy; } -pub trait SharpPitch { +pub trait Sharps { private!(); } -pub trait FlatPitch { +pub trait Flats { private!(); } -pub trait AccidentalPitch: PitchClass { +pub trait Accidentals: PitchClass { private!(); } /* ************* Implementations ************* */ -impl FlatPitch for Flat { +impl IntoPitch for S where S: Into { + fn into_pitch(self) -> Pitch { + self.into() + } +} + +impl Flats for Flat { seal!(); } -impl SharpPitch for Sharp { +impl Sharps for Sharp { seal!(); } -impl AccidentalPitch for Sharp { +impl Accidentals for Sharp { seal!(); } -impl AccidentalPitch for Flat { +impl Accidentals for Flat { seal!(); } diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index ed1abdd..7c543fb 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -67,12 +67,42 @@ impl core::ops::DerefMut for Pitch { } } +impl core::fmt::Binary for Pitch { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Binary::fmt(&self.0, f) + } +} + impl core::fmt::Display for Pitch { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.0) } } +impl core::fmt::LowerExp for Pitch { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::LowerExp::fmt(&self.0, f) + } +} + +impl core::fmt::LowerHex for Pitch { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::LowerHex::fmt(&self.0, f) + } +} + +impl core::fmt::Octal for Pitch { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Octal::fmt(&self.0, f) + } +} + +impl core::fmt::UpperExp for Pitch { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::UpperExp::fmt(&self.0, f) + } +} + impl PitchMod for Pitch { type Output = Self; @@ -107,3 +137,28 @@ impl num::Num for Pitch { PitchTy::from_str_radix(s, radix).map(Self) } } + +impl From for Pitch { + fn from(pitch: PitchTy) -> Self { + Self(pitch) + } +} + +impl From for PitchTy { + fn from(pitch: Pitch) -> Self { + pitch.0 + } +} + +impl From for Pitch { + fn from(pitch: Pitches) -> Self { + Self(pitch.pitch()) + } +} + +impl From for Pitches { + fn from(pitch: Pitch) -> Self { + Pitches::try_from_value(pitch.0).unwrap() + } +} + diff --git a/core/src/primitives.rs b/core/src/primitives.rs index 018a056..44c82f8 100644 --- a/core/src/primitives.rs +++ b/core/src/primitives.rs @@ -16,3 +16,12 @@ pub mod consts { /// A tone is a difference of two pub const TONE: i8 = 2; } + +pub mod statics { + use lazy_static::lazy_static; + use regex::Regex; + + lazy_static! { + static ref REGEX_PITCH: Regex = Regex::new("^[ABCDEFGabcdefg][b♭♯#s𝄪x]*").unwrap(); + } +} \ No newline at end of file diff --git a/core/src/types/octave.rs b/core/src/types/octave.rs index 88a9536..a6d5061 100644 --- a/core/src/types/octave.rs +++ b/core/src/types/octave.rs @@ -63,12 +63,42 @@ impl core::ops::DerefMut for Octave { } } +impl core::fmt::Binary for Octave { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Binary::fmt(&self.0, f) + } +} + impl core::fmt::Display for Octave { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.0) } } +impl core::fmt::LowerExp for Octave { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::LowerExp::fmt(&self.0, f) + } +} + +impl core::fmt::LowerHex for Octave { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::LowerHex::fmt(&self.0, f) + } +} + +impl core::fmt::Octal for Octave { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Octal::fmt(&self.0, f) + } +} + +impl core::fmt::UpperExp for Octave { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::UpperExp::fmt(&self.0, f) + } +} + wrapper_ops!(Octave::: Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); wrapper_unop!(Octave impls Neg.neg, Not.not); diff --git a/neo/src/triad/classes.rs b/neo/src/triad/classes.rs index 4bac1a8..c56ce89 100644 --- a/neo/src/triad/classes.rs +++ b/neo/src/triad/classes.rs @@ -10,60 +10,53 @@ use rstmt::{Fifth, Third}; pub trait TriadKind { private!(); - fn class(&self) -> Triads { - if self.is_augmented() { - Triads::Augmented - } else if self.is_diminished() { - Triads::Diminished - } else if self.is_major() { + fn is_valid(notes: &[u8; 3]) -> bool { + Self::class().validate(notes) + } + + fn class() -> Triads { + if Self::is_major() { Triads::Major - } else { + } else if Self::is_minor() { Triads::Minor - } + } else if Self::is_augmented() { + Triads::Augmented + } else { + Triads::Diminished + } } - fn thirds(&self) -> (Third, Third) { - use Third::*; - match self.class() { - Triads::Augmented => (Major, Major), - Triads::Diminished => (Minor, Minor), - Triads::Major => (Major, Minor), - Triads::Minor => (Minor, Major), - } + fn thirds() -> (Third, Third) { + Self::class().thirds() } - fn root_to_third(&self) -> Third { - self.thirds().0 + fn root_to_third() -> Third { + Self::thirds().0 } - fn third_to_fifth(&self) -> Third { - self.thirds().1 + fn third_to_fifth() -> Third { + Self::thirds().1 } - fn root_to_fifth(&self) -> Fifth { - use Fifth::*; - match self.class() { - Triads::Augmented => Augmented, - Triads::Diminished => Diminished, - Triads::Major | Triads::Minor => Perfect, - } + fn root_to_fifth() -> Fifth { + Self::class().root_to_fifth() } - fn name(&self) -> &str; + fn name() -> &'static str; - fn is_augmented(&self) -> bool { + fn is_augmented() -> bool { false } - fn is_diminished(&self) -> bool { + fn is_diminished() -> bool { false } - fn is_major(&self) -> bool { + fn is_major() -> bool { false } - fn is_minor(&self) -> bool { + fn is_minor() -> bool { false } } @@ -77,12 +70,12 @@ macro_rules! class { impl TriadKind for $name { seal!(); - fn name(&self) -> &str { - stringify!($call) + fn name() -> &'static str { + stringify!($name) } paste::paste! { - fn [](&self) -> bool { + fn []() -> bool { true } } @@ -157,6 +150,15 @@ impl Triads { Triads::Minor } + pub fn validate(&self, notes: &[u8; 3]) -> bool { + // the interval between the root and the third must be a third + let rt = notes[1] - notes[0]; + // the interval between the third and the fifth must be a third + let tf = notes[2] - notes[1]; + + Third::try_from(rt).is_ok() && Third::try_from(tf).is_ok() + } + pub fn thirds(&self) -> (Third, Third) { use Third::*; match self { diff --git a/neo/src/triad/mod.rs b/neo/src/triad/mod.rs index b2da4c8..2ad2b8f 100644 --- a/neo/src/triad/mod.rs +++ b/neo/src/triad/mod.rs @@ -53,8 +53,43 @@ pub trait TriadData { fn fifth(&self) -> &Self::Elem; } +/* + ************* Implementations ************* +*/ impl IntoTriad for [u8; 3] { fn into_triad(self) -> Triad { - Triad::from_slice(self) + Triad::from_slice(self).unwrap() + } +} + +impl TriadData for [T; 3] { + type Elem = T; + + fn root(&self) -> &Self::Elem { + &self[0] + } + + fn third(&self) -> &Self::Elem { + &self[1] + } + + fn fifth(&self) -> &Self::Elem { + &self[2] + } +} + +impl TriadData for (T, T, T) { + type Elem = T; + + fn root(&self) -> &Self::Elem { + &self.0 + } + + fn third(&self) -> &Self::Elem { + &self.1 + } + + fn fifth(&self) -> &Self::Elem { + &self.2 } } diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index 800a89a..4448d10 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -4,6 +4,7 @@ */ use super::{Major, TriadKind}; use core::marker::PhantomData; +use rstmt::{Error, Third}; pub struct Triad { pub(crate) kind: PhantomData, @@ -14,10 +15,19 @@ impl Triad where K: TriadKind, { - pub fn from_slice(notes: [u8; 3]) -> Self { - Self { - kind: PhantomData::, - notes, + pub fn from_slice(notes: [u8; 3]) -> Result { + let (rt, tf) = K::thirds(); + let (r, t, f) = (notes[0], notes[1], notes[2]); + let rt2 = Third::try_from(t - r); + let tf2 = Third::try_from(f - t); + if rt2 == Ok(rt) && tf2 == Ok(tf) { + Ok(Self { + kind: PhantomData::, + notes, + }) + } else { + Err(Error::invalid_interval("Invalid interval.")) } + } } From 1b91aade9c2171bd2b03aecabd5208245f8dbb7f Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 08:02:01 -0500 Subject: [PATCH 16/21] update --- core/src/error/err.rs | 2 - core/src/lib.rs | 4 +- core/src/macros/intervals.rs | 29 +++++--- core/src/macros/ops.rs | 16 +++++ core/src/macros/pitches.rs | 2 - core/src/notes/note.rs | 133 +++++++++++++++++++++++------------ core/src/pitch/kinds.rs | 2 +- core/src/pitch/mod.rs | 5 +- core/src/pitch/pitch.rs | 9 ++- core/src/primitives.rs | 4 +- core/src/traits/notable.rs | 15 +++- core/src/types/intervals.rs | 65 ++++++++--------- neo/src/triad/classes.rs | 12 ++-- neo/src/triad/mod.rs | 8 +-- neo/src/triad/triad.rs | 23 ++++-- 15 files changed, 212 insertions(+), 117 deletions(-) diff --git a/core/src/error/err.rs b/core/src/error/err.rs index a1f349f..0bd39af 100644 --- a/core/src/error/err.rs +++ b/core/src/error/err.rs @@ -87,8 +87,6 @@ where } } - - pub fn kind(&self) -> &K { &self.kind } diff --git a/core/src/lib.rs b/core/src/lib.rs index 1e93ea8..6bb939c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -12,9 +12,9 @@ extern crate alloc; #[doc(inline)] pub use self::{ - error::{Error, Result}, + error::{Error, MusicalError, Result}, notes::Note, - pitch::{Pitch, PitchClass, PitchTy}, + pitch::{IntoPitch, Pitch, PitchClass, PitchTy}, primitives::*, }; #[doc(inline)] diff --git a/core/src/macros/intervals.rs b/core/src/macros/intervals.rs index 630660b..a704d81 100644 --- a/core/src/macros/intervals.rs +++ b/core/src/macros/intervals.rs @@ -19,22 +19,35 @@ macro_rules! interval { pub fn validate(value: i8) -> bool { Self::try_from(value).is_ok() } + } + enum_as!($name: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + impl $crate::Interval for $name { + fn value(&self) -> i8 { + *self as i8 + } } - enum_as!($name: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + impl TryFrom for $name where { + type Error = $crate::error::MusicalError; - impl From for $name where { - fn from(interval: i8) -> $name { - use strum::EnumCount; - match interval % Self::COUNT as i8 { - $($val => $name::$key),*, - _ => panic!("Invalid interval value: {}", interval), + fn try_from(interval: i8) -> Result<$name, Self::Error> { + match interval { + $($val => Ok($name::$key)),*, + _ => Err($crate::error::MusicalError::InvalidInterval), } } } - enum_from_value!(u8 => $name {$($key: $val),*}); + + impl TryFrom<$crate::Pitch> for $name where { + type Error = $crate::error::MusicalError; + + fn try_from(interval: $crate::Pitch) -> Result<$name, Self::Error> { + Self::try_from(interval.0) + } + } + $( impl Default for $name { fn default() -> Self { diff --git a/core/src/macros/ops.rs b/core/src/macros/ops.rs index 4d1f440..def3fdb 100644 --- a/core/src/macros/ops.rs +++ b/core/src/macros/ops.rs @@ -3,6 +3,22 @@ Contrib: FL03 */ +macro_rules! impl_binop_method { + (@impl $trait:ident.$call:ident -> $out:ty) => { + paste::paste! { + pub fn [<$call _interval>](&self, interval: $crate::Intervals) -> $out { + let p = core::ops::$trait::$call(self.value(), interval.value()); + Self::new(p) + } + } + }; + ($($trait:ident.$call:ident -> $out:ty),* $(,)?) => { + $( + impl_binop_method!(@impl $trait.$call -> $out); + )* + }; +} + macro_rules! wrapper_ops { (@impl $name:ident::<$type:ty>::$trait:ident.$call:ident) => { impl core::ops::$trait<$name> for $name { diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index e3f501b..4f20c3b 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -6,8 +6,6 @@ macro_rules! pitch { (@impl $class:ident($mud:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { pub struct $class; - - }; } diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index 8f78abf..e584e2b 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -2,36 +2,36 @@ Appellation: note Contrib: FL03 */ -use crate::{Notable, Octave, Pitch}; +use crate::{IntoPitch, Octave, Pitch}; -#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Note

{ +pub struct Note { pub(crate) octave: Octave, - pub(crate) pitch: P, + pub(crate) pitch: Pitch, } -impl

Note

-where - P: Notable, -{ - pub fn new(octave: Octave, pitch: P) -> Self { - Self { octave, pitch } +impl Note { + pub fn new(pitch: Pitch) -> Self { + Self { + octave: Octave::default(), + pitch, + } } - /// Returns an owned instance of the note's octave - pub const fn octave(&self) -> &Octave { - &self.octave + /// Returns an instance of the note's octave + pub fn octave(&self) -> Octave { + self.octave } /// Returns a mutable reference to the note's octave pub fn octave_mut(&mut self) -> &mut Octave { &mut self.octave } /// Returns an owned instance of the note's pitch - pub const fn pitch(&self) -> &P { - &self.pitch + pub fn pitch(&self) -> Pitch { + self.pitch } /// Returns a mutable reference to the note's pitch - pub fn pitch_mut(&mut self) -> &mut P { + pub fn pitch_mut(&mut self) -> &mut Pitch { &mut self.pitch } /// Sets the note's octave @@ -39,7 +39,7 @@ where self.octave = octave; } /// Sets the note's pitch - pub fn set_pitch(&mut self, pitch: P) { + pub fn set_pitch(&mut self, pitch: Pitch) { self.pitch = pitch; } /// Returns a new instance of the note with the given octave @@ -47,36 +47,27 @@ where Self { octave, ..self } } /// Returns a new instance of the note with the given pitch - pub fn with_pitch(self, pitch: P2) -> Note - where - P2: Notable, - { + pub fn with_pitch(self, pitch: impl IntoPitch) -> Note { Note { octave: self.octave, - pitch, + pitch: pitch.into_pitch(), } } } -impl

core::fmt::Display for Note

-where - P: Notable, -{ +impl core::fmt::Display for Note { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}.{}", self.pitch, self.octave) } } -unsafe impl

Send for Note

where P: Send {} +unsafe impl Send for Note {} -unsafe impl

Sync for Note

where P: Sync {} +unsafe impl Sync for Note {} macro_rules! impl_std_ops { (@impl $trait:ident.$call:ident($rhs:ty) -> $out:ty) => { - impl

core::ops::$trait<$rhs> for Note

- where - P: $crate::Notable + core::ops::$trait, - { + impl core::ops::$trait<$rhs> for Note { type Output = $out; fn $call(self, rhs: $rhs) -> Self::Output { @@ -87,10 +78,7 @@ macro_rules! impl_std_ops { } } - impl<'a, P> core::ops::$trait<&'a $rhs> for Note

- where - P: $crate::Notable + core::ops::$trait, - { + impl<'a> core::ops::$trait<&'a $rhs> for Note { type Output = $out; fn $call(self, rhs: &'a $rhs) -> Self::Output { @@ -101,10 +89,7 @@ macro_rules! impl_std_ops { } } - impl<'a, P> core::ops::$trait<$rhs> for &'a Note

- where - P: $crate::Notable + core::ops::$trait, - { + impl<'a> core::ops::$trait<$rhs> for &'a Note { type Output = $out; fn $call(self, rhs: $rhs) -> Self::Output { @@ -115,10 +100,7 @@ macro_rules! impl_std_ops { } } - impl<'a, P> core::ops::$trait<&'a $rhs> for &'a Note

- where - P: $crate::Notable + core::ops::$trait, - { + impl<'a> core::ops::$trait<&'a $rhs> for &'a Note { type Output = $out; fn $call(self, rhs: &'a $rhs) -> Self::Output { @@ -129,11 +111,12 @@ macro_rules! impl_std_ops { } } }; + (@impl $trait:ident.$call:ident($rhs:ty)) => { - impl_std_ops!(@impl $trait.$call($rhs) -> Note

); + impl_std_ops!(@impl $trait.$call($rhs) -> Note); }; (@impl $trait:ident.$call:ident $(-> $out:ty)?) => { - impl_std_ops!(@impl $trait.$call(Note

) $(-> $out)?); + impl_std_ops!(@impl $trait.$call(Note) $(-> $out)?); }; ($($trait:ident.$call:ident$(($rhs:ty))? $(-> $out:ty)?),* $(,)?) => { $( @@ -143,3 +126,61 @@ macro_rules! impl_std_ops { } impl_std_ops!(Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); + +macro_rules! impl_binop_assign { + (@impl $name:ident::$trait:ident.$call:ident) => {}; +} + +macro_rules! operator { + (@base $trait:ident.$call:ident($lhs:ty, $rhs:ty) -> $out:ty {$($rest:tt)*}) => { + impl core::ops::$trait<$rhs> for $lhs { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + $($rest)* + } + } + }; + (@a $trait:ident.$call:ident($lhs:ty, $rhs:ty) -> $out:ty {$e:expr}) => { + operator!(@base $trait.$call($lhs, $rhs) -> $out {$e($lhs, $rhs)}); + }; + + (@impl $trait:ident.$call:ident($lhs:ty, $rhs:ty) -> $out:ty {$e:expr}) => { + impl core::ops::$trait<$rhs> for $lhs { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + $e(self, rhs) + } + } + + impl<'a> core::ops::$trait<&'a $rhs> for $lhs { + type Output = $out; + + fn $call(self, rhs: &'a $rhs) -> Self::Output { + $e(self, *rhs) + } + } + + impl<'a> core::ops::$trait<$rhs> for &'a $lhs { + type Output = $out; + + fn $call(self, rhs: $rhs) -> Self::Output { + $e(*self, rhs) + } + } + + impl<'a> core::ops::$trait<&'a $rhs> for &'a $lhs { + type Output = $out; + + fn $call(self, rhs: &'a $rhs) -> Self::Output { + $e(*self, *rhs) + } + } + }; + ($($trait:ident.$call:ident(self: $lhs:ty, rhs: $rhs:ty) -> $out:ty {$e:expr}),* $(,)?) => { + $( + operator!(@impl $trait.$call($lhs, $rhs) -> $out {$e}); + )* + }; +} diff --git a/core/src/pitch/kinds.rs b/core/src/pitch/kinds.rs index c6c4491..57a1c2e 100644 --- a/core/src/pitch/kinds.rs +++ b/core/src/pitch/kinds.rs @@ -95,7 +95,7 @@ impl Pitches { impl PitchClass for Pitches { seal!(); - + fn pitch(&self) -> PitchTy { self.pitch() } diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index bbaed48..c8dbeef 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -44,7 +44,10 @@ pub trait Accidentals: PitchClass { /* ************* Implementations ************* */ -impl IntoPitch for S where S: Into { +impl IntoPitch for S +where + S: Into, +{ fn into_pitch(self) -> Pitch { self.into() } diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 7c543fb..608feb8 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -33,6 +33,14 @@ impl Pitch { pub fn value(&self) -> PitchTy { self.0 } + + impl_binop_method! { + Add.add -> Self, + Sub.sub -> Self, + Mul.mul -> Self, + Div.div -> Self, + Rem.rem -> Self + } } impl AsRef for Pitch { @@ -161,4 +169,3 @@ impl From for Pitches { Pitches::try_from_value(pitch.0).unwrap() } } - diff --git a/core/src/primitives.rs b/core/src/primitives.rs index 44c82f8..aee271a 100644 --- a/core/src/primitives.rs +++ b/core/src/primitives.rs @@ -20,8 +20,8 @@ pub mod consts { pub mod statics { use lazy_static::lazy_static; use regex::Regex; - + lazy_static! { static ref REGEX_PITCH: Regex = Regex::new("^[ABCDEFGabcdefg][b♭♯#s𝄪x]*").unwrap(); } -} \ No newline at end of file +} diff --git a/core/src/traits/notable.rs b/core/src/traits/notable.rs index c083a5f..9e954b0 100644 --- a/core/src/traits/notable.rs +++ b/core/src/traits/notable.rs @@ -2,7 +2,10 @@ Appellation: notable Contrib: FL03 */ -use crate::pitch::{Pitch, PitchTy, Pitches}; +use crate::{ + pitch::{Pitch, PitchTy, Pitches}, + Intervals, +}; pub trait Notable: Copy + Sized + core::fmt::Display { /// Classify the pitch into a pitch class @@ -13,9 +16,19 @@ pub trait Notable: Copy + Sized + core::fmt::Display { fn pitch(&self) -> Pitch; } +pub trait Interval { + fn value(&self) -> i8; +} + /* ************* Implementations ************* */ +impl Interval for Intervals { + fn value(&self) -> i8 { + self.value() + } +} + impl Notable for Pitch { fn class(&self) -> Pitches { self.class() diff --git a/core/src/types/intervals.rs b/core/src/types/intervals.rs index e30ba90..442ee5b 100644 --- a/core/src/types/intervals.rs +++ b/core/src/types/intervals.rs @@ -2,21 +2,7 @@ Appellation: intervals Contrib: FL03 */ -use crate::{Notable, Pitch}; -use num::traits::NumOps; - -pub trait IntervalOps: NumOps {} - -pub struct Interval { - pub lhs: A, - pub rhs: B, -} - -impl Interval { - pub fn new(lhs: A, rhs: B) -> Self { - Self { lhs, rhs } - } -} +use crate::{IntoPitch, Pitch}; unit_enum! { rename: "lowercase"; @@ -33,57 +19,67 @@ unit_enum! { } impl Intervals { - pub fn from_value(value: impl Notable) -> Self { + pub fn from_value(value: impl IntoPitch) -> Self { use Intervals::*; - let pitch = value.pitch(); + let pitch = value.into_pitch(); match *pitch { 1 => Semitone, 2 => Tone, - 3..=4 => Thirds(Third::from(pitch.value())), - 5 => Fourths(Fourth::from(pitch.value())), - 6..=8 => Fifths(Fifth::from(pitch.value())), - 9..=12 => Sevenths(Seventh::from(pitch.value())), + 3 => Thirds(Third::Minor), + 4 => Thirds(Third::Major), + 5 => Fourths(Fourth::Perfect), + 6 => Fifths(Fifth::Diminished), + 7 => Fifths(Fifth::Perfect), + 8 => Fifths(Fifth::Augmented), + 9 => Sevenths(Seventh::Diminished), + 10 => Sevenths(Seventh::Minor), + 11 => Sevenths(Seventh::Major), + 12 => Sevenths(Seventh::Augmented), + _ => panic!("Invalid interval value: {}", pitch.value()), } } - pub fn from_semitone() -> Self { + pub fn semitone() -> Self { Intervals::Semitone } - pub fn from_tone() -> Self { + pub fn tone() -> Self { Intervals::Tone } - pub fn from_thirds(third: Third) -> Self { + pub fn third(third: Third) -> Self { Intervals::Thirds(third) } - pub fn from_fourths(fourth: Fourth) -> Self { + pub fn fourth(fourth: Fourth) -> Self { Intervals::Fourths(fourth) } - pub fn from_fifths(fifth: Fifth) -> Self { + pub fn fifth(fifth: Fifth) -> Self { Intervals::Fifths(fifth) } - pub fn from_sevenths(seventh: Seventh) -> Self { + pub fn seventh(seventh: Seventh) -> Self { Intervals::Sevenths(seventh) } + /// Interpret the current interval as a pitch. + pub fn as_pitch(&self) -> Pitch { + Pitch::from(self.value()) + } pub fn name(&self) -> &str { self.as_ref() } - pub fn value(&self) -> Pitch { - let p = match *self { + pub fn value(&self) -> i8 { + match *self { Intervals::Semitone => 1, Intervals::Tone => 2, Intervals::Thirds(third) => third as i8, Intervals::Fourths(fourth) => fourth as i8, Intervals::Fifths(fifth) => fifth as i8, Intervals::Sevenths(seventh) => seventh as i8, - }; - Pitch(p) + } } } @@ -148,6 +144,11 @@ interval! { impl Fifth { pub fn from_thirds(lhs: Third, rhs: Third) -> Self { let value = lhs as i8 + rhs as i8; - Self::from(value) + match value { + 6 => Fifth::Diminished, + 7 => Fifth::Perfect, + 8 => Fifth::Augmented, + _ => panic!("Invalid fifth value: {}", value), + } } } diff --git a/neo/src/triad/classes.rs b/neo/src/triad/classes.rs index c56ce89..e2946e7 100644 --- a/neo/src/triad/classes.rs +++ b/neo/src/triad/classes.rs @@ -2,7 +2,7 @@ Appellation: classes Contrib: FL03 */ -use rstmt::{Fifth, Third}; +use rstmt::{Fifth, Note, Third}; /// 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 @@ -10,7 +10,7 @@ use rstmt::{Fifth, Third}; pub trait TriadKind { private!(); - fn is_valid(notes: &[u8; 3]) -> bool { + fn is_valid(notes: &[Note; 3]) -> bool { Self::class().validate(notes) } @@ -23,7 +23,7 @@ pub trait TriadKind { Triads::Augmented } else { Triads::Diminished - } + } } fn thirds() -> (Third, Third) { @@ -150,13 +150,13 @@ impl Triads { Triads::Minor } - pub fn validate(&self, notes: &[u8; 3]) -> bool { + pub fn validate(&self, notes: &[Note; 3]) -> bool { // the interval between the root and the third must be a third let rt = notes[1] - notes[0]; // the interval between the third and the fifth must be a third let tf = notes[2] - notes[1]; - - Third::try_from(rt).is_ok() && Third::try_from(tf).is_ok() + + Third::try_from(rt.pitch()).is_ok() && Third::try_from(tf.pitch()).is_ok() } pub fn thirds(&self) -> (Third, Third) { diff --git a/neo/src/triad/mod.rs b/neo/src/triad/mod.rs index 2ad2b8f..68c717b 100644 --- a/neo/src/triad/mod.rs +++ b/neo/src/triad/mod.rs @@ -54,14 +54,8 @@ pub trait TriadData { } /* - ************* Implementations ************* + ************* Implementations ************* */ -impl IntoTriad for [u8; 3] { - fn into_triad(self) -> Triad { - Triad::from_slice(self).unwrap() - } -} - impl TriadData for [T; 3] { type Elem = T; diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index 4448d10..cf03d3c 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -4,22 +4,34 @@ */ use super::{Major, TriadKind}; use core::marker::PhantomData; -use rstmt::{Error, Third}; +use rstmt::{Error, Note, Third}; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Triad { pub(crate) kind: PhantomData, - pub notes: [u8; 3], + pub notes: [Note; 3], } impl Triad where K: TriadKind, { - pub fn from_slice(notes: [u8; 3]) -> Result { + pub fn new(root: Note) -> Self { + let (rt, tf) = K::thirds(); + let octave = root.octave(); + let t = Note::new(root.pitch() + rt.value()).with_octave(octave); + let f = Note::new(t.pitch() + tf.value()).with_octave(octave); + Self { + kind: PhantomData::, + notes: [root, t, f], + } + } + pub fn from_slice(notes: [Note; 3]) -> Result { let (rt, tf) = K::thirds(); let (r, t, f) = (notes[0], notes[1], notes[2]); - let rt2 = Third::try_from(t - r); - let tf2 = Third::try_from(f - t); + let rt2 = Third::try_from(*(t - r).pitch()); + let tf2 = Third::try_from(*(f - t).pitch()); if rt2 == Ok(rt) && tf2 == Ok(tf) { Ok(Self { kind: PhantomData::, @@ -28,6 +40,5 @@ where } else { Err(Error::invalid_interval("Invalid interval.")) } - } } From 7fd5ee2b238d3fdccee4aa74eaf990ad24a0b0d4 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 09:25:51 -0500 Subject: [PATCH 17/21] update --- core/src/lib.rs | 5 +- core/src/macros.rs | 38 ++-------- core/src/macros/enums.rs | 37 ++++++++++ core/src/macros/notes.rs | 62 ++++++++++++++++ core/src/macros/pitches.rs | 6 +- core/src/notes/note.rs | 10 ++- core/src/pitch/kinds.rs | 1 - core/src/traits/notable.rs | 13 ++++ core/src/types/mod.rs | 4 +- core/src/types/qualities.rs | 139 ++++++++++++++++++++++++++++++++++++ core/src/utils.rs | 19 +++++ neo/Cargo.toml | 1 + neo/src/error.rs | 85 ++++++++++++++++++++++ neo/src/lib.rs | 3 + neo/src/triad/classes.rs | 6 ++ neo/src/triad/triad.rs | 90 ++++++++++++++++++++--- neo/src/types/factors.rs | 5 +- neo/src/types/mod.rs | 4 +- neo/tests/triad.rs | 17 +++++ rstmt/examples/basic.rs | 12 ++++ rstmt/examples/misc.rs | 34 --------- 21 files changed, 500 insertions(+), 91 deletions(-) create mode 100644 core/src/macros/enums.rs create mode 100644 core/src/macros/notes.rs create mode 100644 core/src/types/qualities.rs create mode 100644 neo/src/error.rs create mode 100644 neo/tests/triad.rs create mode 100644 rstmt/examples/basic.rs delete mode 100644 rstmt/examples/misc.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 6bb939c..263e750 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -16,13 +16,15 @@ pub use self::{ notes::Note, pitch::{IntoPitch, Pitch, PitchClass, PitchTy}, primitives::*, + utils::*, }; #[doc(inline)] -pub use self::{ops::prelude::*, traits::prelude::*, types::prelude::*}; +pub use self::{ops::prelude::*, traits::prelude::*, types::prelude::*,}; #[macro_use] pub(crate) mod macros; pub(crate) mod primitives; +pub(crate) mod utils; pub mod error; pub mod notes; @@ -39,4 +41,5 @@ pub mod prelude { pub use super::primitives::prelude::*; pub use super::traits::prelude::*; pub use super::types::prelude::*; + pub use super::utils::*; } diff --git a/core/src/macros.rs b/core/src/macros.rs index dffe948..67a9293 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -4,47 +4,17 @@ */ #![allow(unused_macros)] +#[macro_use] +pub(crate) mod enums; #[macro_use] pub(crate) mod errors; #[macro_use] pub(crate) mod intervals; #[macro_use] +pub(crate) mod notes; +#[macro_use] pub(crate) mod ops; #[macro_use] pub(crate) mod pitches; #[macro_use] pub(crate) mod seal; - -macro_rules! unit_enum { - ($(#[derive($($der:ident),*$(,)?)])? $vis:vis enum $class:ident $($rest:tt)*) => { - unit_enum!( - rename: "lowercase"; - $(#[derive($($der),*)])? - $vis enum $class $($rest)* - ); - }; - (rename: $rename:literal; $(#[derive($($der:ident),*$(,)?)])? $vis:vis enum $class:ident $($rest:tt)*) => { - - #[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - strum::AsRefStr, - strum::Display, - strum::EnumCount, - strum::EnumIs, - strum::VariantNames, - $($($der),*)? - )] - #[repr(i8)] - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = $rename))] - #[strum(serialize_all = $rename)] - $vis enum $class $($rest)* - }; - -} diff --git a/core/src/macros/enums.rs b/core/src/macros/enums.rs new file mode 100644 index 0000000..63c437c --- /dev/null +++ b/core/src/macros/enums.rs @@ -0,0 +1,37 @@ +/* + Appellation: enums + Contrib: FL03 +*/ + +macro_rules! unit_enum { + ($(#[derive($($der:ident),*$(,)?)])? $vis:vis enum $class:ident $($rest:tt)*) => { + unit_enum!( + rename: "lowercase"; + $(#[derive($($der),*)])? + $vis enum $class $($rest)* + ); + }; + (rename: $rename:literal; $(#[derive($($der:ident),*$(,)?)])? $vis:vis enum $class:ident $($rest:tt)*) => { + + #[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::VariantNames, + $($($der),*)? + )] + #[repr(i8)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = $rename))] + #[strum(serialize_all = $rename)] + $vis enum $class $($rest)* + }; +} diff --git a/core/src/macros/notes.rs b/core/src/macros/notes.rs new file mode 100644 index 0000000..11bafbf --- /dev/null +++ b/core/src/macros/notes.rs @@ -0,0 +1,62 @@ +/* + Appellation: notes + Contrib: FL03 +*/ + +macro_rules! notes { + ($($suffix:literal)? $name:ident {$($cls:ident($value:expr)),* $(,)?} ) => { + pub enum $name { + $($cls = $value),* + } + + impl AsRef for $name { + fn as_ref(&self) -> &str { + match self { + $(Self::$cls => stringify!($cls)),* + } + } + } + + impl core::fmt::Display for $name { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + + write!(f, "{}", self.as_ref()) + } + } + }; +} + +macro_rules! pitches { + (@impl $class:ident($n:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { + pub struct $class; + }; +} + + +macro_rules! impl_pitch { + ($class:ident($mud:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { + impl $class { + pub fn mud() -> i8 { + $mud + } + pub fn flat() -> i8 { + $flat + } + pub fn sharp() -> i8 { + $sharp + } + } + }; +} + +notes! { + Sample { + C(0), + D(2), + E(4), + F(5), + G(7), + A(9), + B(11) + } +} \ No newline at end of file diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index 4f20c3b..3e5d36a 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -3,11 +3,7 @@ Contrib: FL03 */ -macro_rules! pitch { - (@impl $class:ident($mud:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { - pub struct $class; - }; -} + macro_rules! pitch_class { ($(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? $(#[rename($rename:literal)])? $vis:vis enum $name:ident $($rest:tt)*) => { diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index e584e2b..52ffcc5 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -12,10 +12,16 @@ pub struct Note { } impl Note { - pub fn new(pitch: Pitch) -> Self { + pub fn new(octave: Octave, pitch: impl IntoPitch) -> Self { + Self { + octave, + pitch: pitch.into_pitch(), + } + } + pub fn from_pitch(pitch: impl IntoPitch) -> Self { Self { octave: Octave::default(), - pitch, + pitch: pitch.into_pitch(), } } /// Returns an instance of the note's octave diff --git a/core/src/pitch/kinds.rs b/core/src/pitch/kinds.rs index 57a1c2e..e5e607f 100644 --- a/core/src/pitch/kinds.rs +++ b/core/src/pitch/kinds.rs @@ -7,7 +7,6 @@ use crate::error::{Error, MusicalError}; pitch_class! { #[default(C)] - #[rename("lowercase")] pub enum Natural { C = 0, D = 2, diff --git a/core/src/traits/notable.rs b/core/src/traits/notable.rs index 9e954b0..d36db66 100644 --- a/core/src/traits/notable.rs +++ b/core/src/traits/notable.rs @@ -17,6 +17,11 @@ pub trait Notable: Copy + Sized + core::fmt::Display { } pub trait Interval { + /// Returns the interval associated with the value + fn kind(&self) -> Intervals { + Intervals::from_value(self.value()) + } + /// Returns the value associated with the interval fn value(&self) -> i8; } @@ -29,6 +34,14 @@ impl Interval for Intervals { } } +impl Notable for crate::Note { + + fn pitch(&self) -> Pitch { + self.pitch() + } + +} + impl Notable for Pitch { fn class(&self) -> Pitches { self.class() diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index fd67c4c..df13e4d 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,15 +2,17 @@ Appellation: types Contrib: FL03 */ -pub use self::{intervals::*, octave::*}; +pub use self::{intervals::*, octave::*, qualities::*}; pub mod intervals; pub mod octave; +pub mod qualities; /// pub type Tuple3 = (A, B, C); pub(crate) mod prelude { pub use super::intervals::*; + pub use super::qualities::*; pub use super::{Octave, Tuple3}; } diff --git a/core/src/types/qualities.rs b/core/src/types/qualities.rs new file mode 100644 index 0000000..b376fca --- /dev/null +++ b/core/src/types/qualities.rs @@ -0,0 +1,139 @@ +/* + Appellation: qualities + Contrib: FL03 +*/ +/// Musically speaking, an [interval quality](Quality) is used to identify the different versions of various musical +/// objects. +/// +/// There are five primary qualities: +/// - [augmented](Augmented) +/// - [diminished](Diminished) +/// - [major](Major) +/// - [minor](Minor) +/// - [perfect](Perfect) +pub trait Quality { + private!(); + + fn phantom() -> PhantomData { + PhantomData:: + } + + fn kind() -> IntervalQuality { + match Self::name() { + "Augmented" => IntervalQuality::augmented(), + "Diminished" => IntervalQuality::diminished(), + "Major" => IntervalQuality::major(), + "Minor" => IntervalQuality::minor(), + "Perfect" => IntervalQuality::perfect(), + _ => unreachable!() + } + } + + fn name() -> &'static str; + + fn is_augmented() -> bool { + false + } + + fn is_diminished() -> bool { + false + } + + fn is_perfect() -> bool { + false + } + + fn is_major() -> bool { + false + } + + fn is_minor() -> bool { + false + } +} + +macro_rules! quality { + (@impl $name:ident::$call:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize),)] + pub enum $name {} + + impl Quality for $name { + seal!(); + + fn name() -> &'static str { + stringify!($name) + } + + paste::paste! { + fn []() -> bool { + true + } + } + } + + unsafe impl Send for $name {} + + unsafe impl Sync for $name {} + }; + ($($name:ident::$call:ident),* $(,)?) => { + $( + quality!(@impl $name::$call); + )* + }; +} + +quality!( + Augmented::augmented, + Diminished::diminished, + Major::major, + Minor::minor, + Perfect::perfect +); + +use core::marker::PhantomData; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumCount, strum::EnumIs, strum::EnumIter, strum::EnumString, strum::VariantNames)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] +#[non_exhaustive] +#[strum(serialize_all = "lowercase")] +pub enum IntervalQuality { + Augmented(PhantomData), + Diminished(PhantomData), + Major(PhantomData), + Minor(PhantomData), + Perfect(PhantomData) +} + +impl IntervalQuality { + pub fn augmented() -> Self { + IntervalQuality::Augmented(PhantomData) + } + + pub fn diminished() -> Self { + IntervalQuality::Diminished(PhantomData) + } + + pub fn major() -> Self { + IntervalQuality::Major(PhantomData) + } + + pub fn minor() -> Self { + IntervalQuality::Minor(PhantomData) + } + + pub fn perfect() -> Self { + IntervalQuality::Perfect(PhantomData) + } + + pub fn from_str(value: &str) -> Option { + match value { + "Augmented" | "augmented" => Some(IntervalQuality::augmented()), + "Diminished" | "diminished" => Some(IntervalQuality::diminished()), + "Major" | "major" => Some(IntervalQuality::major()), + "Minor" | "minor" => Some(IntervalQuality::minor()), + "Perfect" | "perfect" => Some(IntervalQuality::perfect()), + _ => None + } + } +} diff --git a/core/src/utils.rs b/core/src/utils.rs index 602b6be..331dbc9 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -2,4 +2,23 @@ Appellation: utils Contrib: FL03 */ +use num::traits::{FromPrimitive, Num, Signed}; +pub fn absmod(value: T, m: T) -> T +where + T: Copy + Num + PartialOrd + Signed, +{ + let val = value % m; + if val >= T::zero() { + return val; + } + ((val + m) % m).abs() +} +/// A utilitarian function computing the absolute value of the remainder between +/// the gvien value and the number of notes in the chromatic scale. +pub fn pmodulo(value: T) -> T +where + T: Copy + FromPrimitive + Num + PartialOrd + Signed, +{ + absmod(value, T::from_i8(crate::MODULUS).unwrap()) +} \ No newline at end of file diff --git a/neo/Cargo.toml b/neo/Cargo.toml index 8e54099..893d821 100644 --- a/neo/Cargo.toml +++ b/neo/Cargo.toml @@ -46,6 +46,7 @@ num = "0.4" paste.workspace = true petgraph = "0.6" strum.workspace = true +thiserror = "1" [dependencies.serde] features = ["derive"] diff --git a/neo/src/error.rs b/neo/src/error.rs new file mode 100644 index 0000000..e987307 --- /dev/null +++ b/neo/src/error.rs @@ -0,0 +1,85 @@ +/* + Appellation: error + Contrib: FL03 +*/ + +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; + +use rstmt::{Intervals, Note, Pitch, Tuple3}; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::EnumIs, strum::VariantNames, thiserror::Error)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "PascalCase"))] +#[repr(C)] +#[strum(serialize_all = "PascalCase")] +pub enum TriadError { + #[error("InvalidPitch: {0}")] + InvalidPitch(Pitch), + #[error("Invalid Interval: {0}")] + InvalidInterval(Intervals), + #[error("Invalid Triad: {0:?}")] + InvalidTriad(Tuple3), + #[error("{0}")] + Music(#[from]rstmt::MusicalError), + #[error("{0}")] + Unknown(String), +} + +impl TriadError { + pub fn invalid_pitch(value: Pitch) -> Self { + Self::InvalidPitch(value) + } + + pub fn invalid_interval(value: Intervals) -> Self { + Self::InvalidInterval(value) + } + + pub fn invalid_triad(root: Note, third: Note, fifth: Note) -> Self { + Self::InvalidTriad((root, third, fifth)) + } + + pub fn unknown(message: impl Into) -> Self { + Self::Unknown(message.into()) + } + +} + + +impl From for TriadError { + fn from(err: Pitch) -> Self { + TriadError::InvalidPitch(err) + } +} + +impl From<(Note, Note, Note)> for TriadError { + fn from(err: (Note, Note, Note)) -> Self { + TriadError::InvalidTriad(err) + } +} + + +mod res { + #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + pub struct EResult { + pub expected: A, + pub found: B, + } + + impl EResult { + pub fn new(expected: A, found: B) -> Self { + Self { expected, found } + } + } + + impl core::fmt::Display for EResult + where + A: core::fmt::Display, + B: core::fmt::Display, + { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Expected: {}, Found: {}", self.expected, self.found) + } + } +} \ No newline at end of file diff --git a/neo/src/lib.rs b/neo/src/lib.rs index 2128ccd..ccf4df2 100644 --- a/neo/src/lib.rs +++ b/neo/src/lib.rs @@ -9,6 +9,7 @@ extern crate rstmt_core as rstmt; #[doc(inline)] pub use self::{ + error::{TriadError, TriadResult}, triad::{Triad, TriadBuilder}, types::*, utils::*, @@ -18,6 +19,7 @@ pub use self::{ pub(crate) mod macros; pub(crate) mod utils; +pub mod error; pub mod space; pub mod tonnetz; pub mod transform; @@ -25,6 +27,7 @@ pub mod triad; pub mod types; pub mod prelude { + pub use crate::error::{TriadError, TriadResult}; pub use crate::space::prelude::*; pub use crate::tonnetz::prelude::*; pub use crate::transform::prelude::*; diff --git a/neo/src/triad/classes.rs b/neo/src/triad/classes.rs index e2946e7..8493f67 100644 --- a/neo/src/triad/classes.rs +++ b/neo/src/triad/classes.rs @@ -9,6 +9,12 @@ use rstmt::{Fifth, Note, Third}; /// two additional classes: [augmented](Augmented) and [diminished](Diminished). This trait is used to determine pub trait 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. + fn phantom() -> core::marker::PhantomData { + core::marker::PhantomData:: + } fn is_valid(notes: &[Note; 3]) -> bool { Self::class().validate(notes) diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index cf03d3c..d7967e3 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -3,10 +3,12 @@ Contrib: FL03 */ use super::{Major, TriadKind}; +use crate::{Factors, TriadError}; use core::marker::PhantomData; -use rstmt::{Error, Note, Third}; +use rstmt::{Note, Third}; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Triad { pub(crate) kind: PhantomData, @@ -20,25 +22,95 @@ where pub fn new(root: Note) -> Self { let (rt, tf) = K::thirds(); let octave = root.octave(); - let t = Note::new(root.pitch() + rt.value()).with_octave(octave); - let f = Note::new(t.pitch() + tf.value()).with_octave(octave); + let t = Note::from_pitch(root.pitch() + rt.value()).with_octave(octave); + let f = Note::from_pitch(t.pitch() + tf.value()).with_octave(octave); Self { kind: PhantomData::, notes: [root, t, f], } } - pub fn from_slice(notes: [Note; 3]) -> Result { - let (rt, tf) = K::thirds(); + /// Create a new triad from a slice of notes. + pub fn from_slice(notes: [Note; 3]) -> Result { let (r, t, f) = (notes[0], notes[1], notes[2]); - let rt2 = Third::try_from(*(t - r).pitch()); - let tf2 = Third::try_from(*(f - t).pitch()); - if rt2 == Ok(rt) && tf2 == Ok(tf) { + // compute the interval between the root and the third + let a = Third::try_from(*(t - r).pitch())?; + // compute the interval between the third and the fifth + let b = Third::try_from(*(f - t).pitch())?; + if a == K::root_to_third() && b == K::third_to_fifth() { Ok(Self { kind: PhantomData::, notes, }) } else { - Err(Error::invalid_interval("Invalid interval.")) + Err(TriadError::invalid_triad(r, t, f)) + } + } + + pub fn as_slice(&self) -> &[Note; 3] { + &self.notes + } + + pub fn as_tuple(&self) -> (Note, Note, Note) { + (self.notes[0], self.notes[1], self.notes[2]) + } + + pub fn into_tuple(self) -> (Note, Note, Note) { + (self.notes[0], self.notes[1], self.notes[2]) + } + + pub fn into_slice(self) -> [Note; 3] { + self.notes + } + + pub fn root(&self) -> Note { + self.notes[0] + } + + pub fn third(&self) -> Note { + self.notes[1] + } + + pub fn fifth(&self) -> Note { + self.notes[2] + } + + pub fn reversed(&self) -> Self { + Self { + kind: PhantomData::, + notes: [self.notes[2], self.notes[1], self.notes[0]], } } + +} + + +impl core::fmt::Display for Triad +where + K: TriadKind, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.write_str(&self.notes[0].to_string())?; + f.write_str(&self.notes[1].to_string())?; + f.write_str(&self.notes[2].to_string()) + } +} + +impl core::ops::Index for Triad +where + K: TriadKind, +{ + type Output = Note; + + fn index(&self, index: Factors) -> &Self::Output { + &self.notes[index as usize] + } } + +impl core::ops::IndexMut for Triad +where + K: TriadKind, +{ + fn index_mut(&mut self, index: Factors) -> &mut Self::Output { + &mut self.notes[index as usize] + } +} \ No newline at end of file diff --git a/neo/src/types/factors.rs b/neo/src/types/factors.rs index 0cf02e5..ef5a677 100644 --- a/neo/src/types/factors.rs +++ b/neo/src/types/factors.rs @@ -46,7 +46,7 @@ use strum::IntoEnumIterator; strum::VariantNames ) )] -#[repr(u8)] +#[repr(usize)] #[strum(serialize_all = "lowercase")] pub enum ChordFactor { #[strum(serialize = "r", serialize = "root")] @@ -105,7 +105,8 @@ mod impl_factors { impl From for Factors { fn from(x: usize) -> Self { - match x % 3 { + use strum::EnumCount; + match x % Self::COUNT { 0 => Factors::Root, 1 => Factors::Third, _ => Factors::Fifth, diff --git a/neo/src/types/mod.rs b/neo/src/types/mod.rs index 05ee1cf..d0818f8 100644 --- a/neo/src/types/mod.rs +++ b/neo/src/types/mod.rs @@ -3,10 +3,10 @@ Contrib: FL03 */ #[doc(inline)] -pub use self::factors::ChordFactor; +pub use self::factors::{ChordFactor, Factors}; pub mod factors; pub(crate) mod prelude { - pub use super::factors::ChordFactor; + pub use super::factors::{ChordFactor, Factors}; } diff --git a/neo/tests/triad.rs b/neo/tests/triad.rs new file mode 100644 index 0000000..7982ae8 --- /dev/null +++ b/neo/tests/triad.rs @@ -0,0 +1,17 @@ +/* + Appellation: triad + Contrib: FL03 +*/ +extern crate rstmt_core as rstmt; +extern crate rstmt_neo as neo; + +use rstmt::Note; +use neo::triad::{Triad, Major}; + +#[test] +fn test_traid() { + let triad = Triad::::new(Note::from_pitch(0)); + assert_eq!(triad.root(), Note::from_pitch(0)); + assert_eq!(triad.third(), Note::from_pitch(4)); + assert_eq!(triad.fifth(), Note::from_pitch(7)); +} \ No newline at end of file diff --git a/rstmt/examples/basic.rs b/rstmt/examples/basic.rs new file mode 100644 index 0000000..c78acff --- /dev/null +++ b/rstmt/examples/basic.rs @@ -0,0 +1,12 @@ +/* + Appellation: misc + Contrib: FL03 +*/ +use rstmt::{absmod, pmodulo}; + +fn main() -> Result<(), Box> { + assert_eq!(11, absmod(11, 12)); + assert_ne!(dbg!(pmodulo(-17)), dbg!(-17 % 12)); + + Ok(()) +} diff --git a/rstmt/examples/misc.rs b/rstmt/examples/misc.rs deleted file mode 100644 index 344ed74..0000000 --- a/rstmt/examples/misc.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Appellation: misc - Contrib: FL03 -*/ -use utils::pmod; - -fn main() -> Result<(), Box> { - assert_eq!(11, utils::absmod(11, 12)); - assert_ne!(dbg!(pmod(-17)), dbg!(-17 % 12)); - - Ok(()) -} - -mod utils { - use num::traits::{FromPrimitive, Num, Signed}; - - pub fn absmod(value: T, m: T) -> T - where - T: Copy + Num + PartialOrd + Signed, - { - let val = value % m; - if val >= T::zero() { - return val; - } - ((val + m) % m).abs() - } - - pub fn pmod(value: T) -> T - where - T: Copy + FromPrimitive + Num + PartialOrd + Signed, - { - absmod(value, T::from_i8(12).unwrap()) - } -} From 9ff468cb989e5bbaaed722ecfdfee8655c05a046 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 13:12:17 -0500 Subject: [PATCH 18/21] update --- core/src/intervals/interval.rs | 57 ++++++++++ .../intervals.rs => intervals/kinds.rs} | 82 ++++++++------ core/src/intervals/mod.rs | 14 +++ core/src/lib.rs | 4 +- core/src/macros/intervals.rs | 4 +- core/src/macros/notes.rs | 16 ++- core/src/macros/ops.rs | 16 +-- core/src/macros/pitches.rs | 7 +- core/src/notes/note.rs | 58 ---------- core/src/ops/absmod.rs | 107 +++++++++++++++++- core/src/pitch/impls/pitch_ops.rs | 62 ++++++++++ core/src/pitch/mod.rs | 48 ++++---- core/src/pitch/pitch.rs | 48 +------- core/src/primitives.rs | 2 + core/src/traits/harmonic.rs | 27 +++++ core/src/traits/mod.rs | 4 + core/src/traits/notable.rs | 6 +- core/src/traits/symbols.rs | 8 ++ core/src/types/mod.rs | 9 +- core/src/types/qualities.rs | 45 ++++++-- core/src/types/steps.rs | 41 +++++++ core/src/utils.rs | 37 +++++- core/tests/ops.rs | 105 ++++++++++++++++- neo/src/error.rs | 27 +++-- neo/src/transform/lpr.rs | 54 ++++++++- neo/src/triad/triad.rs | 24 +++- neo/tests/triad.rs | 4 +- rstmt/examples/basic.rs | 11 +- 28 files changed, 708 insertions(+), 219 deletions(-) create mode 100644 core/src/intervals/interval.rs rename core/src/{types/intervals.rs => intervals/kinds.rs} (71%) create mode 100644 core/src/intervals/mod.rs create mode 100644 core/src/pitch/impls/pitch_ops.rs create mode 100644 core/src/traits/harmonic.rs create mode 100644 core/src/traits/symbols.rs create mode 100644 core/src/types/steps.rs diff --git a/core/src/intervals/interval.rs b/core/src/intervals/interval.rs new file mode 100644 index 0000000..068caf3 --- /dev/null +++ b/core/src/intervals/interval.rs @@ -0,0 +1,57 @@ +/* + Appellation: interval + Contrib: FL03 +*/ +use crate::Step; + +/// The number of an interval. +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::VariantArray, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] +#[non_exhaustive] +#[repr(i8)] +#[strum(serialize_all = "lowercase")] +pub enum IntervalLevel { + #[default] + Unison = 1, + Second = 2, + Third, + Fourth = 5, + Fifth, + Sixth, + Seventh, + Octave = 12, +} + + + +#[allow(dead_code)] +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Interval { + pub(crate) distance: i8, + pub(crate) level: IntervalLevel, + pub(crate) source: T, + pub(crate) step: Step, +} diff --git a/core/src/types/intervals.rs b/core/src/intervals/kinds.rs similarity index 71% rename from core/src/types/intervals.rs rename to core/src/intervals/kinds.rs index 442ee5b..8125fec 100644 --- a/core/src/types/intervals.rs +++ b/core/src/intervals/kinds.rs @@ -1,21 +1,38 @@ /* - Appellation: intervals + Appellation: kinds Contrib: FL03 */ use crate::{IntoPitch, Pitch}; -unit_enum! { - rename: "lowercase"; - #[derive(Default)] - pub enum Intervals { - #[default] - Semitone = 1, - Tone = 2, - Thirds(Third), - Fourths(Fourth), - Fifths(Fifth), - Sevenths(Seventh), - } +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumIs, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] +#[repr(i8)] +#[strum(serialize_all = "lowercase")] +pub enum Intervals { + #[default] + Semitone = 1, + Tone = 2, + Thirds(Third), + Fourths(Fourth), + Fifths(Fifth), + Sevenths(Seventh), } impl Intervals { @@ -35,7 +52,6 @@ impl Intervals { 10 => Sevenths(Seventh::Minor), 11 => Sevenths(Seventh::Major), 12 => Sevenths(Seventh::Augmented), - _ => panic!("Invalid interval value: {}", pitch.value()), } } @@ -83,28 +99,26 @@ impl Intervals { } } -impl From for Intervals { - fn from(third: Third) -> Self { - Intervals::Thirds(third) - } -} - -impl From for Intervals { - fn from(fourth: Fourth) -> Self { - Intervals::Fourths(fourth) - } -} - -impl From for Intervals { - fn from(fifth: Fifth) -> Self { - Intervals::Fifths(fifth) - } +macro_rules! impl_from_value { + (@impl $name:ident::$variant:ident($T:ty)) => { + impl From<$T> for $name { + fn from(value: $T) -> Self { + $name::$variant(value) + } + } + }; + ($($name:ident::$variant:ident($T:ty)),* $(,)?) => { + $( + impl_from_value!(@impl $name::$variant($T)); + )* + }; } -impl From for Intervals { - fn from(seventh: Seventh) -> Self { - Intervals::Sevenths(seventh) - } +impl_from_value! { + Intervals::Thirds(Third), + Intervals::Fourths(Fourth), + Intervals::Fifths(Fifth), + Intervals::Sevenths(Seventh), } interval! { diff --git a/core/src/intervals/mod.rs b/core/src/intervals/mod.rs new file mode 100644 index 0000000..7286119 --- /dev/null +++ b/core/src/intervals/mod.rs @@ -0,0 +1,14 @@ +/* + Appellation: intervals + Contrib: FL03 +*/ +#[doc(inline)] +pub use self::{kinds::*, interval::*}; + +pub(crate) mod kinds; +pub(crate) mod interval; + +pub(crate) mod prelude { + pub use super::kinds::*; + pub use super::interval::*; +} \ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs index 263e750..15172c0 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -13,13 +13,14 @@ extern crate alloc; #[doc(inline)] pub use self::{ error::{Error, MusicalError, Result}, + intervals::*, notes::Note, pitch::{IntoPitch, Pitch, PitchClass, PitchTy}, primitives::*, utils::*, }; #[doc(inline)] -pub use self::{ops::prelude::*, traits::prelude::*, types::prelude::*,}; +pub use self::{ops::prelude::*, traits::prelude::*, types::prelude::*}; #[macro_use] pub(crate) mod macros; @@ -27,6 +28,7 @@ pub(crate) mod primitives; pub(crate) mod utils; pub mod error; +pub mod intervals; pub mod notes; pub mod ops; pub mod pitch; diff --git a/core/src/macros/intervals.rs b/core/src/macros/intervals.rs index a704d81..d10a0ac 100644 --- a/core/src/macros/intervals.rs +++ b/core/src/macros/intervals.rs @@ -23,9 +23,9 @@ macro_rules! interval { enum_as!($name: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); - impl $crate::Interval for $name { + impl $crate::IntervalKind for $name { fn value(&self) -> i8 { - *self as i8 + $name::value(self) } } diff --git a/core/src/macros/notes.rs b/core/src/macros/notes.rs index 11bafbf..0fba9df 100644 --- a/core/src/macros/notes.rs +++ b/core/src/macros/notes.rs @@ -5,10 +5,19 @@ macro_rules! notes { ($($suffix:literal)? $name:ident {$($cls:ident($value:expr)),* $(,)?} ) => { - pub enum $name { - $($cls = $value),* + + paste::paste! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize),)] + pub enum $name { + $( + #[cfg_attr(feature = "serde", serde(rename = $cls))] + $cls = $value + ),* + } } + impl AsRef for $name { fn as_ref(&self) -> &str { match self { @@ -32,7 +41,6 @@ macro_rules! pitches { }; } - macro_rules! impl_pitch { ($class:ident($mud:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { impl $class { @@ -59,4 +67,4 @@ notes! { A(9), B(11) } -} \ No newline at end of file +} diff --git a/core/src/macros/ops.rs b/core/src/macros/ops.rs index def3fdb..f4a8082 100644 --- a/core/src/macros/ops.rs +++ b/core/src/macros/ops.rs @@ -4,18 +4,20 @@ */ macro_rules! impl_binop_method { - (@impl $trait:ident.$call:ident -> $out:ty) => { + (@impl $trait:ident.$call:ident($rhs:ty) -> $out:ty) => { paste::paste! { - pub fn [<$call _interval>](&self, interval: $crate::Intervals) -> $out { - let p = core::ops::$trait::$call(self.value(), interval.value()); + pub fn $call(&self, rhs: $rhs) -> $out { + let p = core::ops::$trait::$call(self.value(), rhs.value()); Self::new(p) } } }; - ($($trait:ident.$call:ident -> $out:ty),* $(,)?) => { - $( - impl_binop_method!(@impl $trait.$call -> $out); - )* + (suffix: $s:ident; $($trait:ident.$call:ident($rhs:ty) -> $out:ty),* $(,)?) => { + paste::paste! { + $( + impl_binop_method!(@impl $trait.[<$call $s>]($rhs) -> $out); + )* + } }; } diff --git a/core/src/macros/pitches.rs b/core/src/macros/pitches.rs index 3e5d36a..c5a6d78 100644 --- a/core/src/macros/pitches.rs +++ b/core/src/macros/pitches.rs @@ -4,7 +4,6 @@ */ - macro_rules! pitch_class { ($(#[derive($($derive:ident),* $(,)?)])? $(#[default($default:ident)])? $(#[rename($rename:literal)])? $vis:vis enum $name:ident $($rest:tt)*) => { pitch_class!(@impl $(#[derive($($derive),*)])? $(#[default($default)])? $(#[rename($rename)])? $vis enum $name $($rest)*); @@ -55,8 +54,8 @@ macro_rules! impl_pitch { } pub fn try_from_value(value: impl $crate::pitch::IntoPitch) -> Result> { - match $crate::PitchMod::pitchmod(&value.into_pitch()) { - $(x if *x == $value => Ok(Self::$class),)* + match $crate::PitchMod::pitchmod(&*value.into_pitch()) { + $(x if x == $value => Ok(Self::$class),)* _ => Err($crate::Error::invalid_pitch("Invalid pitch value.")) } } @@ -106,4 +105,4 @@ macro_rules! impl_pitch { } } }; -} +} \ No newline at end of file diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index 52ffcc5..c645c24 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -132,61 +132,3 @@ macro_rules! impl_std_ops { } impl_std_ops!(Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); - -macro_rules! impl_binop_assign { - (@impl $name:ident::$trait:ident.$call:ident) => {}; -} - -macro_rules! operator { - (@base $trait:ident.$call:ident($lhs:ty, $rhs:ty) -> $out:ty {$($rest:tt)*}) => { - impl core::ops::$trait<$rhs> for $lhs { - type Output = $out; - - fn $call(self, rhs: $rhs) -> Self::Output { - $($rest)* - } - } - }; - (@a $trait:ident.$call:ident($lhs:ty, $rhs:ty) -> $out:ty {$e:expr}) => { - operator!(@base $trait.$call($lhs, $rhs) -> $out {$e($lhs, $rhs)}); - }; - - (@impl $trait:ident.$call:ident($lhs:ty, $rhs:ty) -> $out:ty {$e:expr}) => { - impl core::ops::$trait<$rhs> for $lhs { - type Output = $out; - - fn $call(self, rhs: $rhs) -> Self::Output { - $e(self, rhs) - } - } - - impl<'a> core::ops::$trait<&'a $rhs> for $lhs { - type Output = $out; - - fn $call(self, rhs: &'a $rhs) -> Self::Output { - $e(self, *rhs) - } - } - - impl<'a> core::ops::$trait<$rhs> for &'a $lhs { - type Output = $out; - - fn $call(self, rhs: $rhs) -> Self::Output { - $e(*self, rhs) - } - } - - impl<'a> core::ops::$trait<&'a $rhs> for &'a $lhs { - type Output = $out; - - fn $call(self, rhs: &'a $rhs) -> Self::Output { - $e(*self, *rhs) - } - } - }; - ($($trait:ident.$call:ident(self: $lhs:ty, rhs: $rhs:ty) -> $out:ty {$e:expr}),* $(,)?) => { - $( - operator!(@impl $trait.$call($lhs, $rhs) -> $out {$e}); - )* - }; -} diff --git a/core/src/ops/absmod.rs b/core/src/ops/absmod.rs index 23847ce..a8d3ddc 100644 --- a/core/src/ops/absmod.rs +++ b/core/src/ops/absmod.rs @@ -3,8 +3,32 @@ Contrib: FL03 */ use crate::pitch::PitchTy; -use num::traits::Signed; +/// The [`PyMod`] trait, inpired by pythons `%` operator, describes a method for finding +/// the modulo between two numbers which, rather uniquely, preserves the sign of the +/// _denominator_. +/// +/// ### Example +/// +/// ```rust +/// use rstmt_core::PyMod; +/// +/// let m = 12; +/// assert_eq!(22.pymod(m), 10); +/// assert_eq!(-17.pymod(m), -5); +/// +/// ``` +pub trait PyMod { + type Output; + + fn pymod(&self, rhs: Rhs) -> Self::Output; +} + +/// This trait further generalizes [PyMod] by returning the absolute value of the result; +/// dropping all signs in the process. This method is particularly useful when working +/// in environments where the magnitude of the result is more important than the sign. +/// +/// ### Example pub trait AbsMod { type Output; @@ -20,24 +44,95 @@ pub trait PitchMod { impl PitchMod for S where - S: AbsMod, + S: PyMod, { - type Output = >::Output; + type Output = >::Output; fn pitchmod(&self) -> Self::Output { - self.absmod(Self::MOD) + self.pymod(Self::MOD) } } /* ************* Implementations ************* */ +use core::ops::{Add, Rem}; +use num::traits::{Num, Signed}; + +pub trait Floor { + type Output; + + fn floor(self) -> Self::Output; +} + +pub trait FloorDiv { + type Output; + + fn floor_div(self, rhs: Rhs) -> Self::Output; +} + +macro_rules! impl_floor_div { + (@float $t:ty) => { + impl FloorDiv for $t where $t: ::core::ops::Div { + type Output = O; + + fn floor_div(self, rhs: T) -> Self::Output { + (self / rhs).floor() + } + } + }; + (@impl f32) => { + impl_floor_div!(@float f32); + }; + (@impl f64) => { + impl_floor_div!(@float f64); + }; + (@impl $t:ty) => { + impl FloorDiv for $t where $t: ::core::ops::Div { + type Output = O; + + fn floor_div(self, rhs: T) -> Self::Output { + self / rhs + } + } + }; + ($($t:ty),*) => { + $( + impl_floor_div!(@impl $t); + )* + }; +} + +impl_floor_div!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64); + +impl PyMod for T +where + T: Copy + Num + PartialOrd + Signed +{ + type Output = T; + + fn pymod(&self, y: T) -> Self::Output { + crate::pymod(*self, y) + } +} + +// impl AbsMod for A +// where +// A: PyMod, +// C: Signed, +// { +// type Output = C; + +// fn absmod(&self, rhs: B) -> Self::Output { +// self.pymod(rhs).abs() +// } +// } impl AbsMod for A where - A: Copy + core::ops::Rem + core::ops::Add, + A: Copy + Add + Rem, B: Copy, - C: core::ops::Add + core::ops::Rem + Signed, + C: Add + Rem + Signed, { type Output = C; diff --git a/core/src/pitch/impls/pitch_ops.rs b/core/src/pitch/impls/pitch_ops.rs new file mode 100644 index 0000000..2f69eb8 --- /dev/null +++ b/core/src/pitch/impls/pitch_ops.rs @@ -0,0 +1,62 @@ +/* + Appellation: pitch_ops + Contrib: FL03 +*/ +use crate::{Pitch, PitchMod, PitchTy}; +use num::{Num, One, Zero}; + +macro_rules! impl_interval_method { + (@impl $trait:ident.$call:ident) => { + paste::paste! { + pub fn [<$call _interval>](&self, rhs: T) -> Self where i8: ::core::ops::$trait { + let p = ::core::ops::$trait::$call(self.value(), rhs); + Self::new(p) + } + } + }; + ($($trait:ident.$call:ident),* $(,)?) => { + $( + impl_interval_method!(@impl $trait.$call); + )* + }; +} + +impl Pitch { + impl_interval_method!(Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); +} + +wrapper_ops!(Pitch::: Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); + +wrapper_unop!(Pitch impls Neg.neg, Not.not); + +impl PitchMod for Pitch { + type Output = Self; + + fn pitchmod(&self) -> Self::Output { + Self(self.0.pitchmod()) + } +} + +impl One for Pitch { + fn one() -> Self { + Self(PitchTy::one()) + } +} + +impl Zero for Pitch { + fn zero() -> Self { + Self(PitchTy::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +impl Num for Pitch { + type FromStrRadixErr = ::FromStrRadixErr; + + fn from_str_radix(s: &str, radix: u32) -> Result { + PitchTy::from_str_radix(s, radix).map(Self) + } +} diff --git a/core/src/pitch/mod.rs b/core/src/pitch/mod.rs index c8dbeef..d94e355 100644 --- a/core/src/pitch/mod.rs +++ b/core/src/pitch/mod.rs @@ -8,15 +8,20 @@ pub use self::{kinds::*, pitch::Pitch}; pub(crate) mod kinds; pub(crate) mod pitch; +mod impls { + mod pitch_ops; +} + pub(crate) mod prelude { pub use super::kinds::{Flat, Natural, Pitches, Sharp}; pub use super::pitch::Pitch; - pub use super::{Accidentals, PitchClass, PitchTy}; + pub use super::{IntoPitch, PitchClass, PitchTy}; } /// A type alias for an integer representing a particular pitch of a note pub type PitchTy = i8; +/// A trait for converting a type into a [Pitch](Pitch) instance. pub trait IntoPitch { fn into_pitch(self) -> Pitch; } @@ -29,18 +34,6 @@ pub trait PitchClass { fn pitch(&self) -> PitchTy; } -pub trait Sharps { - private!(); -} - -pub trait Flats { - private!(); -} - -pub trait Accidentals: PitchClass { - private!(); -} - /* ************* Implementations ************* */ @@ -53,17 +46,28 @@ where } } -impl Flats for Flat { - seal!(); +pub enum SymbolCount { + Double = 2, + Single = 1, } +pub struct FlatSymbol(SymbolCount); -impl Sharps for Sharp { - seal!(); -} -impl Accidentals for Sharp { - seal!(); +pub struct SharpSym(SymbolCount); + +impl SharpSym { + pub fn symbol(&self) -> &str { + match self.0 { + SymbolCount::Double => "♯♯", + SymbolCount::Single => "♯", + } + } } -impl Accidentals for Flat { - seal!(); +impl FlatSymbol { + pub fn symbol(&self) -> &str { + match self.0 { + SymbolCount::Double => "♭♭", + SymbolCount::Single => "♭", + } + } } diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 608feb8..158fa43 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -5,8 +5,11 @@ use super::{PitchTy, Pitches}; use crate::PitchMod; +/// A [pitch](Pitch) is a discrete tone with an individual frequency that may be +/// classified as a [pitch class](Pitches). #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[repr(transparent)] pub struct Pitch(pub(crate) PitchTy); impl Pitch { @@ -14,7 +17,7 @@ impl Pitch { Self(pitch.into().pitchmod()) } /// Returns the absolute value of the remainder of the pitch divided by the modulus. - pub fn modulo(&self) -> Self { + pub fn abs(&self) -> Self { self.pitchmod() } /// Returns a new instance of the class representing the given pitch. @@ -33,14 +36,6 @@ impl Pitch { pub fn value(&self) -> PitchTy { self.0 } - - impl_binop_method! { - Add.add -> Self, - Sub.sub -> Self, - Mul.mul -> Self, - Div.div -> Self, - Rem.rem -> Self - } } impl AsRef for Pitch { @@ -111,41 +106,6 @@ impl core::fmt::UpperExp for Pitch { } } -impl PitchMod for Pitch { - type Output = Self; - - fn pitchmod(&self) -> Self::Output { - Self(self.0.pitchmod()) - } -} - -wrapper_ops!(Pitch::: Add.add, Div.div, Mul.mul, Rem.rem, Sub.sub); -wrapper_unop!(Pitch impls Neg.neg, Not.not); - -impl num::One for Pitch { - fn one() -> Self { - Self(PitchTy::one()) - } -} - -impl num::Zero for Pitch { - fn zero() -> Self { - Self(PitchTy::zero()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - -impl num::Num for Pitch { - type FromStrRadixErr = ::FromStrRadixErr; - - fn from_str_radix(s: &str, radix: u32) -> Result { - PitchTy::from_str_radix(s, radix).map(Self) - } -} - impl From for Pitch { fn from(pitch: PitchTy) -> Self { Self(pitch) diff --git a/core/src/primitives.rs b/core/src/primitives.rs index aee271a..0fbae98 100644 --- a/core/src/primitives.rs +++ b/core/src/primitives.rs @@ -9,6 +9,8 @@ pub(crate) mod prelude { } pub mod consts { + pub const FLAT_SUFFIX: &str = "♭"; + pub const SHARP_SUFFIX: &str = "♯"; /// Used to describe the total number of notes considered pub const MODULUS: i8 = 12; /// A semitone is half of a tone diff --git a/core/src/traits/harmonic.rs b/core/src/traits/harmonic.rs new file mode 100644 index 0000000..0ba05a3 --- /dev/null +++ b/core/src/traits/harmonic.rs @@ -0,0 +1,27 @@ +/* + Appellation: harmonic + Contrib: FL03 +*/ +use num::traits::{Num, NumOps}; + +pub trait Frequency { + fn period(self) -> T; +} +pub struct Freq { + hz: T, +} + +impl Freq +where + T: Num + NumOps, +{ + pub fn period(self) -> T { + T::one() / self.hz + } +} + +pub trait Tone {} + +pub trait Harmonic { + type Tone: Tone; +} diff --git a/core/src/traits/mod.rs b/core/src/traits/mod.rs index 4f87ab3..f806860 100644 --- a/core/src/traits/mod.rs +++ b/core/src/traits/mod.rs @@ -5,8 +5,12 @@ #[doc(inline)] pub use self::notable::Notable; +pub mod harmonic; pub mod notable; +pub mod symbols; pub(crate) mod prelude { + pub use super::harmonic::*; pub use super::notable::*; + pub use super::symbols::*; } diff --git a/core/src/traits/notable.rs b/core/src/traits/notable.rs index d36db66..af7eee7 100644 --- a/core/src/traits/notable.rs +++ b/core/src/traits/notable.rs @@ -16,7 +16,7 @@ pub trait Notable: Copy + Sized + core::fmt::Display { fn pitch(&self) -> Pitch; } -pub trait Interval { +pub trait IntervalKind { /// Returns the interval associated with the value fn kind(&self) -> Intervals { Intervals::from_value(self.value()) @@ -28,18 +28,16 @@ pub trait Interval { /* ************* Implementations ************* */ -impl Interval for Intervals { +impl IntervalKind for Intervals { fn value(&self) -> i8 { self.value() } } impl Notable for crate::Note { - fn pitch(&self) -> Pitch { self.pitch() } - } impl Notable for Pitch { diff --git a/core/src/traits/symbols.rs b/core/src/traits/symbols.rs new file mode 100644 index 0000000..b79f617 --- /dev/null +++ b/core/src/traits/symbols.rs @@ -0,0 +1,8 @@ +/* + Appellation: symbols + Contrib: FL03 +*/ + +pub trait Symbolic { + fn symbol(&self) -> &str; +} diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index df13e4d..ed8fe9d 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,17 +2,18 @@ Appellation: types Contrib: FL03 */ -pub use self::{intervals::*, octave::*, qualities::*}; +pub use self::{octave::*, qualities::*, steps::*}; -pub mod intervals; pub mod octave; pub mod qualities; +pub mod steps; /// pub type Tuple3 = (A, B, C); pub(crate) mod prelude { - pub use super::intervals::*; + pub use super::octave::Octave; pub use super::qualities::*; - pub use super::{Octave, Tuple3}; + pub use super::steps::*; + pub use super::Tuple3; } diff --git a/core/src/types/qualities.rs b/core/src/types/qualities.rs index b376fca..a353db8 100644 --- a/core/src/types/qualities.rs +++ b/core/src/types/qualities.rs @@ -4,9 +4,9 @@ */ /// Musically speaking, an [interval quality](Quality) is used to identify the different versions of various musical /// objects. -/// +/// /// There are five primary qualities: -/// - [augmented](Augmented) +/// - [augmented](Augmented) /// - [diminished](Diminished) /// - [major](Major) /// - [minor](Minor) @@ -25,7 +25,7 @@ pub trait Quality { "Major" => IntervalQuality::major(), "Minor" => IntervalQuality::minor(), "Perfect" => IntervalQuality::perfect(), - _ => unreachable!() + _ => unreachable!(), } } @@ -61,8 +61,12 @@ macro_rules! quality { impl Quality for $name { seal!(); + fn kind() -> IntervalQuality { + IntervalQuality::$call() + } + fn name() -> &'static str { - stringify!($name) + stringify!($call) } paste::paste! { @@ -93,8 +97,30 @@ quality!( use core::marker::PhantomData; -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::Display, strum::EnumCount, strum::EnumIs, strum::EnumIter, strum::EnumString, strum::VariantNames)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "lowercase"))] +/// This enum provides a more streamlined means of working with the implemented [Quality] types. +/// Primarily, it provides simple creation routines for otherwise unitializable types. +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::EnumString, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] #[non_exhaustive] #[strum(serialize_all = "lowercase")] pub enum IntervalQuality { @@ -102,10 +128,13 @@ pub enum IntervalQuality { Diminished(PhantomData), Major(PhantomData), Minor(PhantomData), - Perfect(PhantomData) + Perfect(PhantomData), } impl IntervalQuality { + pub fn new() -> Self { + Q::kind() + } pub fn augmented() -> Self { IntervalQuality::Augmented(PhantomData) } @@ -133,7 +162,7 @@ impl IntervalQuality { "Major" | "major" => Some(IntervalQuality::major()), "Minor" | "minor" => Some(IntervalQuality::minor()), "Perfect" | "perfect" => Some(IntervalQuality::perfect()), - _ => None + _ => None, } } } diff --git a/core/src/types/steps.rs b/core/src/types/steps.rs new file mode 100644 index 0000000..a096570 --- /dev/null +++ b/core/src/types/steps.rs @@ -0,0 +1,41 @@ +/* + Appellation: steps + Contrib: FL03 +*/ + +/// A step between notes. +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::VariantArray, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "lowercase") +)] +#[non_exhaustive] +#[repr(i8)] +#[strum(serialize_all = "lowercase")] +pub enum Step { + #[default] + /// A semitone step. + Half = 1, + /// A tone step. + Whole = 2, + /// A tritone step. + Tritone = 3, +} \ No newline at end of file diff --git a/core/src/utils.rs b/core/src/utils.rs index 331dbc9..8ce47b7 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -4,6 +4,41 @@ */ use num::traits::{FromPrimitive, Num, Signed}; +pub fn amod(value: T, m: T) -> T +where + T: Copy + Num + PartialOrd + Signed, +{ + let val = value % m; + if val >= T::zero() { + return val; + } + val + m +} + +pub fn pymod(lhs: T, rhs: T) -> T where T: Copy + Num + PartialOrd { + let r = lhs % rhs; + if (r < T::zero() && rhs > T::zero()) || (r > T::zero() && rhs < T::zero()) { + r + rhs + } else { + r + } +} + +/// Computes the `absmod` of the value given a modulus. +/// The modulo is calculated before determining if +/// the value is positive or negative. +/// If negative, the value is reflected by adding the modulus +/// before taking the modulo again. +/// +/// # Example +/// +/// ```rust +/// use rstmt_core::absmod; +/// +/// assert_eq!(absmod(22, 12), 22 % 12); +/// assert_ne!(absmod(-17, 12), (-17 % 12).abs()); +/// assert_eq!(absmod(-17, 12), 7); +/// ``` pub fn absmod(value: T, m: T) -> T where T: Copy + Num + PartialOrd + Signed, @@ -21,4 +56,4 @@ where T: Copy + FromPrimitive + Num + PartialOrd + Signed, { absmod(value, T::from_i8(crate::MODULUS).unwrap()) -} \ No newline at end of file +} diff --git a/core/tests/ops.rs b/core/tests/ops.rs index db15c2d..47d6f65 100644 --- a/core/tests/ops.rs +++ b/core/tests/ops.rs @@ -4,12 +4,109 @@ */ extern crate rstmt_core as rstmt; -use rstmt::ops::{AbsMod, PitchMod}; +const MOD: i8 = 12; + +lazy_static::lazy_static! { + static ref CASES: Vec> = PAIRS.iter().map(|(i, j)| res::Pair::new(*i, *j)).collect(); +} +const PAIRS: [(i8, i8); 6] = [(5, 5), (-17, 7), (0, 0), (12, 0), (-12, 0), (-13, 11)]; #[test] fn test_absmod() { - let pairs = [(5, 12), (-5, 12), (0, 12), (12, 12), (13, 12), (-13, 12)]; - for (i, j) in pairs.iter() { - assert_eq!(i.absmod(*j), i.pitchmod()); + use rstmt::ops::AbsMod; + let cases = PAIRS; + for (i, j) in cases.iter() { + assert_eq!(i.absmod(MOD), *j); + } +} + +#[test] +fn test_pymod() { + use rstmt::ops::PyMod; + assert_eq!(5.pymod(MOD), 5); + assert_eq!((-17).pymod(MOD), 7); + assert_eq!(17.pymod(-MOD), -7); +} + +pub mod res { + #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + pub struct Pair(pub A, pub B); + + impl Pair { + pub fn new(a: A, b: B) -> Self { + Self(a, b) + } + + pub fn as_tuple(&self) -> (&A, &B) { + (&self.0, &self.1) + } + + pub fn into_tuple(self) -> (A, B) { + (self.0, self.1) + } + + pub const fn a(&self) -> &A { + &self.0 + } + + pub const fn b(&self) -> &B { + &self.1 + } + + pub fn is_eq(&self) -> bool + where + A: PartialEq, + { + self.a() == self.b() + } + + pub fn is_ne(&self) -> bool + where + A: PartialEq, + { + self.a() != self.b() + } + + pub fn is_ge(&self) -> bool + where + A: PartialOrd, + { + self.a() >= self.b() + } + + pub fn is_gt(&self) -> bool + where + A: PartialOrd, + { + self.a() > self.b() + } + + pub fn is_le(&self) -> bool + where + A: PartialOrd, + { + self.a() <= self.b() + } + + pub fn is_lt(&self) -> bool + where + A: PartialOrd, + { + self.a() < self.b() + } + + pub fn is_default(&self) -> bool + where + A: PartialEq + Default, + { + self.a() == &A::default() + } + } + + impl From<(A, B)> for Pair { + fn from(pair: (A, B)) -> Self { + Self(pair.0, pair.1) + } } } diff --git a/neo/src/error.rs b/neo/src/error.rs index e987307..4401f07 100644 --- a/neo/src/error.rs +++ b/neo/src/error.rs @@ -9,8 +9,24 @@ pub type TriadResult = core::result::Result; use rstmt::{Intervals, Note, Pitch, Tuple3}; -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::AsRefStr, strum::EnumIs, strum::VariantNames, thiserror::Error)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(rename_all = "PascalCase"))] +#[derive( + Clone, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::EnumIs, + strum::VariantNames, + thiserror::Error, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "PascalCase") +)] #[repr(C)] #[strum(serialize_all = "PascalCase")] pub enum TriadError { @@ -21,7 +37,7 @@ pub enum TriadError { #[error("Invalid Triad: {0:?}")] InvalidTriad(Tuple3), #[error("{0}")] - Music(#[from]rstmt::MusicalError), + Music(#[from] rstmt::MusicalError), #[error("{0}")] Unknown(String), } @@ -42,10 +58,8 @@ impl TriadError { pub fn unknown(message: impl Into) -> Self { Self::Unknown(message.into()) } - } - impl From for TriadError { fn from(err: Pitch) -> Self { TriadError::InvalidPitch(err) @@ -58,7 +72,6 @@ impl From<(Note, Note, Note)> for TriadError { } } - mod res { #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -82,4 +95,4 @@ mod res { write!(f, "Expected: {}, Found: {}", self.expected, self.found) } } -} \ No newline at end of file +} diff --git a/neo/src/transform/lpr.rs b/neo/src/transform/lpr.rs index f1dfa01..47d9a36 100644 --- a/neo/src/transform/lpr.rs +++ b/neo/src/transform/lpr.rs @@ -2,9 +2,57 @@ Appellation: lpr Contrib: FL03 */ +use crate::Triad; +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::VariantArray, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "UPPERCASE") +)] +#[repr(u8)] +#[strum(serialize_all = "UPPERCASE")] pub enum LPR { - L, - P, - R, + #[cfg_attr(feature = "serde", serde(alias = "l", alias = "leading"))] + L = 0, + #[cfg_attr(feature = "serde", serde(alias = "p", alias = "parallel"))] + P = 1, + #[cfg_attr(feature = "serde", serde(alias = "r", alias = "relative"))] + R = 2, +} + +impl LPR { + /// A functional constructor for the `leading` transformation + pub fn leading() -> Self { + LPR::L + } + /// A functional constructor for the `parallel` transformation + pub fn parallel() -> Self { + LPR::P + } + /// A functional constructor for the `relative` transformation + pub fn relative() -> Self { + LPR::R + } + /// Apply the current transformation to the given triad; + /// returns a [Triad] with the new notes and classification + pub fn apply(&self, _triad: &Triad) -> Triad { + unimplemented!() + } } diff --git a/neo/src/triad/triad.rs b/neo/src/triad/triad.rs index d7967e3..aa7f9de 100644 --- a/neo/src/triad/triad.rs +++ b/neo/src/triad/triad.rs @@ -80,10 +80,8 @@ where notes: [self.notes[2], self.notes[1], self.notes[0]], } } - } - impl core::fmt::Display for Triad where K: TriadKind, @@ -113,4 +111,24 @@ where fn index_mut(&mut self, index: Factors) -> &mut Self::Output { &mut self.notes[index as usize] } -} \ No newline at end of file +} + +impl core::ops::Index> for Triad +where + K: TriadKind, +{ + type Output = [Note]; + + fn index(&self, index: core::ops::Range) -> &Self::Output { + &self.notes[index.start as usize..index.end as usize] + } +} + +impl core::ops::IndexMut> for Triad +where + K: TriadKind, +{ + fn index_mut(&mut self, index: core::ops::Range) -> &mut Self::Output { + &mut self.notes[index.start as usize..index.end as usize] + } +} diff --git a/neo/tests/triad.rs b/neo/tests/triad.rs index 7982ae8..9edd91a 100644 --- a/neo/tests/triad.rs +++ b/neo/tests/triad.rs @@ -5,8 +5,8 @@ extern crate rstmt_core as rstmt; extern crate rstmt_neo as neo; +use neo::triad::{Major, Triad}; use rstmt::Note; -use neo::triad::{Triad, Major}; #[test] fn test_traid() { @@ -14,4 +14,4 @@ fn test_traid() { assert_eq!(triad.root(), Note::from_pitch(0)); assert_eq!(triad.third(), Note::from_pitch(4)); assert_eq!(triad.fifth(), Note::from_pitch(7)); -} \ No newline at end of file +} diff --git a/rstmt/examples/basic.rs b/rstmt/examples/basic.rs index c78acff..9cd2622 100644 --- a/rstmt/examples/basic.rs +++ b/rstmt/examples/basic.rs @@ -7,6 +7,15 @@ use rstmt::{absmod, pmodulo}; fn main() -> Result<(), Box> { assert_eq!(11, absmod(11, 12)); assert_ne!(dbg!(pmodulo(-17)), dbg!(-17 % 12)); - + println!("{}", pymod(17, -12)); Ok(()) } + +fn pymod(lhs: T, rhs: T) -> T where T: Copy + num::Num + PartialOrd { + let r = lhs % rhs; + if (r < T::zero() && rhs > T::zero()) || (r > T::zero() && rhs < T::zero()) { + r + rhs + } else { + r + } +} \ No newline at end of file From bdd4098f80195a3f9a5054064255c3a6d4002cf5 Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 13:21:37 -0500 Subject: [PATCH 19/21] update --- core/src/lib.rs | 3 ++- core/src/macros/notes.rs | 3 ++- core/src/notes/note.rs | 8 ++++++-- core/src/traits/mod.rs | 1 + core/src/traits/symbols.rs | 6 ++++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 15172c0..93831be 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -15,7 +15,7 @@ pub use self::{ error::{Error, MusicalError, Result}, intervals::*, notes::Note, - pitch::{IntoPitch, Pitch, PitchClass, PitchTy}, + pitch::{IntoPitch, Pitch, Pitches, PitchTy}, primitives::*, utils::*, }; @@ -37,6 +37,7 @@ pub mod types; pub mod prelude { pub use super::error::prelude::*; + pub use super::intervals::prelude::*; pub use super::notes::prelude::*; pub use super::ops::prelude::*; pub use super::pitch::prelude::*; diff --git a/core/src/macros/notes.rs b/core/src/macros/notes.rs index 0fba9df..c3e5fad 100644 --- a/core/src/macros/notes.rs +++ b/core/src/macros/notes.rs @@ -2,9 +2,10 @@ Appellation: notes Contrib: FL03 */ +#![allow(unused)] macro_rules! notes { - ($($suffix:literal)? $name:ident {$($cls:ident($value:expr)),* $(,)?} ) => { + ($(suffix: $suffix:literal;)? $name:ident {$($cls:ident($value:expr)),* $(,)?} ) => { paste::paste! { #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] diff --git a/core/src/notes/note.rs b/core/src/notes/note.rs index c645c24..1511525 100644 --- a/core/src/notes/note.rs +++ b/core/src/notes/note.rs @@ -2,7 +2,7 @@ Appellation: note Contrib: FL03 */ -use crate::{IntoPitch, Octave, Pitch}; +use crate::{IntoPitch, Octave, Pitch, Pitches}; #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -24,6 +24,10 @@ impl Note { pitch: pitch.into_pitch(), } } + pub fn class(&self) -> Pitches { + self.pitch.class() + } + /// Returns an instance of the note's octave pub fn octave(&self) -> Octave { self.octave @@ -63,7 +67,7 @@ impl Note { impl core::fmt::Display for Note { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{}.{}", self.pitch, self.octave) + write!(f, "{}.{}", self.class(), self.octave) } } diff --git a/core/src/traits/mod.rs b/core/src/traits/mod.rs index f806860..f7a3270 100644 --- a/core/src/traits/mod.rs +++ b/core/src/traits/mod.rs @@ -5,6 +5,7 @@ #[doc(inline)] pub use self::notable::Notable; +#[doc(hidden)] pub mod harmonic; pub mod notable; pub mod symbols; diff --git a/core/src/traits/symbols.rs b/core/src/traits/symbols.rs index b79f617..f98ec37 100644 --- a/core/src/traits/symbols.rs +++ b/core/src/traits/symbols.rs @@ -3,6 +3,12 @@ Contrib: FL03 */ +/// [Symbolic] is used to describe a type that has some +/// symbolic representation. +/// +/// pub trait Symbolic { fn symbol(&self) -> &str; } + + From 0c06709fefc53630b9a9667db30b5f010e89e28f Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 13:35:01 -0500 Subject: [PATCH 20/21] update --- core/src/ops/absmod.rs | 45 ----------------- core/src/ops/arith.rs | 84 +++++++++++++++++++++++++++++++ core/src/ops/mod.rs | 2 + core/src/pitch/impls/pitch_ops.rs | 4 +- core/src/pitch/pitch.rs | 9 ++-- 5 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 core/src/ops/arith.rs diff --git a/core/src/ops/absmod.rs b/core/src/ops/absmod.rs index a8d3ddc..0604f04 100644 --- a/core/src/ops/absmod.rs +++ b/core/src/ops/absmod.rs @@ -59,51 +59,6 @@ where use core::ops::{Add, Rem}; use num::traits::{Num, Signed}; -pub trait Floor { - type Output; - - fn floor(self) -> Self::Output; -} - -pub trait FloorDiv { - type Output; - - fn floor_div(self, rhs: Rhs) -> Self::Output; -} - -macro_rules! impl_floor_div { - (@float $t:ty) => { - impl FloorDiv for $t where $t: ::core::ops::Div { - type Output = O; - - fn floor_div(self, rhs: T) -> Self::Output { - (self / rhs).floor() - } - } - }; - (@impl f32) => { - impl_floor_div!(@float f32); - }; - (@impl f64) => { - impl_floor_div!(@float f64); - }; - (@impl $t:ty) => { - impl FloorDiv for $t where $t: ::core::ops::Div { - type Output = O; - - fn floor_div(self, rhs: T) -> Self::Output { - self / rhs - } - } - }; - ($($t:ty),*) => { - $( - impl_floor_div!(@impl $t); - )* - }; -} - -impl_floor_div!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64); impl PyMod for T where diff --git a/core/src/ops/arith.rs b/core/src/ops/arith.rs new file mode 100644 index 0000000..b250321 --- /dev/null +++ b/core/src/ops/arith.rs @@ -0,0 +1,84 @@ +/* + Appellation: arith + Contrib: FL03 +*/ + +pub trait Floor { + type Output; + + fn floor(self) -> Self::Output; +} + +pub trait FloorDiv { + type Output; + + fn floor_div(self, rhs: Rhs) -> Self::Output; +} + + +macro_rules! impl_floor { + (@base $t:ty) => { + impl Floor for $t { + type Output = $t; + + fn floor(self) -> Self::Output { + <$t>::floor(self) + } + } + }; + (@impl f32) => { + impl_floor!(@base f32); + }; + (@impl f64) => { + impl_floor!(@base f64); + }; + (@impl $t:ty) => { + impl Floor for $t { + type Output = $t; + + fn floor(self) -> Self::Output { + self + } + } + }; + ($($t:ty),*) => { + $( + impl_floor!(@impl $t); + )* + }; +} + +macro_rules! impl_floor_div { + (@float $t:ty) => { + impl FloorDiv for $t where $t: ::core::ops::Div { + type Output = O; + + fn floor_div(self, rhs: T) -> Self::Output { + (self / rhs).floor() + } + } + }; + (@impl f32) => { + impl_floor_div!(@float f32); + }; + (@impl f64) => { + impl_floor_div!(@float f64); + }; + (@impl $t:ty) => { + impl FloorDiv for $t where $t: ::core::ops::Div { + type Output = O; + + fn floor_div(self, rhs: T) -> Self::Output { + self / rhs + } + } + }; + ($($t:ty),*) => { + $( + impl_floor_div!(@impl $t); + )* + }; +} + +impl_floor!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64); +impl_floor_div!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64); \ No newline at end of file diff --git a/core/src/ops/mod.rs b/core/src/ops/mod.rs index 1c59191..8de19eb 100644 --- a/core/src/ops/mod.rs +++ b/core/src/ops/mod.rs @@ -6,9 +6,11 @@ pub use self::prelude::*; pub mod absmod; +pub mod arith; pub mod distance; pub(crate) mod prelude { pub use super::absmod::*; + pub use super::arith::*; pub use super::distance::*; } diff --git a/core/src/pitch/impls/pitch_ops.rs b/core/src/pitch/impls/pitch_ops.rs index 2f69eb8..8da7226 100644 --- a/core/src/pitch/impls/pitch_ops.rs +++ b/core/src/pitch/impls/pitch_ops.rs @@ -2,7 +2,7 @@ Appellation: pitch_ops Contrib: FL03 */ -use crate::{Pitch, PitchMod, PitchTy}; +use crate::{Pitch, PitchMod, PitchTy, PyMod}; use num::{Num, One, Zero}; macro_rules! impl_interval_method { @@ -33,7 +33,7 @@ impl PitchMod for Pitch { type Output = Self; fn pitchmod(&self) -> Self::Output { - Self(self.0.pitchmod()) + Self(self.0.pymod(Self::MOD)) } } diff --git a/core/src/pitch/pitch.rs b/core/src/pitch/pitch.rs index 158fa43..14e2d64 100644 --- a/core/src/pitch/pitch.rs +++ b/core/src/pitch/pitch.rs @@ -3,7 +3,7 @@ Contrib: FL03 */ use super::{PitchTy, Pitches}; -use crate::PitchMod; +use crate::PyMod; /// A [pitch](Pitch) is a discrete tone with an individual frequency that may be /// classified as a [pitch class](Pitches). @@ -13,12 +13,15 @@ use crate::PitchMod; pub struct Pitch(pub(crate) PitchTy); impl Pitch { + const MOD: PitchTy = crate::MODULUS; + pub fn new(pitch: impl Into) -> Self { - Self(pitch.into().pitchmod()) + let val: PitchTy = pitch.into(); + Self(val.pymod(Self::MOD)) } /// Returns the absolute value of the remainder of the pitch divided by the modulus. pub fn abs(&self) -> Self { - self.pitchmod() + Self(self.0.pymod(Self::MOD).abs()) } /// Returns a new instance of the class representing the given pitch. pub fn class(&self) -> Pitches { From a0cf697dae5668c493939d79f59a8f59b392205f Mon Sep 17 00:00:00 2001 From: Joe McCain III Date: Mon, 15 Jul 2024 13:51:43 -0500 Subject: [PATCH 21/21] update --- core/src/macros/notes.rs | 65 ---------------------------------------- 1 file changed, 65 deletions(-) diff --git a/core/src/macros/notes.rs b/core/src/macros/notes.rs index c3e5fad..6b3765b 100644 --- a/core/src/macros/notes.rs +++ b/core/src/macros/notes.rs @@ -4,68 +4,3 @@ */ #![allow(unused)] -macro_rules! notes { - ($(suffix: $suffix:literal;)? $name:ident {$($cls:ident($value:expr)),* $(,)?} ) => { - - paste::paste! { - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize),)] - pub enum $name { - $( - #[cfg_attr(feature = "serde", serde(rename = $cls))] - $cls = $value - ),* - } - } - - - impl AsRef for $name { - fn as_ref(&self) -> &str { - match self { - $(Self::$cls => stringify!($cls)),* - } - } - } - - impl core::fmt::Display for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - - write!(f, "{}", self.as_ref()) - } - } - }; -} - -macro_rules! pitches { - (@impl $class:ident($n:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { - pub struct $class; - }; -} - -macro_rules! impl_pitch { - ($class:ident($mud:expr, flat: $flat:expr, sharp: $sharp:expr) ) => { - impl $class { - pub fn mud() -> i8 { - $mud - } - pub fn flat() -> i8 { - $flat - } - pub fn sharp() -> i8 { - $sharp - } - } - }; -} - -notes! { - Sample { - C(0), - D(2), - E(4), - F(5), - G(7), - A(9), - B(11) - } -}