From 0d5580a95a291dc6f2575413922c00865f910bbf Mon Sep 17 00:00:00 2001 From: FL03 Date: Sat, 10 Jan 2026 09:36:34 -0600 Subject: [PATCH 01/17] v0.0.13 Signed-off-by: FL03 --- Cargo.lock | 10 +++++----- Cargo.toml | 12 ++++++------ default.nix | 2 +- flake.nix | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4c58b0..2fbec1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -889,7 +889,7 @@ dependencies = [ [[package]] name = "rstmt" -version = "0.0.12" +version = "0.0.13" dependencies = [ "anyhow", "criterion", @@ -903,7 +903,7 @@ dependencies = [ [[package]] name = "rstmt-core" -version = "0.0.12" +version = "0.0.13" dependencies = [ "anyhow", "contained", @@ -931,7 +931,7 @@ dependencies = [ [[package]] name = "rstmt-macros" -version = "0.0.12" +version = "0.0.13" dependencies = [ "proc-macro2", "quote", @@ -940,7 +940,7 @@ dependencies = [ [[package]] name = "rstmt-nrt" -version = "0.0.12" +version = "0.0.13" dependencies = [ "anyhow", "getrandom", @@ -968,7 +968,7 @@ dependencies = [ [[package]] name = "rstmt-traits" -version = "0.0.12" +version = "0.0.13" dependencies = [ "hashbrown 0.16.1", "num-complex", diff --git a/Cargo.toml b/Cargo.toml index 2d89c56..ebfae41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,14 +29,14 @@ license = "Apache-2.0" readme = "README.md" repository = "https://github.com/FL03/rstmt.git" rust-version = "1.85.0" -version = "0.0.12" +version = "0.0.13" [workspace.dependencies] -rstmt = { default-features = false, path = "rstmt", version = "0.0.12" } -rstmt-core = { default-features = false, path = "core", version = "0.0.12" } -rstmt-macros = { path = "macros", version = "0.0.12" } -rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.12" } -rstmt-traits = { default-features = false, path = "traits", version = "0.0.12" } +rstmt = { default-features = false, path = "rstmt", version = "0.0.13" } +rstmt-core = { default-features = false, path = "core", version = "0.0.13" } +rstmt-macros = { path = "macros", version = "0.0.13" } +rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.13" } +rstmt-traits = { default-features = false, path = "traits", version = "0.0.13" } # custom contained = { default-features = false, features = ["macros"], version = "0.2.3" } rshyper = { default-features = false, features = ["macros"], version = "0.1.9" } diff --git a/default.nix b/default.nix index 254c70a..55ddf11 100644 --- a/default.nix +++ b/default.nix @@ -26,7 +26,7 @@ let }; common = { - version = "0.0.12"; + version = "0.0.13"; src = self; # ./.; cargoLock = { diff --git a/flake.nix b/flake.nix index beca457..31c6ade 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ { packages.default = rustPlatform.buildRustPackage { pname = "rstmt"; - version = "0.0.12"; + version = "0.0.13"; src = self; # "./."; # If Cargo.lock doesn't exist yet, remove or comment out this block: cargoLock = { From 7e1ff8a9b88e4856bd5b317cb9e055256a6b3f71 Mon Sep 17 00:00:00 2001 From: FL03 Date: Sat, 10 Jan 2026 09:40:23 -0600 Subject: [PATCH 02/17] update Cargo.toml Signed-off-by: FL03 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ebfae41..922c849 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ opt-level = 2 overflow-checks = true panic = "abort" rpath = true -strip = "symbols" +strip = false [profile.release] codegen-units = 16 From df5fbd0b3a26b6d167643e47c924ff6a0d587805 Mon Sep 17 00:00:00 2001 From: FL03 Date: Sat, 10 Jan 2026 10:10:02 -0600 Subject: [PATCH 03/17] update Signed-off-by: FL03 --- core/src/octave/impl_octave.rs | 7 +--- core/src/octave/impl_octave_repr.rs | 8 +--- nrt/src/impls/impl_triad_base.rs | 22 ++++++----- nrt/src/impls/impl_triad_ext.rs | 4 +- nrt/src/impls/impl_triad_repr.rs | 18 ++++----- nrt/src/triad.rs | 60 +++++++++++++++++++++++------ nrt/src/types/lpr.rs | 2 +- 7 files changed, 77 insertions(+), 44 deletions(-) diff --git a/core/src/octave/impl_octave.rs b/core/src/octave/impl_octave.rs index 42fb03d..4a57d26 100644 --- a/core/src/octave/impl_octave.rs +++ b/core/src/octave/impl_octave.rs @@ -2,12 +2,9 @@ appellation: impl_octave authors: @FL03 */ -use crate::octave::{Octave, RawOctave}; +use crate::octave::Octave; -impl Octave -where - T: RawOctave, -{ +impl Octave { /// a functional constructor for [`Octave`], essentially wrapping the given value pub const fn new(octave: T) -> Self { Octave(octave) diff --git a/core/src/octave/impl_octave_repr.rs b/core/src/octave/impl_octave_repr.rs index b35da83..1e4fdbc 100644 --- a/core/src/octave/impl_octave_repr.rs +++ b/core/src/octave/impl_octave_repr.rs @@ -2,11 +2,9 @@ appellation: impl_octave_repr authors: @FL03 */ -use crate::octave::{Octave, RawOctave}; +use crate::octave::Octave; impl Octave<&T> -where - T: RawOctave, { /// returns a new instance of the [`Octave`] with a cloned instance of the current value.alloc pub fn cloned(&self) -> Octave @@ -25,8 +23,6 @@ where } impl Octave<&mut T> -where - T: RawOctave, { /// returns a new instance of the [`Octave`] with a cloned instance of the current value.alloc pub fn cloned(&self) -> Octave @@ -45,8 +41,6 @@ where } impl Octave<*const T> -where - T: RawOctave, { /// returns a new instance of the [`Octave`] with a copied instance of the current value pub const fn copied(&self) -> Octave diff --git a/nrt/src/impls/impl_triad_base.rs b/nrt/src/impls/impl_triad_base.rs index b9fe506..86b9275 100644 --- a/nrt/src/impls/impl_triad_base.rs +++ b/nrt/src/impls/impl_triad_base.rs @@ -7,7 +7,7 @@ use crate::triad::TriadBase; use crate::traits::{TriadRepr, TriadReprMut, TriadType}; use crate::types::LPR; -use num_traits::{Float, FromPrimitive, ToPrimitive}; +use num_traits::{Float, FromPrimitive, ToPrimitive, Zero}; use rstmt_core::{Octave, PitchMod, Transform}; impl TriadBase @@ -16,17 +16,17 @@ where S: TriadRepr, { /// Returns a new instance of the [`TriadBase`] with the given chord and kind. - pub const fn new(chord: S, class: K) -> Self { + pub fn new(chord: S, class: K) -> Self where T: Zero { Self { chord, class, - octave: Octave(0), + octave: Octave::zero(), } } /// Create a new triad from a root pitch and class pub fn from_root_with_class(root: T, class: K) -> Self where - T: Copy + FromPrimitive + PitchMod + core::ops::Add, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { // generate the chord factors from the root and class let chord = [ @@ -37,7 +37,7 @@ where Self { class, chord: S::from_arr(chord), - octave: rstmt_core::Octave(0), + octave: Octave::zero(), } } #[inline] @@ -65,10 +65,11 @@ where pub const fn class_mut(&mut self) -> &mut K { &mut self.class } - pub const fn octave(&self) -> Octave { - self.octave + /// returns a reference to the octave of the triad. + pub const fn octave(&self) -> &Octave { + &self.octave } - pub const fn octave_mut(&mut self) -> &mut Octave { + pub const fn octave_mut(&mut self) -> &mut Octave { &mut self.octave } /// returns a reference to the root note of the triad. @@ -149,7 +150,7 @@ where } } #[inline] - pub fn with_octave(self, octave: Octave) -> Self { + pub fn with_octave(self, octave: Octave) -> Self { Self { octave, ..self } } /// consumes the triad and returns the chord and class @@ -175,10 +176,11 @@ where /// computes the centroid of the triad pub fn centroid(&self) -> Option<[U; 2]> where + T: Copy + ToPrimitive, U: Float + FromPrimitive + ToPrimitive + core::iter::Sum, S: Clone + IntoIterator, { - let y = U::from_isize(*self.octave)?; + let y = U::from(*self.octave().get())?; let x = self.chord().clone().into_iter().sum::() / U::from_u8(3)?; Some([x, y]) } diff --git a/nrt/src/impls/impl_triad_ext.rs b/nrt/src/impls/impl_triad_ext.rs index f220cd8..bb2280e 100644 --- a/nrt/src/impls/impl_triad_ext.rs +++ b/nrt/src/impls/impl_triad_ext.rs @@ -7,7 +7,7 @@ use crate::triad::TriadBase; use crate::traits::{Relative, TriadRepr, TriadReprMut, TriadType}; use crate::types::{Factors, LPR}; -use num_traits::{FromPrimitive, One}; +use num_traits::{FromPrimitive, One, Zero}; use rstmt_core::{PitchMod, Transform}; impl Transform for TriadBase @@ -44,6 +44,7 @@ impl core::fmt::Display for TriadBase where S: TriadRepr + core::fmt::Debug, K: TriadType + core::fmt::Display, + T: core::fmt::Display, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( @@ -215,6 +216,7 @@ where impl From<(S, K)> for TriadBase where S: TriadRepr, + T: Zero, K: TriadType, { fn from((chord, class): (S, K)) -> Self { diff --git a/nrt/src/impls/impl_triad_repr.rs b/nrt/src/impls/impl_triad_repr.rs index 3e52f2c..d7e8c23 100644 --- a/nrt/src/impls/impl_triad_repr.rs +++ b/nrt/src/impls/impl_triad_repr.rs @@ -7,7 +7,7 @@ use crate::triad::TriadBase; use crate::traits::TriadRepr; use crate::types::{LPR, Triads}; -use num_traits::{Float, FromPrimitive, Num, ToPrimitive}; +use num_traits::{Float, FromPrimitive, Num, ToPrimitive, Zero}; use rstmt_core::traits::{PitchMod, Transform}; use rstmt_core::{Augmented, Diminished, Major, Minor}; @@ -19,7 +19,7 @@ where /// augmented triad. pub fn augmented(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Augmented) } @@ -33,7 +33,7 @@ where /// diminished triad. pub fn diminished(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Diminished) } @@ -47,7 +47,7 @@ where /// triad. pub fn major(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Major) } @@ -61,7 +61,7 @@ where /// triad. pub fn minor(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Minor) } @@ -78,21 +78,21 @@ where // /// creates a new diminished triad from the given root // pub fn diminished(root: T) -> Self // where - // T: Copy + FromPrimitive + core::ops::Add + PitchMod, + // T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, // { // Self::from_root_with_class(root, TriadClass::Diminished) // } // /// Create a new major triad from the given root // pub fn major(root: T) -> Self // where - // T: Copy + FromPrimitive + core::ops::Add + PitchMod, + // T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, // { // Self::from_root_with_class(root, TriadClass::Major) // } // /// creates a new minor triad from the given root // pub fn minor(root: T) -> Self // where - // T: Copy + FromPrimitive + core::ops::Add + PitchMod, + // T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, // { // Self::from_root_with_class(root, TriadClass::Minor) // } @@ -106,7 +106,7 @@ where let note = p.into_aspn(); let px = U::from_usize(note.class().pmod()).unwrap(); let py = U::from_isize(*note.octave()).unwrap(); - let y = U::from_isize(*self.octave).unwrap(); + let y = U::from(*self.octave).unwrap(); let [v0, v1, v2] = self.chord().map(|n| U::from(n).unwrap()); let d00 = v0 * v0 + y * y; diff --git a/nrt/src/triad.rs b/nrt/src/triad.rs index 12768f3..f9f75a5 100644 --- a/nrt/src/triad.rs +++ b/nrt/src/triad.rs @@ -8,22 +8,60 @@ //! //! # Overview //! +//! A triad is defined to be a chord, composed of three notes, each of which maintain certain +//! intervallic relationships with one another. More specifically, the distance between the +//! first and second as well as the second and third notes is defined to be a major or minor +//! third, whilst the distance between the first and third notes is some variant of a _fifth_. +//! +//! These compositional contraints lead to four possible triadic chord types, namely: major, +//! minor, augmented, and diminished. Each of these chord types can be represented as a 3-tuple +//! containing the pitch classes of each note within the chord. For example, a C-major triad +//! can be represented as the tuple (0, 4, 7), where `0` represents the root note C, `4` the +//! major third E, and `7` as the perfect fifth G. +//! +//! Additionally, triads may be transformed using any one of three transformations defined as: +//! leading, parallel, and relative. The behavior of these transformations is determined by the +//! interval between the first two notes of the triad, i.e. a major or minor third. Each of +//! these transformations is capable of being chained together into discrete and continuous +//! sequences or spaces and is its own inverse. This means that any consecutive applications of +//! any one particular transformation simply reverts the object back into its original state. +//! +//! That being said, augmented and diminished traids _can_ be transformed, however, the exact +//! nature of their responses is not as predictable as with major / minor triads. +//! +//! ## Examples +//! +//! ### _Basic Usage_ +//! +//! Initialize a C-major triad and verify its composition +//! //! ```rust //! use rstmt_nrt::Triad; -//! -//! // initialize a c-major triad: (0, 4, 7) +//! // create new triad //! let c_major = Triad::major(0); //! // verify the composition //! assert_eq! { c_major, [0, 4, 7] } -//! assert! { c_major.is_major() && !c_major.is_minor() } //! ``` -//! -//! # Background -//! -//! A triad is defined to be a chord, composed of three notes, each of which maintain certain -//! intervallic relationships with one another. More specifically, the distance between the -//! first and second as well as the second and third notes is defined to be a major or minor -//! third, whilst the distance between the first and third notes is some variant of a _fifth_. +//! +//! ### _Transformations_ +//! +//! Initialize a C-major triad before applying each of the three transformations: +//! +//! ```rust +//! use rstmt_nrt::Triad; +//! // create new triad +//! let c_major = Triad::major(0); +//! // apply leading transformation +//! assert_eq! { c_major.leading(), Triad::minor(4) } +//! // apply parallel transformation +//! assert_eq! { c_major.parallel(), Triad::minor(0) } +//! // apply relative transformation +//! assert_eq! { c_major.relative(), Triad::minor(9) } +//! // confirm inverses +//! assert_eq! { c_major.leading().leading(), c_major } +//! assert_eq! { c_major.parallel().parallel(), c_major } +//! assert_eq! { c_major.relative().relative(), c_major } +//! ``` //! //! # References //! @@ -58,7 +96,7 @@ where { pub(crate) chord: S, pub(crate) class: K, - pub(crate) octave: Octave, + pub(crate) octave: Octave, } #[cfg(test)] diff --git a/nrt/src/types/lpr.rs b/nrt/src/types/lpr.rs index 491c42b..4943138 100644 --- a/nrt/src/types/lpr.rs +++ b/nrt/src/types/lpr.rs @@ -157,7 +157,7 @@ where Ok(TriadBase { chord: S::from_arr(notes), class: ::rel(&rhs.class()), - octave: rhs.octave(), + octave: *rhs.octave(), }) } } From 3218a8df439e7a5d8b2f2d33ca9ceb77e1ab2f0d Mon Sep 17 00:00:00 2001 From: FL03 Date: Sat, 10 Jan 2026 14:20:49 -0600 Subject: [PATCH 04/17] update Signed-off-by: FL03 --- core/src/freq/impl_freq.rs | 4 +-- core/src/lib.rs | 16 ++++++++-- core/src/note.rs | 4 +-- core/src/note/impl_note_base.rs | 5 ++-- core/src/note/impl_note_ext.rs | 12 ++++---- core/src/octave/impl_octave_repr.rs | 9 ++---- core/src/pitch/impls/impl_pclass.rs | 30 +++++++++---------- core/src/pitch/impls/impl_pclass_ext.rs | 26 ++++++++-------- core/src/pitch/impls/impl_pclass_ops.rs | 10 +++---- core/src/pitch/pitch_class.rs | 30 +++++++++++++++++-- core/src/pitch/traits/accidental.rs | 40 +++++++++++++++---------- core/src/pitch/traits/classifiers.rs | 4 +-- nrt/src/impls/impl_triad_base.rs | 5 +++- nrt/src/lib.rs | 10 +------ nrt/src/tonnetz.rs | 15 +++++----- nrt/src/tonnetz/impl_hyper_tonnetz.rs | 10 +++---- nrt/src/triad.rs | 34 ++++++++++----------- 17 files changed, 151 insertions(+), 113 deletions(-) diff --git a/core/src/freq/impl_freq.rs b/core/src/freq/impl_freq.rs index 75af5b9..54a090f 100644 --- a/core/src/freq/impl_freq.rs +++ b/core/src/freq/impl_freq.rs @@ -4,7 +4,7 @@ */ use super::{Frequency, RawFrequency}; use crate::consts::A4_FREQUENCY; -use crate::pitch::{PitchClass, RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClass, RawPitchClass}; use crate::utils::{classify_freq_with_scale, compute_freq_of_pitch}; use num_traits::{Float, FromPrimitive, ToPrimitive}; use rstmt_traits::ClassifyBy; @@ -44,7 +44,7 @@ where pub fn from_pitch_class(class: PitchClass, root: T) -> Self where P: RawPitchClass, - K: RawAccidental, + K: Accidental, T: RawFrequency + Float + FromPrimitive, { let semitones = class.get().index(); diff --git a/core/src/lib.rs b/core/src/lib.rs index bfe4ef8..5ea2ca6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,7 +1,19 @@ -#![crate_name = "rstmt_core"] //! This crate provides the core functionality for the `rstmt` library, including [`Aspn`], //! [`NoteBase`], [`Pitch`], and [`Octave`]. Additionally, the crate provides a host of //! other primitives and utilities designed to manifest and manipulate musical concepts. +//! +//! ## Overview +//! +//! The core modules focus on establishing the basic primitives and interfaces needed to +//! represent musical notes, pitches, octaves, and related concepts. These modules +//! provide the foundational building blocks for more complex musical structures and +//! operations. +//! +//! These modules are designed to be efficient, flexible, and correct, ensuring conversions +//! between different representations are handled seamlessly. For example, _any_ [`PitchClass`] +//! is able to be converted directly into a [`Frequency`]. +#![crate_name = "rstmt_core"] +#![crate_type = "lib"] #![allow( clippy::derivable_impls, clippy::len_without_is_empty, @@ -15,7 +27,7 @@ clippy::upper_case_acronyms )] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(allocator_api))] +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // compiler check #[cfg(not(any(feature = "std", feature = "alloc")))] compile_error! { "either the \"std\" or \"alloc\" feature must be enabled" } diff --git a/core/src/note.rs b/core/src/note.rs index 3aca9c3..8b91aae 100644 --- a/core/src/note.rs +++ b/core/src/note.rs @@ -9,7 +9,7 @@ mod impl_note_ext; mod impl_note_repr; use crate::octave::Octave; -use crate::pitch::{self, PitchClass, RawAccidental, RawPitchClass}; +use crate::pitch::{self, Accidental, PitchClass, RawPitchClass}; /// The [`AsAspn`] trait is used to convert a reference into a [`Aspn`] pub trait AsAspn { @@ -49,7 +49,7 @@ pub struct Aspn { pub struct NoteBase

::Tag> where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { pub(crate) class: PitchClass, pub(crate) octave: Octave, diff --git a/core/src/note/impl_note_base.rs b/core/src/note/impl_note_base.rs index 9c3fc0c..4446640 100644 --- a/core/src/note/impl_note_base.rs +++ b/core/src/note/impl_note_base.rs @@ -5,12 +5,12 @@ */ use super::NoteBase; use crate::octave::Octave; -use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawPitchClass}; impl NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { /// constructs a new [`NoteBase`] instance pub const fn new(class: PitchClass, octave: Octave) -> Self { @@ -20,7 +20,6 @@ where pub fn from_octave(octave: Octave) -> Self where P: PitchClassRepr, - K: Accidental, { Self { class: PitchClass::new(), diff --git a/core/src/note/impl_note_ext.rs b/core/src/note/impl_note_ext.rs index fa132c7..637ceba 100644 --- a/core/src/note/impl_note_ext.rs +++ b/core/src/note/impl_note_ext.rs @@ -5,12 +5,12 @@ */ use crate::note::NoteBase; use crate::octave::Octave; -use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawPitchClass}; impl core::fmt::Debug for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(self.aspn().as_str()) @@ -20,7 +20,7 @@ where impl core::fmt::Display for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(self.aspn().as_str()) @@ -30,7 +30,7 @@ where impl PartialEq for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &str) -> bool { self.aspn() == other @@ -40,7 +40,7 @@ where impl PartialEq<&str> for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &&str) -> bool { self.aspn() == *other @@ -50,7 +50,7 @@ where impl core::str::FromStr for NoteBase where P: PitchClassRepr, - K: Accidental, + K: Accidental + core::str::FromStr,

::Err: core::fmt::Debug, { type Err = crate::error::Error; diff --git a/core/src/octave/impl_octave_repr.rs b/core/src/octave/impl_octave_repr.rs index 1e4fdbc..c52c15e 100644 --- a/core/src/octave/impl_octave_repr.rs +++ b/core/src/octave/impl_octave_repr.rs @@ -4,8 +4,7 @@ */ use crate::octave::Octave; -impl Octave<&T> -{ +impl Octave<&T> { /// returns a new instance of the [`Octave`] with a cloned instance of the current value.alloc pub fn cloned(&self) -> Octave where @@ -22,8 +21,7 @@ impl Octave<&T> } } -impl Octave<&mut T> -{ +impl Octave<&mut T> { /// returns a new instance of the [`Octave`] with a cloned instance of the current value.alloc pub fn cloned(&self) -> Octave where @@ -40,8 +38,7 @@ impl Octave<&mut T> } } -impl Octave<*const T> -{ +impl Octave<*const T> { /// returns a new instance of the [`Octave`] with a copied instance of the current value pub const fn copied(&self) -> Octave where diff --git a/core/src/pitch/impls/impl_pclass.rs b/core/src/pitch/impls/impl_pclass.rs index 935e4e1..03ce318 100644 --- a/core/src/pitch/impls/impl_pclass.rs +++ b/core/src/pitch/impls/impl_pclass.rs @@ -4,32 +4,32 @@ Contrib: @FL03 */ use crate::freq::{Frequency, RawFrequency}; -use crate::pitch::{Flat, Natural, PitchClass, RawAccidental, RawPitchClass, Sharp}; +use crate::pitch::{Accidental, Flat, Natural, PitchClass, RawPitchClass, Sharp}; use num_traits::{Float, FromPrimitive}; impl PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { - pub fn new() -> Self - where - P: Default, - K: Default, - { + pub fn new() -> Self { Self { - class: P::default(), - kind: K::default(), + class: P::new(), + kind: K::new(), } } - pub fn from_class(class: P) -> Self - where - K: Default, - { + pub fn from_class(class: P) -> Self { Self { class, - kind: K::default(), + kind: K::new(), + } + } + + pub fn from_kind(kind: K) -> Self { + Self { + class: P::new(), + kind, } } /// returns a pointer to the class @@ -44,7 +44,7 @@ where pub fn as_frequency(&self) -> Frequency where P: RawPitchClass, - K: RawAccidental, + K: Accidental, T: RawFrequency + Float + FromPrimitive, { Frequency::from_class_on_a4(self.get().index()) diff --git a/core/src/pitch/impls/impl_pclass_ext.rs b/core/src/pitch/impls/impl_pclass_ext.rs index b9acd7c..626b453 100644 --- a/core/src/pitch/impls/impl_pclass_ext.rs +++ b/core/src/pitch/impls/impl_pclass_ext.rs @@ -5,12 +5,12 @@ */ use crate::error::Error; use crate::pitch::pitch_class::PitchClass; -use crate::pitch::traits::{Accidental, PitchClassRepr, RawAccidental, RawPitchClass}; +use crate::pitch::traits::{Accidental, PitchClassRepr, RawPitchClass}; impl core::fmt::Debug for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if self.is_natural() { @@ -24,7 +24,7 @@ where impl core::fmt::Display for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if self.is_natural() { @@ -38,7 +38,7 @@ where impl core::str::FromStr for PitchClass where P: PitchClassRepr, - K: Accidental, + K: Accidental + core::str::FromStr, { type Err = Error; @@ -65,7 +65,7 @@ where if P::is(value) { return Ok(Self { class: P::new(), - kind: K::default(), + kind: K::new(), }); } Err(Error::MismatchedPitchClasses(value,

::IDX)) @@ -75,7 +75,7 @@ where impl AsRef for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn as_ref(&self) -> &isize { self.get().as_ref() @@ -85,7 +85,7 @@ where impl AsRef for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn as_ref(&self) -> &str { self.get().name() @@ -95,7 +95,7 @@ where impl core::borrow::Borrow for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn borrow(&self) -> &isize { self.get().borrow() @@ -105,7 +105,7 @@ where impl core::ops::Deref for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { type Target = P; @@ -117,21 +117,21 @@ where unsafe impl Send for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { } unsafe impl Sync for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { } impl PartialEq for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &isize) -> bool { self.get().index() == *other @@ -141,7 +141,7 @@ where impl PartialEq> for isize where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &PitchClass) -> bool { *self == other.get().index() diff --git a/core/src/pitch/impls/impl_pclass_ops.rs b/core/src/pitch/impls/impl_pclass_ops.rs index 021e589..929354f 100644 --- a/core/src/pitch/impls/impl_pclass_ops.rs +++ b/core/src/pitch/impls/impl_pclass_ops.rs @@ -4,7 +4,7 @@ Contrib: @FL03 */ use crate::pitch::pitch_class::PitchClass; -use crate::pitch::traits::{RawAccidental, RawPitchClass}; +use crate::pitch::traits::{Accidental, RawPitchClass}; use rstmt_traits::PitchMod; /// Add two pitch-classes producing a wrapped semitone count in the tonal space. @@ -17,9 +17,9 @@ use rstmt_traits::PitchMod; impl core::ops::Add> for PitchClass where T1: RawPitchClass, - A1: RawAccidental, + A1: Accidental, T2: RawPitchClass, - A2: RawAccidental, + A2: Accidental, { type Output = isize; @@ -31,9 +31,9 @@ where impl core::ops::Sub> for PitchClass where T1: RawPitchClass, - A1: RawAccidental, + A1: Accidental, T2: RawPitchClass, - A2: RawAccidental, + A2: Accidental, { /// Subtract two pitch-classes producing a wrapped signed semitone interval. type Output = isize; diff --git a/core/src/pitch/pitch_class.rs b/core/src/pitch/pitch_class.rs index 46cdfaf..3091d59 100644 --- a/core/src/pitch/pitch_class.rs +++ b/core/src/pitch/pitch_class.rs @@ -3,7 +3,18 @@ Created At: 2025.12.20:09:31:05 Contrib: @FL03 */ -use crate::pitch::{RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClassRepr, RawPitchClass}; +use rstmt_traits::PitchMod; + +pub trait IntoPitchClass +where + P: RawPitchClass, + K: Accidental, +{ + fn into_pitch_class(self) -> PitchClass; + + private! {} +} /// The [`PitchClass`] implementations works to generically define the structure for a pitch /// class. This is accomplished through the use of two type parameters: `N`, which defines the @@ -22,7 +33,7 @@ use crate::pitch::{RawAccidental, RawPitchClass}; pub struct PitchClass

::Tag> where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { pub(crate) class: P, pub(crate) kind: K, @@ -59,3 +70,18 @@ classes! { A::, B::, } + +impl IntoPitchClass for isize +where + P: PitchClassRepr, + K: Accidental + Default, +{ + seal! {} + + fn into_pitch_class(self) -> PitchClass { + match self.pmod() { + x if x == P::IDX => PitchClass::from_class(P::new()), + _ => panic!("cannot convert {self} into pitch class"), + } + } +} diff --git a/core/src/pitch/traits/accidental.rs b/core/src/pitch/traits/accidental.rs index 7f537dc..d6588da 100644 --- a/core/src/pitch/traits/accidental.rs +++ b/core/src/pitch/traits/accidental.rs @@ -6,21 +6,28 @@ /// [`Accidental`] is a sealed marker trait used to designate various _kinds_ of musical notes, /// i.e., sharp, flat, natural, etc. -pub trait RawAccidental: - 'static + AsRef + Send + Sync + core::fmt::Debug + core::fmt::Display +pub trait Accidental +where + Self: 'static + AsRef + Send + Sync + core::fmt::Debug + core::fmt::Display, { private! {} + fn new() -> Self + where + Self: Sized; + + #[allow(clippy::should_implement_trait)] + fn from_str(s: &str) -> Result + where + Self: Sized + core::str::FromStr, + { + s.parse::() + } fn name(&self) -> &str; fn symbol(&self) -> char; } -pub trait Accidental: RawAccidental -where - Self: Default + core::str::FromStr, -{ -} /* ************* Implementations ************* */ @@ -51,9 +58,13 @@ macro_rules! accidental { } } - impl $crate::pitch::RawAccidental for $name { + impl $crate::pitch::Accidental for $name { seal! {} + fn new() -> Self { + Self + } + fn name(&self) -> &str { self.name() } @@ -63,11 +74,6 @@ macro_rules! accidental { } } - impl $crate::pitch::Accidental for $name { - - - } - impl AsRef for $name { fn as_ref(&self) -> &str { stringify!($name) @@ -126,9 +132,13 @@ impl Natural { } } -impl crate::pitch::RawAccidental for Natural { +impl Accidental for Natural { seal! {} + fn new() -> Self { + Self::new() + } + fn name(&self) -> &str { self.name() } @@ -138,8 +148,6 @@ impl crate::pitch::RawAccidental for Natural { } } -impl crate::pitch::Accidental for Natural {} - impl AsRef for Natural { fn as_ref(&self) -> &str { "Natural" diff --git a/core/src/pitch/traits/classifiers.rs b/core/src/pitch/traits/classifiers.rs index 2481a9c..19e17cb 100644 --- a/core/src/pitch/traits/classifiers.rs +++ b/core/src/pitch/traits/classifiers.rs @@ -3,7 +3,7 @@ Created At: 2025.12.21:09:08:08 Contrib: @FL03 */ -use crate::pitch::RawAccidental; +use crate::pitch::Accidental; use rstmt_traits::PitchMod; /// The [`RawPitchClass`] is a sealed trait used to define raw pitch class types. @@ -19,7 +19,7 @@ where + core::fmt::Debug + core::fmt::Display, { - type Tag: RawAccidental; + type Tag: Accidental; private! {} diff --git a/nrt/src/impls/impl_triad_base.rs b/nrt/src/impls/impl_triad_base.rs index 86b9275..3635419 100644 --- a/nrt/src/impls/impl_triad_base.rs +++ b/nrt/src/impls/impl_triad_base.rs @@ -16,7 +16,10 @@ where S: TriadRepr, { /// Returns a new instance of the [`TriadBase`] with the given chord and kind. - pub fn new(chord: S, class: K) -> Self where T: Zero { + pub fn new(chord: S, class: K) -> Self + where + T: Zero, + { Self { chord, class, diff --git a/nrt/src/lib.rs b/nrt/src/lib.rs index 715e2b7..30268b0 100644 --- a/nrt/src/lib.rs +++ b/nrt/src/lib.rs @@ -8,11 +8,6 @@ //! //! ## Background //! -//! Before diving into the implementation details, it is important to understand the concepts -//! at hand and the theory behind them. -//! -//! ### Neo-Riemannian Theory -//! //! The neo-riemannian theory is a loose collection of musical theories focused on the triad. //! Research in the field has been ongoing for over a century, culminating in the successful //! generalization of the tonnetz, a geometric representation of the triad and its @@ -35,10 +30,6 @@ //! // chain together two parallel transformations to confirm inversion //! assert_eq! { triad.parallel().parallel(), triad } //! ``` -//! -//! ## Resources -//! -//! - [The Generalized Tonnetz](https://dmitri.mycpanel.princeton.edu/tonnetzes.pdf) #![allow( clippy::derivable_impls, clippy::len_without_is_empty, @@ -52,6 +43,7 @@ clippy::upper_case_acronyms )] #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // compiler check #[cfg(not(any(feature = "std", feature = "alloc")))] compile_error! { "either the \"std\" or \"alloc\" feature must be enabled" } diff --git a/nrt/src/tonnetz.rs b/nrt/src/tonnetz.rs index 9fd0e08..b096188 100644 --- a/nrt/src/tonnetz.rs +++ b/nrt/src/tonnetz.rs @@ -4,16 +4,17 @@ Contrib: @FL03 */ //! The tonnetz is a conceptual lattice representation of tonal space first proposed in 1739 by -//! Leonhard Euler as a means of visualizing relationships between triads +//! Leonhard Euler as a means of visualizing relationships between triads. Over the centuries, +//! researchers have expanded upon Euler's initial concept, eventually culminating in the +//! development of the long-awaited _**generalized tonnetz**_. //! -//! ## Resources +//! # Resources //! -//! Listed below are some useful resources for understanding the tonnetz and its potential -//! applications in both music theroy as well as computer science: +//! Listed below are some useful resources for understanding the tonnetz, its history, and its +//! applications in music theory and beyond. //! //! - [The Generalized Tonnetz](https://dmitri.mycpanel.princeton.edu/tonnetzes.pdf) //! - [Wikipedia: Tonnetz](https://en.wikipedia.org/wiki/Tonnetz) -//! mod impl_hyper_tonnetz; use crate::triad::TriadBase; @@ -23,7 +24,7 @@ use rshyper::{EdgeId, RawIndex, UnHyperMap}; use rspace_traits::RawSpace; /// a type alias for a [`HashMap`] that maps an [`EdgeId`] to a [`TriadBase`] -pub(crate) type TriadMap::Elem, Ix = usize> = +pub(crate) type EdgeMap::Elem, Ix = usize> = HashMap, TriadBase>; /// a type alias for a [`HashMap`] that maps an [`EdgeId`] to a [`HashMap`] of [`LPR`] /// transformations. @@ -49,7 +50,7 @@ where /// a hypergraph representing the tonal space pub(crate) graph: NoteGraph, /// Maps EdgeIds to Triad for efficient access - pub(crate) triads: TriadMap, + pub(crate) triads: EdgeMap, /// Tracks adjacency between triads via transformations pub(crate) transformations: LprMap, } diff --git a/nrt/src/tonnetz/impl_hyper_tonnetz.rs b/nrt/src/tonnetz/impl_hyper_tonnetz.rs index 293a0f8..320acc7 100644 --- a/nrt/src/tonnetz/impl_hyper_tonnetz.rs +++ b/nrt/src/tonnetz/impl_hyper_tonnetz.rs @@ -3,7 +3,7 @@ Created At: 2025.12.28:10:45:40 Contrib: @FL03 */ -use crate::tonnetz::{HyperTonnetz, LprMap, NoteGraph, TriadMap}; +use crate::tonnetz::{EdgeMap, HyperTonnetz, LprMap, NoteGraph}; use crate::traits::{TriadRepr, TriadType}; use crate::triad::TriadBase; use core::hash::Hash; @@ -25,7 +25,7 @@ where { HyperTonnetz { graph: NoteGraph::new(), - triads: TriadMap::new(), + triads: EdgeMap::new(), transformations: LprMap::new(), } } @@ -50,11 +50,11 @@ where &mut self.graph } /// returns a reference to the triads map - pub const fn triads(&self) -> &TriadMap { + pub const fn triads(&self) -> &EdgeMap { &self.triads } /// returns a mutable reference to the triads map - pub const fn triads_mut(&mut self) -> &mut TriadMap { + pub const fn triads_mut(&mut self) -> &mut EdgeMap { &mut self.triads } /// returns a reference to the transformations map @@ -81,7 +81,7 @@ where } #[inline] /// update the triad map - pub fn set_triads(&mut self, triads: TriadMap) { + pub fn set_triads(&mut self, triads: EdgeMap) { self.triads = triads } #[inline] diff --git a/nrt/src/triad.rs b/nrt/src/triad.rs index f9f75a5..8782bd1 100644 --- a/nrt/src/triad.rs +++ b/nrt/src/triad.rs @@ -12,29 +12,29 @@ //! intervallic relationships with one another. More specifically, the distance between the //! first and second as well as the second and third notes is defined to be a major or minor //! third, whilst the distance between the first and third notes is some variant of a _fifth_. -//! -//! These compositional contraints lead to four possible triadic chord types, namely: major, +//! +//! These compositional contraints lead to four possible triadic chord types, namely: major, //! minor, augmented, and diminished. Each of these chord types can be represented as a 3-tuple -//! containing the pitch classes of each note within the chord. For example, a C-major triad -//! can be represented as the tuple (0, 4, 7), where `0` represents the root note C, `4` the +//! containing the pitch classes of each note within the chord. For example, a C-major triad +//! can be represented as the tuple (0, 4, 7), where `0` represents the root note C, `4` the //! major third E, and `7` as the perfect fifth G. -//! -//! Additionally, triads may be transformed using any one of three transformations defined as: +//! +//! Additionally, triads may be transformed using any one of three transformations defined as: //! leading, parallel, and relative. The behavior of these transformations is determined by the -//! interval between the first two notes of the triad, i.e. a major or minor third. Each of -//! these transformations is capable of being chained together into discrete and continuous +//! interval between the first two notes of the triad, i.e. a major or minor third. Each of +//! these transformations is capable of being chained together into discrete and continuous //! sequences or spaces and is its own inverse. This means that any consecutive applications of -//! any one particular transformation simply reverts the object back into its original state. -//! -//! That being said, augmented and diminished traids _can_ be transformed, however, the exact +//! any one particular transformation simply reverts the object back into its original state. +//! +//! That being said, augmented and diminished traids _can_ be transformed, however, the exact //! nature of their responses is not as predictable as with major / minor triads. -//! +//! //! ## Examples //! //! ### _Basic Usage_ -//! +//! //! Initialize a C-major triad and verify its composition -//! +//! //! ```rust //! use rstmt_nrt::Triad; //! // create new triad @@ -42,11 +42,11 @@ //! // verify the composition //! assert_eq! { c_major, [0, 4, 7] } //! ``` -//! +//! //! ### _Transformations_ -//! +//! //! Initialize a C-major triad before applying each of the three transformations: -//! +//! //! ```rust //! use rstmt_nrt::Triad; //! // create new triad From af0a766f444dd5c81e3bd195fe96c7a5f703099e Mon Sep 17 00:00:00 2001 From: FL03 Date: Sat, 10 Jan 2026 14:35:30 -0600 Subject: [PATCH 05/17] update Signed-off-by: FL03 --- .../src/{comp => compose}/impls/impl_scale.rs | 2 +- core/src/{comp => compose}/mod.rs | 0 core/src/{comp => compose}/scale.rs | 0 core/src/lib.rs | 6 +-- core/src/pitch/pitch_class.rs | 4 +- nrt/src/impls/impl_triad_base.rs | 6 +-- nrt/src/traits/triad_type.rs | 48 +++++++++++++++---- nrt/src/triad.rs | 2 +- 8 files changed, 50 insertions(+), 18 deletions(-) rename core/src/{comp => compose}/impls/impl_scale.rs (98%) rename core/src/{comp => compose}/mod.rs (100%) rename core/src/{comp => compose}/scale.rs (100%) diff --git a/core/src/comp/impls/impl_scale.rs b/core/src/compose/impls/impl_scale.rs similarity index 98% rename from core/src/comp/impls/impl_scale.rs rename to core/src/compose/impls/impl_scale.rs index 897f042..6cb8c19 100644 --- a/core/src/comp/impls/impl_scale.rs +++ b/core/src/compose/impls/impl_scale.rs @@ -3,7 +3,7 @@ Created At: 2025.12.21:08:58:02 Contrib: @FL03 */ -use crate::comp::Scale; +use crate::compose::Scale; use crate::freq::{Frequency, IntoFrequency, RawFrequency}; use num_traits::{Float, FromPrimitive}; diff --git a/core/src/comp/mod.rs b/core/src/compose/mod.rs similarity index 100% rename from core/src/comp/mod.rs rename to core/src/compose/mod.rs diff --git a/core/src/comp/scale.rs b/core/src/compose/scale.rs similarity index 100% rename from core/src/comp/scale.rs rename to core/src/compose/scale.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 5ea2ca6..f72c6b2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -44,7 +44,7 @@ pub(crate) mod macros { extern crate alloc; pub mod chord; -pub mod comp; +pub mod compose; pub mod consts; pub mod error; pub mod freq; @@ -75,7 +75,7 @@ pub mod utils { #[doc(inline)] pub use self::{ chord::{RawChord, RawChordMut}, - comp::Scale, + compose::Scale, consts::*, error::*, freq::*, @@ -96,7 +96,7 @@ pub mod prelude { pub use rstmt_traits::prelude::*; pub use crate::chord::prelude::*; - pub use crate::comp::prelude::*; + pub use crate::compose::prelude::*; pub use crate::consts::*; pub use crate::freq::*; pub use crate::intervals::prelude::*; diff --git a/core/src/pitch/pitch_class.rs b/core/src/pitch/pitch_class.rs index 3091d59..3f1f9c5 100644 --- a/core/src/pitch/pitch_class.rs +++ b/core/src/pitch/pitch_class.rs @@ -74,13 +74,13 @@ classes! { impl IntoPitchClass for isize where P: PitchClassRepr, - K: Accidental + Default, + K: Accidental, { seal! {} fn into_pitch_class(self) -> PitchClass { match self.pmod() { - x if x == P::IDX => PitchClass::from_class(P::new()), + x if x == P::IDX => PitchClass::new(), _ => panic!("cannot convert {self} into pitch class"), } } diff --git a/nrt/src/impls/impl_triad_base.rs b/nrt/src/impls/impl_triad_base.rs index 3635419..745ca91 100644 --- a/nrt/src/impls/impl_triad_base.rs +++ b/nrt/src/impls/impl_triad_base.rs @@ -60,9 +60,9 @@ where pub const fn chord_mut(&mut self) -> &mut S { &mut self.chord } - /// returns a copy of the class of the triad. - pub const fn class(&self) -> K { - self.class + /// returns a reference of the class of the triad. + pub const fn class(&self) -> &K { + &self.class } /// returns a mutable reference to the class of the triad. pub const fn class_mut(&mut self) -> &mut K { diff --git a/nrt/src/traits/triad_type.rs b/nrt/src/traits/triad_type.rs index 2b8390a..5c28132 100644 --- a/nrt/src/traits/triad_type.rs +++ b/nrt/src/traits/triad_type.rs @@ -16,18 +16,15 @@ pub trait Relative { /// The [`TriadType`] trait is used to represent the various classifications of a triad /// considered by the Neo-Riemannian theory. -pub trait TriadType: Relative +pub trait TriadType where - Self: 'static + Copy + Default + Relative + Send + Sync + core::fmt::Debug + core::fmt::Display, + Self: 'static + Copy + Relative + Send + Sync + core::fmt::Debug + core::fmt::Display, { private! {} fn new() -> Self where - Self: Sized, - { - Self::default() - } + Self: Sized; fn root(&self) -> usize; @@ -35,8 +32,18 @@ where fn third(&self) -> usize; /// consumes the instance, returning a variant of the [`TriadClass`] enum - fn dynamic(self) -> crate::Triads { - crate::Triads::from_class(self) + fn dynamic(&self) -> crate::Triads { + if self.is_major() { + Triads::Major + } else if self.is_minor() { + Triads::Minor + } else if self.is_augmented() { + Triads::Augmented + } else if self.is_diminished() { + Triads::Diminished + } else { + unreachable!("invalid triad type") + } } fn is_major(&self) -> bool { @@ -56,10 +63,31 @@ where } } +pub trait RelTriad: TriadType + Relative +where + Self::Rel: TriadType, +{ + private! {} + + fn relative(&self) -> Self::Rel; +} + /* ************* Implementations ************* */ +impl RelTriad for A +where + A: TriadType + Relative, + B: TriadType, +{ + seal! {} + + fn relative(&self) -> B { + ::new() + } +} + impl Relative for Triads { type Rel = Triads; @@ -116,6 +144,10 @@ macro_rules! triad_kind { seal! {} + fn new() -> Self { + Self::default() + } + fn root(&self) -> usize { $r } diff --git a/nrt/src/triad.rs b/nrt/src/triad.rs index 8782bd1..b05f4e3 100644 --- a/nrt/src/triad.rs +++ b/nrt/src/triad.rs @@ -131,7 +131,7 @@ mod tests { fn test_triad_properties() { let fsharp_minor = Triad::minor(6); assert! { fsharp_minor.is_minor() && !fsharp_minor.is_major() } - assert_eq! { fsharp_minor.class(), rstmt_core::Minor } + assert_eq! { fsharp_minor.class(), &rstmt_core::Minor } } #[test] From a33e488ca3fbecd31ef2c848bfd8f0b7cea2a4d8 Mon Sep 17 00:00:00 2001 From: FL03 Date: Sat, 10 Jan 2026 14:37:41 -0600 Subject: [PATCH 06/17] update accidental.rs Signed-off-by: FL03 --- core/src/pitch/traits/accidental.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/pitch/traits/accidental.rs b/core/src/pitch/traits/accidental.rs index d6588da..66b2cdf 100644 --- a/core/src/pitch/traits/accidental.rs +++ b/core/src/pitch/traits/accidental.rs @@ -23,6 +23,7 @@ where { s.parse::() } + fn name(&self) -> &str; fn symbol(&self) -> char; From de39178ae509b076bc69f2c6f3327de13b8f92bc Mon Sep 17 00:00:00 2001 From: FL03 Date: Sun, 11 Jan 2026 16:16:08 -0600 Subject: [PATCH 07/17] upate Cargo.toml Signed-off-by: FL03 --- core/Cargo.toml | 1 + nrt/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/core/Cargo.toml b/core/Cargo.toml index 419c136..18db8e0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -103,6 +103,7 @@ std = [ "anyhow/std", "contained/std", "hashbrown?/default", + "getrandom?/std", "num-complex?/std", "num-integer/std", "num-traits/std", diff --git a/nrt/Cargo.toml b/nrt/Cargo.toml index 54bec53..abf8fac 100644 --- a/nrt/Cargo.toml +++ b/nrt/Cargo.toml @@ -106,6 +106,7 @@ nightly = [ std = [ "alloc", "anyhow/std", + "getrandom?/std", "hashbrown?/default", "itertools/use_std", "num-complex?/std", From 3f6dc8634a9f22ff5051c33e41de7626692b72d0 Mon Sep 17 00:00:00 2001 From: FL03 Date: Mon, 19 Jan 2026 23:53:19 -0600 Subject: [PATCH 08/17] update Signed-off-by: FL03 --- nrt/src/impls/impl_triad_base.rs | 12 ++++++------ nrt/src/impls/impl_triad_ext.rs | 9 +++++---- nrt/src/impls/impl_triad_repr.rs | 11 +++++------ nrt/src/types/lpr.rs | 6 +++--- nrt/tests/transform.rs | 23 ++++++++++------------- rstmt/lib.rs | 2 +- traits/src/lib.rs | 7 ++----- traits/src/ops/transform.rs | 2 +- 8 files changed, 33 insertions(+), 39 deletions(-) diff --git a/nrt/src/impls/impl_triad_base.rs b/nrt/src/impls/impl_triad_base.rs index 745ca91..73b32dc 100644 --- a/nrt/src/impls/impl_triad_base.rs +++ b/nrt/src/impls/impl_triad_base.rs @@ -8,7 +8,7 @@ use crate::triad::TriadBase; use crate::traits::{TriadRepr, TriadReprMut, TriadType}; use crate::types::LPR; use num_traits::{Float, FromPrimitive, ToPrimitive, Zero}; -use rstmt_core::{Octave, PitchMod, Transform}; +use rstmt_core::{Octave, PitchMod, TryTransform}; impl TriadBase where @@ -214,28 +214,28 @@ where /// transforming a major triad, then the resulting triad will be minor (and vice versa). pub fn transform(&self, step: X) -> Y where - Self: Transform, + Self: TryTransform, { - >::transform(self, step) + >::try_transform(self, step).expect("transformation failed") } /// apply the [`Leading`](LPR::Leading) transformation to the triad pub fn leading(&self) -> Y where - Self: Transform, + Self: TryTransform, { self.transform(LPR::Leading) } /// apply the [`Parallel`](LPR::Parallel) transformation to the triad pub fn parallel(&self) -> Y where - Self: Transform, + Self: TryTransform, { self.transform(LPR::Parallel) } /// apply the [`Relative`](LPR::Relative) transformation to the triad pub fn relative(&self) -> Y where - Self: Transform, + Self: TryTransform, { self.transform(LPR::Relative) } diff --git a/nrt/src/impls/impl_triad_ext.rs b/nrt/src/impls/impl_triad_ext.rs index bb2280e..8f5c8bb 100644 --- a/nrt/src/impls/impl_triad_ext.rs +++ b/nrt/src/impls/impl_triad_ext.rs @@ -8,9 +8,9 @@ use crate::triad::TriadBase; use crate::traits::{Relative, TriadRepr, TriadReprMut, TriadType}; use crate::types::{Factors, LPR}; use num_traits::{FromPrimitive, One, Zero}; -use rstmt_core::{PitchMod, Transform}; +use rstmt_core::{PitchMod, TryTransform}; -impl Transform for TriadBase +impl TryTransform for TriadBase where K: TriadType + Relative, R: TriadType + Relative, @@ -22,10 +22,11 @@ where + core::ops::Add + core::ops::Sub, { + type Error = crate::TriadError; type Output = TriadBase; - fn transform(&self, transformation: LPR) -> Self::Output { - LPR::transform(&transformation, self).expect("transformation failed") + fn try_transform(&self, transformation: LPR) -> Result { + LPR::try_transform(&transformation, self) } } diff --git a/nrt/src/impls/impl_triad_repr.rs b/nrt/src/impls/impl_triad_repr.rs index d7e8c23..2ce0d6c 100644 --- a/nrt/src/impls/impl_triad_repr.rs +++ b/nrt/src/impls/impl_triad_repr.rs @@ -8,7 +8,7 @@ use crate::triad::TriadBase; use crate::traits::TriadRepr; use crate::types::{LPR, Triads}; use num_traits::{Float, FromPrimitive, Num, ToPrimitive, Zero}; -use rstmt_core::traits::{PitchMod, Transform}; +use rstmt_core::traits::{PitchMod, TryTransform}; use rstmt_core::{Augmented, Diminished, Major, Minor}; impl TriadBase @@ -137,10 +137,10 @@ where /// otherwise, returns [`None`](Option::None). pub fn is_neighbor(&self, other: &Self) -> Option where - Self: Transform, + Self: TryTransform, T: PartialEq, { - LPR::iter().find(|&dirac| self.transform(dirac) == *other) + LPR::iter().find(|&dirac| self.try_transform(dirac).ok() == Some(*other)) } } impl TriadBase<[T; 3], Triads, T> @@ -153,9 +153,8 @@ where where I: IntoIterator, { - path.into_iter().fold(*self, |triad, dirac| { - dirac.transform(triad).expect("transformation failed") - }) + path.into_iter() + .fold(*self, |triad, dirac| dirac.transform(triad)) } /// apply a chain of transformations to a triad in-place pub fn walk_inplace(&mut self, path: I) diff --git a/nrt/src/types/lpr.rs b/nrt/src/types/lpr.rs index 4943138..28f54be 100644 --- a/nrt/src/types/lpr.rs +++ b/nrt/src/types/lpr.rs @@ -87,11 +87,11 @@ impl LPR { ::iter() } /// Apply a transformation to a triad - pub fn transform(&self, triad: X) -> Result + pub fn transform(&self, triad: X) -> Y where - Self: TryTransform, + Self: TryTransform, { - >::try_transform(self, triad) + >::try_transform(self, triad).expect("transformation failed") } } diff --git a/nrt/tests/transform.rs b/nrt/tests/transform.rs index 0a0ea80..da75d2f 100644 --- a/nrt/tests/transform.rs +++ b/nrt/tests/transform.rs @@ -3,29 +3,26 @@ Contrib: @FL03 */ use LPR::*; -use rstmt_nrt::{LPR, Triad, TriadError}; +use rstmt_nrt::{LPR, Triad}; #[test] -fn test_leading() -> Result<(), TriadError> { +fn test_leading() { let c_major = Triad::major(0); - assert_eq! { Leading.transform(&c_major)?, [4, 7, 11] } - assert_eq! { Leading.transform(Leading.transform(&c_major)?)?, c_major } - Ok(()) + assert_eq! { Leading.transform(&c_major), [4, 7, 11] } + assert_eq! { Leading.transform(Leading.transform(&c_major)), c_major } } #[test] -fn test_parallel() -> Result<(), TriadError> { +fn test_parallel() { let c_major = Triad::major(0); - assert_eq! { Parallel.transform(&c_major)?, [0, 3, 7] } - assert_eq! { Parallel.transform(Parallel.transform(&c_major)?)?, c_major } - Ok(()) + assert_eq! { Parallel.transform(&c_major), [0, 3, 7] } + assert_eq! { Parallel.transform(Parallel.transform(&c_major)), c_major } } #[test] -fn test_relative() -> Result<(), TriadError> { +fn test_relative() { // c-major let c_major = Triad::major(0); - assert_eq! { Relative.transform(&c_major)?, [9, 0, 4] } - assert_eq! { Relative.transform(Relative.transform(&c_major)?)?, c_major } - Ok(()) + assert_eq! { Relative.transform(&c_major), [9, 0, 4] } + assert_eq! { Relative.transform(Relative.transform(&c_major)), c_major } } diff --git a/rstmt/lib.rs b/rstmt/lib.rs index 6505a50..76b7b81 100644 --- a/rstmt/lib.rs +++ b/rstmt/lib.rs @@ -54,7 +54,7 @@ clippy::should_implement_trait )] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(allocator_api))] +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // compile time checks #[cfg(not(any(feature = "std", feature = "alloc")))] compile_error! { "Either the 'std' or 'alloc' feature must be enabled for this crate." } diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 40dcff3..1db00ec 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -1,7 +1,7 @@ -#![crate_name = "rstmt_traits"] //! A collection of useful traits focused on musical abstractions, composition, and operations. //! //! +#![crate_type = "lib"] #![allow( clippy::derivable_impls, clippy::len_without_is_empty, @@ -15,10 +15,7 @@ clippy::upper_case_acronyms )] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(allocator_api))] -// compiler check -#[cfg(not(any(feature = "std", feature = "alloc")))] -compile_error! { "either the \"std\" or \"alloc\" feature must be enabled" } +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // macros #[macro_use] pub(crate) mod macros { diff --git a/traits/src/ops/transform.rs b/traits/src/ops/transform.rs index 883854d..a327af1 100644 --- a/traits/src/ops/transform.rs +++ b/traits/src/ops/transform.rs @@ -13,8 +13,8 @@ pub trait Transform { } /// [`TryTransform`] defines a fallible transformation operation that can fail, producing an pub trait TryTransform { + type Error: core::error::Error; type Output; - type Error; fn try_transform(&self, rhs: Rhs) -> Result; } From c9597466588a440a1ae6b80ef9539b33f3182712 Mon Sep 17 00:00:00 2001 From: FL03 Date: Mon, 19 Jan 2026 23:58:51 -0600 Subject: [PATCH 09/17] update Signed-off-by: FL03 --- core/src/error.rs | 11 +++++++++++ nrt/src/error.rs | 5 +---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 722dca4..d156ffe 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -52,6 +52,17 @@ pub enum Error { Unknown(String), } +impl Error { + /// creates a boxed error from the provided error + #[cfg(feature = "alloc")] + pub fn boxed(err: E) -> Self + where + E: core::error::Error + Send + Sync + 'static, + { + Error::BoxError(Box::new(err)) + } +} + #[cfg(feature = "alloc")] impl From<&str> for Error { fn from(s: &str) -> Self { diff --git a/nrt/src/error.rs b/nrt/src/error.rs index ada71be..5e2445e 100644 --- a/nrt/src/error.rs +++ b/nrt/src/error.rs @@ -4,9 +4,6 @@ */ //! custom error types for the `nrt` crate -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - /// a type alias for a [`Result`](core::result::Result) with [`TriadError`] as its error type. pub(crate) type Result = core::result::Result; @@ -35,7 +32,7 @@ impl From for rstmt::Error { match err { TriadError::CoreError(e) => e, #[cfg(feature = "alloc")] - _ => rstmt::Error::BoxError(Box::new(err)), + _ => rstmt_core::Error::boxed(err), } } } From a7c23d3954cf8e52e16c6f2c623768cdc2b2bf4a Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 09:07:08 -0600 Subject: [PATCH 10/17] update Signed-off-by: FL03 --- Cargo.lock | 128 ++++++++++++------ Cargo.toml | 2 +- core/src/chords/chord_base.rs | 34 +++++ core/src/chords/impls/impl_chord_base.rs | 34 +++++ core/src/{chord => chords}/mod.rs | 10 +- .../src/{chord => chords}/traits/raw_chord.rs | 65 +++++++-- core/src/compose/impls/impl_scale.rs | 8 +- core/src/error.rs | 26 +++- core/src/freq.rs | 90 ------------ core/src/freq/frequency.rs | 17 +++ core/src/freq/{ => impls}/impl_freq.rs | 10 +- core/src/freq/{ => impls}/impl_freq_ext.rs | 4 +- core/src/freq/{ => impls}/impl_freq_rand.rs | 0 core/src/freq/{ => impls}/impl_freq_repr.rs | 0 core/src/freq/mod.rs | 51 +++++++ core/src/freq/traits/convert.rs | 28 ++++ core/src/freq/traits/raw_frequency.rs | 38 ++++++ core/src/{ => freq}/utils/frequency.rs | 2 +- core/src/lib.rs | 30 ++-- core/src/note/impl_aspn.rs | 55 -------- core/src/{note.rs => notes/aspn.rs} | 57 +------- .../impls/impl_aspn.rs} | 54 +++++++- .../{note => notes/impls}/impl_note_base.rs | 2 +- .../{note => notes/impls}/impl_note_ext.rs | 14 +- .../{note => notes/impls}/impl_note_repr.rs | 7 +- core/src/notes/mod.rs | 57 ++++++++ core/src/notes/note_base.rs | 24 ++++ core/src/notes/traits/convert.rs | 0 core/src/{ => notes}/types/accents.rs | 0 core/src/{ => notes}/types/notes.rs | 0 30 files changed, 548 insertions(+), 299 deletions(-) create mode 100644 core/src/chords/chord_base.rs create mode 100644 core/src/chords/impls/impl_chord_base.rs rename core/src/{chord => chords}/mod.rs (77%) rename core/src/{chord => chords}/traits/raw_chord.rs (79%) delete mode 100644 core/src/freq.rs create mode 100644 core/src/freq/frequency.rs rename core/src/freq/{ => impls}/impl_freq.rs (95%) rename core/src/freq/{ => impls}/impl_freq_ext.rs (97%) rename core/src/freq/{ => impls}/impl_freq_rand.rs (100%) rename core/src/freq/{ => impls}/impl_freq_repr.rs (100%) create mode 100644 core/src/freq/mod.rs create mode 100644 core/src/freq/traits/convert.rs create mode 100644 core/src/freq/traits/raw_frequency.rs rename core/src/{ => freq}/utils/frequency.rs (94%) delete mode 100644 core/src/note/impl_aspn.rs rename core/src/{note.rs => notes/aspn.rs} (57%) rename core/src/{note/impl_aspn_ext.rs => notes/impls/impl_aspn.rs} (60%) rename core/src/{note => notes/impls}/impl_note_base.rs (97%) rename core/src/{note => notes/impls}/impl_note_ext.rs (82%) rename core/src/{note => notes/impls}/impl_note_repr.rs (86%) create mode 100644 core/src/notes/mod.rs create mode 100644 core/src/notes/note_base.rs create mode 100644 core/src/notes/traits/convert.rs rename core/src/{ => notes}/types/accents.rs (100%) rename core/src/{ => notes}/types/notes.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 2fbec1b..e54e25c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,9 +79,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "shlex", @@ -95,9 +95,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "num-traits", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "contained" @@ -177,7 +177,7 @@ dependencies = [ "num-complex", "num-traits", "rand 0.9.2", - "rand_core 0.9.3", + "rand_core 0.9.5", "rand_distr", "serde", "serde_json", @@ -325,9 +325,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fnv" @@ -478,9 +478,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -519,12 +519,38 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", + "serde", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -643,6 +669,21 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -699,7 +740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core 0.9.5", "serde", ] @@ -710,7 +751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -724,9 +765,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom", "serde", @@ -744,6 +785,12 @@ dependencies = [ "serde_with", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.11.0" @@ -876,11 +923,12 @@ dependencies = [ [[package]] name = "rspace-traits" -version = "0.0.7" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a0ef5a8f6f27c6004e5be25e2cb1a16767e02826f0754ba5f0b9915f2b6082" +checksum = "f14d444107a912e6c13038f81d3d44ba4147fcbf82872b0a0a572263f556b334" dependencies = [ "hashbrown 0.16.1", + "ndarray", "num-complex", "num-traits", "paste", @@ -1145,18 +1193,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1174,30 +1222,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -1342,9 +1390,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -1355,9 +1403,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1365,9 +1413,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -1378,9 +1426,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -1398,9 +1446,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -1533,6 +1581,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index 922c849..70834ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ rstmt-traits = { default-features = false, path = "traits", version = "0.0.13" } # custom contained = { default-features = false, features = ["macros"], version = "0.2.3" } rshyper = { default-features = false, features = ["macros"], version = "0.1.9" } -rspace-traits = { default-features = false, version = "0.0.7" } +rspace-traits = { default-features = false, version = "0.0.9" } variants = { default-features = false, features = ["derive"], version = "0.0.1" } # benchmarking & testing criterion = { default-features = false, version = "0.8" } diff --git a/core/src/chords/chord_base.rs b/core/src/chords/chord_base.rs new file mode 100644 index 0000000..e74de2e --- /dev/null +++ b/core/src/chords/chord_base.rs @@ -0,0 +1,34 @@ +/* + Appellation: chord_base + Created At: 2026.01.20:08:24:06 + Contrib: @FL03 +*/ +use super::RawChord; + +pub type ChordSlice = ChordBase<[T], T>; + +pub type ChordArray = ChordBase<[T; N], T>; + +pub type ChordSliceRef<'a, T> = ChordBase<&'a [T], T>; + +pub type ChordSliceMut<'a, T> = ChordBase<&'a mut [T], T>; + +#[cfg(feature = "alloc")] +/// a type alias for a [`ChordBase`] that leverages a [`Vec`](alloc::vec::Vec) as its underlying +/// representation. +pub type Chord = ChordBase, T>; + +/// The [`ChordBase`] implementation is designed to be a generic container allowing +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(deny_unknown_fields, rename_all = "snake_case") +)] +#[repr(C)] +pub struct ChordBase::Elem> +where + R: ?Sized + RawChord, +{ + pub(crate) repr: R, +} diff --git a/core/src/chords/impls/impl_chord_base.rs b/core/src/chords/impls/impl_chord_base.rs new file mode 100644 index 0000000..a5723aa --- /dev/null +++ b/core/src/chords/impls/impl_chord_base.rs @@ -0,0 +1,34 @@ +/* + Appellation: impl_chord_base + Created At: 2026.01.20:08:26:02 + Contrib: @FL03 +*/ +use crate::chords::chord_base::ChordBase; + +use crate::chords::RawChord; + +impl ChordBase +where + S: RawChord, +{ + /// creates a new [`ChordBase`] instance from a given chord representation. + pub const fn new(repr: S) -> Self { + Self { repr } + } + /// returns a reference to the underlying chord representation. + pub const fn data(&self) -> &S { + &self.repr + } + /// returns a mutable reference to the underlying chord representation. + pub const fn data_mut(&mut self) -> &mut S { + &mut self.repr + } + /// returns the number of elements in the chord representation. + pub fn len(&self) -> usize { + self.repr.len() + } + /// returns true if the chord contains no elements + pub fn is_empty(&self) -> bool { + self.repr.is_empty() + } +} diff --git a/core/src/chord/mod.rs b/core/src/chords/mod.rs similarity index 77% rename from core/src/chord/mod.rs rename to core/src/chords/mod.rs index 70a8026..5a8a4d9 100644 --- a/core/src/chord/mod.rs +++ b/core/src/chords/mod.rs @@ -7,7 +7,13 @@ //! to generalize their behavior across various representations. //! #[doc(inline)] -pub use self::traits::*; +pub use self::{chord_base::*, traits::*}; + +mod chord_base; + +mod impls { + mod impl_chord_base; +} mod traits { #[doc(inline)] @@ -18,6 +24,6 @@ mod traits { // prelude (local) #[doc(hidden)] pub(crate) mod prelude { - #[doc(inline)] + pub use super::chord_base::*; pub use super::traits::*; } diff --git a/core/src/chord/traits/raw_chord.rs b/core/src/chords/traits/raw_chord.rs similarity index 79% rename from core/src/chord/traits/raw_chord.rs rename to core/src/chords/traits/raw_chord.rs index 7c590b3..23f0b3f 100644 --- a/core/src/chord/traits/raw_chord.rs +++ b/core/src/chords/traits/raw_chord.rs @@ -3,16 +3,18 @@ Created At: 2025.12.23:17:32:04 Contrib: @FL03 */ -use rspace_traits::RawSpace; /// The [`RawChord`] trait works to define a basic interface shared by all compatible /// reprsentations of a chord. Since a chord is essentially a sequence of pitches, the trait /// captures this behavior through association with an element type. -pub trait RawChord: RawSpace { +pub trait RawChord { + type Elem; + + private! {} /// returns a slice representation of the chord. fn as_slice(&self) -> &[Self::Elem]; /// returns the number of elements in the chord representation. fn len(&self) -> usize; - + /// returns true if the chord contains no elements fn is_empty(&self) -> bool { self.len() == 0 } @@ -20,15 +22,21 @@ pub trait RawChord: RawSpace { /// The [`RawChordMut`] trait extends the [`RawChord`] trait to provide mutable access to the /// underlying elements of the chord representation. -pub trait RawChordMut: RawSpace { +pub trait RawChordMut: RawChord { /// returns a mutable slice representation of the chord. fn as_mut_slice(&mut self) -> &mut [Self::Elem]; } - -pub trait ChordRepr: RawSpace {} +/// The [`ChordRepr`] trait extends the [`RawChord`] interface to include useful initialization +/// routines and other associated functions. +pub trait ChordRepr: RawChord + Sized { + /// creates a new chord representation from a slice of elements. + fn from_slice(slice: &[Self::Elem]) -> Self + where + Self::Elem: Clone; +} /// The [`RawChordIter`] trait extends the [`RawChord`] trait to provide an iterator over /// the elements of the chord representation. -pub trait RawChordIter: RawSpace { +pub trait RawChordIter: RawChord { type Iter<'b>: Iterator where Self::Elem: 'b, @@ -45,6 +53,10 @@ impl RawChord for &C where C: RawChord, { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { C::as_slice(*self) } @@ -58,6 +70,10 @@ impl RawChord for &mut C where C: RawChord, { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { C::as_slice(*self) } @@ -68,6 +84,10 @@ where } impl RawChord for (T, T, T) { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { unsafe { core::slice::from_raw_parts(self as *const (T, T, T) as *const T, 3) } } @@ -78,6 +98,10 @@ impl RawChord for (T, T, T) { } impl RawChord for [T] { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self } @@ -87,7 +111,17 @@ impl RawChord for [T] { } } +impl RawChordMut for [T] { + fn as_mut_slice(&mut self) -> &mut [T] { + self + } +} + impl RawChord for &[T] { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self } @@ -98,6 +132,9 @@ impl RawChord for &[T] { } impl RawChord for &mut [T] { + type Elem = T; + + seal! {} fn as_slice(&self) -> &[T] { self } @@ -107,12 +144,6 @@ impl RawChord for &mut [T] { } } -impl RawChordMut for [T] { - fn as_mut_slice(&mut self) -> &mut [T] { - self - } -} - impl RawChordMut for &mut [T] { fn as_mut_slice(&mut self) -> &mut [T] { self @@ -120,6 +151,10 @@ impl RawChordMut for &mut [T] { } impl RawChord for [T; N] { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self } @@ -141,6 +176,10 @@ mod impl_alloc { use alloc::vec::Vec; impl RawChord for Vec { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self.as_slice() } diff --git a/core/src/compose/impls/impl_scale.rs b/core/src/compose/impls/impl_scale.rs index 6cb8c19..8ce4177 100644 --- a/core/src/compose/impls/impl_scale.rs +++ b/core/src/compose/impls/impl_scale.rs @@ -4,7 +4,9 @@ Contrib: @FL03 */ use crate::compose::Scale; -use crate::freq::{Frequency, IntoFrequency, RawFrequency}; +use crate::freq::{ + Frequency, IntoFrequency, RawFrequency, classify_freq_with_scale, get_frequency_of_pitch, +}; use num_traits::{Float, FromPrimitive}; impl Scale { @@ -38,7 +40,7 @@ impl Scale { if freq <= T::zero() { return None; } - crate::classify_freq_with_scale(freq, self.root().value()) + classify_freq_with_scale(freq, self.root().value()) } /// returns the frequency [Hz] of the given pitch class `n`, using the formula: /// @@ -49,6 +51,6 @@ impl Scale { where T: RawFrequency + Float + FromPrimitive, { - crate::compute_freq_of_pitch(n, self.root().value()).into_frequency() + get_frequency_of_pitch(n, self.root().value()).into_frequency() } } diff --git a/core/src/error.rs b/core/src/error.rs index d156ffe..645cb97 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -32,21 +32,29 @@ pub enum Error { IncompatibleIntervals(String), #[error("Invalid Note")] InvalidNote, + // external errors #[error(transparent)] AnyError(#[from] anyhow::Error), + #[error(transparent)] + #[cfg(feature = "serde_json")] + JsonError(#[from] serde_json::Error), + // core errors + #[error(transparent)] + AddrParseError(#[from] core::net::AddrParseError), #[error("The impossible has occurred")] Infallible(#[from] core::convert::Infallible), #[error(transparent)] FmtError(#[from] core::fmt::Error), - #[cfg(feature = "alloc")] #[error(transparent)] - BoxError(#[from] Box), + Utf8Error(#[from] core::str::Utf8Error), + // std-based errors #[cfg(feature = "std")] #[error(transparent)] IOError(#[from] std::io::Error), + // alloc-based errors + #[cfg(feature = "alloc")] #[error(transparent)] - #[cfg(feature = "serde_json")] - JsonError(#[from] serde_json::Error), + BoxError(#[from] Box), #[cfg(feature = "alloc")] #[error("Unknown Error: {0}")] Unknown(String), @@ -61,12 +69,20 @@ impl Error { { Error::BoxError(Box::new(err)) } + /// creates a boxed error from the provided error + #[cfg(feature = "alloc")] + pub fn unknown(err: E) -> Self + where + E: alloc::string::ToString, + { + Error::Unknown(err.to_string()) + } } #[cfg(feature = "alloc")] impl From<&str> for Error { fn from(s: &str) -> Self { - Error::Unknown(String::from(s)) + Error::unknown(s) } } diff --git a/core/src/freq.rs b/core/src/freq.rs deleted file mode 100644 index ebde21f..0000000 --- a/core/src/freq.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* - Appellation: freq - Created At: 2025.12.29:17:13:00 - Contrib: @FL03 -*/ -mod impl_freq; -mod impl_freq_ext; -mod impl_freq_rand; -mod impl_freq_repr; - -/// [`RawFrequency`] is a marker trait denoting objects capable of representing a frequency -pub trait RawFrequency { - private! {} -} - -/// [`AsFrequency`] is a trait enabling the conversion of a reference to a type into a -/// [`Frequency`] instance. -pub trait AsFrequency { - /// Converts the current value into a [`Frequency`] instance. - fn as_frequency(&self) -> Frequency; -} -/// The [`IntoFrequency`] trait consumes the value and converts it into a [`Frequency`]. -pub trait IntoFrequency { - /// Converts the current value into a [`Frequency`] instance. - fn into_frequency(self) -> Frequency; -} - -/// The [`Frequency`] type is a generic wrapper around type `T` that implements the -/// [`RawFrequency`] trait. This implementation is designed to provide a consistent interface -/// for dealing with frequencies within the crate, enabling conversion, arithmetic operations, -/// and other utilities that are common to frequency values. -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(transparent) -)] -#[repr(transparent)] -pub struct Frequency(pub T); - -/* - ************* Implementations ************* -*/ -impl IntoFrequency for U -where - U: Into>, -{ - fn into_frequency(self) -> Frequency { - self.into() - } -} - -macro_rules! raw_frequency { - (@impl $T:ty) => { - impl RawFrequency for $T { - seal! {} - } - }; - {$($T:ty),* $(,)?} => { - $(raw_frequency!(@impl $T);)* - }; -} - -raw_frequency! { - f32, f64, - i8, i16, i32, i64, i128, isize, - u8, u16, u32, u64, u128, usize -} - -#[cfg(feature = "complex")] -impl RawFrequency for num_complex::Complex -where - T: RawFrequency, -{ - seal! {} -} - -#[cfg(test)] -mod tests { - use super::*; - - const A4: f64 = 440.0; - const C4: f64 = 261.6255653005986; - - #[test] - fn test_freq_classification() { - let f = Frequency::new(C4); - assert_eq! { f.classify_by(A4), -9 } - } -} diff --git a/core/src/freq/frequency.rs b/core/src/freq/frequency.rs new file mode 100644 index 0000000..f24f0b4 --- /dev/null +++ b/core/src/freq/frequency.rs @@ -0,0 +1,17 @@ +/* + Appellation: freq + Created At: 2025.12.29:17:13:00 + Contrib: @FL03 +*/ +/// The [`Frequency`] type is a generic wrapper around type `T` that implements the +/// [`RawFrequency`] trait. This implementation is designed to provide a consistent interface +/// for dealing with frequencies within the crate, enabling conversion, arithmetic operations, +/// and other utilities that are common to frequency values. +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(transparent) +)] +#[repr(transparent)] +pub struct Frequency(pub T); diff --git a/core/src/freq/impl_freq.rs b/core/src/freq/impls/impl_freq.rs similarity index 95% rename from core/src/freq/impl_freq.rs rename to core/src/freq/impls/impl_freq.rs index 54a090f..9e85f08 100644 --- a/core/src/freq/impl_freq.rs +++ b/core/src/freq/impls/impl_freq.rs @@ -2,10 +2,10 @@ appellation: impl_freq authors: @FL03 */ -use super::{Frequency, RawFrequency}; use crate::consts::A4_FREQUENCY; +use crate::freq::frequency::Frequency; +use crate::freq::{RawFrequency, classify_freq_with_scale, get_frequency_of_pitch}; use crate::pitch::{Accidental, PitchClass, RawPitchClass}; -use crate::utils::{classify_freq_with_scale, compute_freq_of_pitch}; use num_traits::{Float, FromPrimitive, ToPrimitive}; use rstmt_traits::ClassifyBy; @@ -28,7 +28,7 @@ where T: Float + FromPrimitive, { let class = note.to_isize().unwrap(); - Self(compute_freq_of_pitch(class, root)) + Self(get_frequency_of_pitch(class, root)) } /// a shorthand method for creating a new frequency from the given pitch class using A4 as /// the base frequency @@ -103,9 +103,9 @@ where #[inline] /// apply a function to a reference of the current frequency, capturing the result in a /// new instance - pub fn apply(&self, mut f: F) -> Frequency + pub fn apply(&self, f: F) -> Frequency where - F: FnMut(&T) -> U, + F: FnOnce(&T) -> U, { Frequency(f(self.get())) } diff --git a/core/src/freq/impl_freq_ext.rs b/core/src/freq/impls/impl_freq_ext.rs similarity index 97% rename from core/src/freq/impl_freq_ext.rs rename to core/src/freq/impls/impl_freq_ext.rs index 75c5502..f2a275b 100644 --- a/core/src/freq/impl_freq_ext.rs +++ b/core/src/freq/impls/impl_freq_ext.rs @@ -3,8 +3,8 @@ Created At: 2025.12.29:17:36:58 Contrib: @FL03 */ -use super::Frequency; -use crate::RawFrequency; +use crate::freq::RawFrequency; +use crate::freq::frequency::Frequency; use num_traits::{Num, One, Zero}; contained::fmt_wrapper! { diff --git a/core/src/freq/impl_freq_rand.rs b/core/src/freq/impls/impl_freq_rand.rs similarity index 100% rename from core/src/freq/impl_freq_rand.rs rename to core/src/freq/impls/impl_freq_rand.rs diff --git a/core/src/freq/impl_freq_repr.rs b/core/src/freq/impls/impl_freq_repr.rs similarity index 100% rename from core/src/freq/impl_freq_repr.rs rename to core/src/freq/impls/impl_freq_repr.rs diff --git a/core/src/freq/mod.rs b/core/src/freq/mod.rs new file mode 100644 index 0000000..48a2cf7 --- /dev/null +++ b/core/src/freq/mod.rs @@ -0,0 +1,51 @@ +/* + Appellation: freq + Created At: 2026.01.20:08:34:12 + Contrib: @FL03 +*/ +#[doc(inline)] +pub use self::{frequency::*, traits::*, utils::*}; + +mod frequency; + +mod impls { + mod impl_freq; + mod impl_freq_ext; + mod impl_freq_rand; + mod impl_freq_repr; +} + +mod traits { + #[doc(inline)] + pub use self::{convert::*, raw_frequency::*}; + + mod convert; + mod raw_frequency; +} + +mod utils { + #[doc(inline)] + pub use self::frequency::*; + + mod frequency; +} +// prelude (local) +pub(crate) mod prelude { + pub use super::frequency::*; + pub use super::traits::*; + pub use super::utils::*; +} + +#[cfg(test)] +mod tests { + use super::*; + + const A4: f64 = 440.0; + const C4: f64 = 261.6255653005986; + + #[test] + fn test_freq_classification() { + let f = Frequency::new(C4); + assert_eq! { f.classify_by(A4), -9 } + } +} diff --git a/core/src/freq/traits/convert.rs b/core/src/freq/traits/convert.rs new file mode 100644 index 0000000..85396c0 --- /dev/null +++ b/core/src/freq/traits/convert.rs @@ -0,0 +1,28 @@ +/* + Appellation: convert + Created At: 2026.01.20:08:33:21 + Contrib: @FL03 +*/ +use crate::freq::Frequency; +/// [`AsFrequency`] is a trait enabling the conversion of a reference to a type into a +/// [`Frequency`] instance. +pub trait AsFrequency { + /// Converts the current value into a [`Frequency`] instance. + fn as_frequency(&self) -> Frequency; +} +/// The [`IntoFrequency`] trait consumes the value and converts it into a [`Frequency`]. +pub trait IntoFrequency { + /// Converts the current value into a [`Frequency`] instance. + fn into_frequency(self) -> Frequency; +} +/* + ************* Implementations ************* +*/ +impl IntoFrequency for U +where + U: Into>, +{ + fn into_frequency(self) -> Frequency { + self.into() + } +} diff --git a/core/src/freq/traits/raw_frequency.rs b/core/src/freq/traits/raw_frequency.rs new file mode 100644 index 0000000..9807136 --- /dev/null +++ b/core/src/freq/traits/raw_frequency.rs @@ -0,0 +1,38 @@ +/* + Appellation: raw_frequency + Created At: 2026.01.20:08:32:55 + Contrib: @FL03 +*/ + +/// [`RawFrequency`] is a marker trait denoting objects capable of representing a frequency +pub trait RawFrequency { + private! {} +} + +/* + ************* Implementations ************* +*/ +macro_rules! raw_frequency { + (@impl $T:ty) => { + impl RawFrequency for $T { + seal! {} + } + }; + {$($T:ty),* $(,)?} => { + $(raw_frequency!(@impl $T);)* + }; +} + +raw_frequency! { + f32, f64, + i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize +} + +#[cfg(feature = "complex")] +impl RawFrequency for num_complex::Complex +where + T: RawFrequency, +{ + seal! {} +} diff --git a/core/src/utils/frequency.rs b/core/src/freq/utils/frequency.rs similarity index 94% rename from core/src/utils/frequency.rs rename to core/src/freq/utils/frequency.rs index 79e0aab..0b52a53 100644 --- a/core/src/utils/frequency.rs +++ b/core/src/freq/utils/frequency.rs @@ -11,7 +11,7 @@ use num_traits::{Float, FromPrimitive}; /// ```math /// F=\beta\cdot{2^{\frac{n}{12}}} /// ``` -pub fn compute_freq_of_pitch(n: isize, root: T) -> T +pub fn get_frequency_of_pitch(n: isize, root: T) -> T where T: Float + FromPrimitive, { diff --git a/core/src/lib.rs b/core/src/lib.rs index f72c6b2..497d131 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -43,48 +43,37 @@ pub(crate) mod macros { #[cfg(feature = "alloc")] extern crate alloc; -pub mod chord; +pub mod chords; pub mod compose; pub mod consts; pub mod error; pub mod freq; pub mod intervals; -pub mod note; +pub mod notes; pub mod octave; pub mod pitch; pub mod types { //! this module imimplements various types and other primitives used throughout the library #[doc(inline)] - pub use self::{accents::*, harmonic_funcs::*, notes::*}; + pub use self::harmonic_funcs::*; - mod accents; mod harmonic_funcs; - mod notes; } -pub mod utils { - //! useful utilities for musical primitives for converting between different - //! representations, classification routines, and more. - #[doc(inline)] - pub use self::frequency::*; - - mod frequency; -} // re-exports #[doc(inline)] pub use self::{ - chord::{RawChord, RawChordMut}, + chords::{RawChord, RawChordMut}, compose::Scale, consts::*, error::*, - freq::*, + freq::{Frequency, RawFrequency}, intervals::*, - note::*, + notes::*, octave::*, pitch::*, types::*, - utils::*, }; #[doc(inline)] pub use rstmt_traits as traits; @@ -95,14 +84,13 @@ pub use rstmt_traits::prelude::*; pub mod prelude { pub use rstmt_traits::prelude::*; - pub use crate::chord::prelude::*; + pub use crate::chords::prelude::*; pub use crate::compose::prelude::*; pub use crate::consts::*; - pub use crate::freq::*; + pub use crate::freq::prelude::*; pub use crate::intervals::prelude::*; - pub use crate::note::*; + pub use crate::notes::prelude::*; pub use crate::octave::*; pub use crate::pitch::prelude::*; pub use crate::types::*; - pub use crate::utils::*; } diff --git a/core/src/note/impl_aspn.rs b/core/src/note/impl_aspn.rs deleted file mode 100644 index 38dad3d..0000000 --- a/core/src/note/impl_aspn.rs +++ /dev/null @@ -1,55 +0,0 @@ -/* - Appellation: impl_aspn - Created At: 2025.12.20:08:16:03 - Contrib: @FL03 -*/ -use super::Aspn; -use crate::octave::Octave; -use rstmt_traits::PitchMod; - -impl Aspn { - pub fn new(class: usize, Octave(octave): Octave) -> Self { - Self { - class, - octave: Octave(octave), - } - } - /// returns a new note from a pitch value - pub fn from_pitch(pitch: usize) -> Self { - Self::new(pitch.pmod(), Octave(4)) - } - /// returns a copy to the index of the note's class - pub const fn class(&self) -> usize { - self.class - } - /// returns a mutable reference to the index of the note's class - pub fn class_mut(&mut self) -> &mut usize { - &mut self.class - } - /// returns a copy to the octave of the note - pub const fn octave(&self) -> Octave { - self.octave - } - /// returns a mutable reference to the current octave - pub const fn octave_mut(&mut self) -> &mut Octave { - &mut self.octave - } - /// set the pitch class of the note - pub fn set_class(&mut self, class: usize) -> &mut Self { - self.class = class.pmod(); - self - } - /// set the octave of the note - pub fn set_octave(&mut self, octave: Octave) -> &mut Self { - self.octave = octave; - self - } - /// consumes the current instance to create another with the given pitch class - pub fn with_class(self, class: usize) -> Self { - Self { class, ..self } - } - /// consumes the current instance to create another with the given octave - pub fn with_octave(self, octave: Octave) -> Self { - Self { octave, ..self } - } -} diff --git a/core/src/note.rs b/core/src/notes/aspn.rs similarity index 57% rename from core/src/note.rs rename to core/src/notes/aspn.rs index 8b91aae..497dd89 100644 --- a/core/src/note.rs +++ b/core/src/notes/aspn.rs @@ -2,23 +2,7 @@ Appellation: aspn Contrib: @FL03 */ -mod impl_aspn; -mod impl_aspn_ext; -mod impl_note_base; -mod impl_note_ext; -mod impl_note_repr; - use crate::octave::Octave; -use crate::pitch::{self, Accidental, PitchClass, RawPitchClass}; - -/// The [`AsAspn`] trait is used to convert a reference into a [`Aspn`] -pub trait AsAspn { - fn as_aspn(&self) -> Aspn; -} -/// A trait for converting a type into a [`Aspn`] -pub trait IntoAspn { - fn into_aspn(self) -> Aspn; -} /// An american scientific pitch notation ([`Aspn`]) representation of a musical note; this /// standard is used to represent notes in a way that is consistent with the @@ -38,23 +22,14 @@ pub struct Aspn { pub(crate) octave: Octave, } -/// The [`NoteBase`] is a generic representation of a musical note -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "snake_case") -)] -#[repr(C)] -pub struct NoteBase

::Tag> -where - P: RawPitchClass, - K: Accidental, -{ - pub(crate) class: PitchClass, - pub(crate) octave: Octave, +/// The [`AsAspn`] trait is used to convert a reference into a [`Aspn`] +pub trait AsAspn { + fn as_aspn(&self) -> Aspn; +} +/// A trait for converting a type into a [`Aspn`] +pub trait IntoAspn { + fn into_aspn(self) -> Aspn; } - /* ************* Implementations ************* */ @@ -75,21 +50,3 @@ where self.into() } } - -#[cfg(test)] -mod tests { - use super::NoteBase; - use crate::octave::Octave; - use crate::pitch::{C, CNote}; - - #[test] - fn test_note_from_octave() { - assert_eq! { NoteBase::::from_octave(Octave(4)), "C.4" } - } - #[test] - // #[ignore = "need to fix"] - fn test_note_parse() { - let exp = NoteBase::new(C::default(), Octave(4)); - assert_eq! { "C.4".parse::>().unwrap(), exp } - } -} diff --git a/core/src/note/impl_aspn_ext.rs b/core/src/notes/impls/impl_aspn.rs similarity index 60% rename from core/src/note/impl_aspn_ext.rs rename to core/src/notes/impls/impl_aspn.rs index 6a43915..b02ceab 100644 --- a/core/src/note/impl_aspn_ext.rs +++ b/core/src/notes/impls/impl_aspn.rs @@ -1,11 +1,59 @@ /* - Appellation: impl_aspn_ext - Created At: 2025.12.20:08:15:34 + Appellation: impl_aspn + Created At: 2025.12.20:08:16:03 Contrib: @FL03 */ -use super::Aspn; +use crate::notes::aspn::Aspn; +use crate::octave::Octave; use rstmt_traits::PitchMod; +impl Aspn { + pub fn new(class: usize, Octave(octave): Octave) -> Self { + Self { + class, + octave: Octave(octave), + } + } + /// returns a new note from a pitch value + pub fn from_pitch(pitch: usize) -> Self { + Self::new(pitch.pmod(), Octave(4)) + } + /// returns a copy to the index of the note's class + pub const fn class(&self) -> usize { + self.class + } + /// returns a mutable reference to the index of the note's class + pub fn class_mut(&mut self) -> &mut usize { + &mut self.class + } + /// returns a copy to the octave of the note + pub const fn octave(&self) -> Octave { + self.octave + } + /// returns a mutable reference to the current octave + pub const fn octave_mut(&mut self) -> &mut Octave { + &mut self.octave + } + /// set the pitch class of the note + pub fn set_class(&mut self, class: usize) -> &mut Self { + self.class = class.pmod(); + self + } + /// set the octave of the note + pub fn set_octave(&mut self, octave: Octave) -> &mut Self { + self.octave = octave; + self + } + /// consumes the current instance to create another with the given pitch class + pub fn with_class(self, class: usize) -> Self { + Self { class, ..self } + } + /// consumes the current instance to create another with the given octave + pub fn with_octave(self, octave: Octave) -> Self { + Self { octave, ..self } + } +} + impl core::fmt::Display for Aspn { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}.{}", self.class, self.octave) diff --git a/core/src/note/impl_note_base.rs b/core/src/notes/impls/impl_note_base.rs similarity index 97% rename from core/src/note/impl_note_base.rs rename to core/src/notes/impls/impl_note_base.rs index 4446640..487a5e8 100644 --- a/core/src/note/impl_note_base.rs +++ b/core/src/notes/impls/impl_note_base.rs @@ -3,7 +3,7 @@ Created At: 2025.12.20:09:35:09 Contrib: @FL03 */ -use super::NoteBase; +use crate::notes::note_base::NoteBase; use crate::octave::Octave; use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawPitchClass}; diff --git a/core/src/note/impl_note_ext.rs b/core/src/notes/impls/impl_note_ext.rs similarity index 82% rename from core/src/note/impl_note_ext.rs rename to core/src/notes/impls/impl_note_ext.rs index 637ceba..830889b 100644 --- a/core/src/note/impl_note_ext.rs +++ b/core/src/notes/impls/impl_note_ext.rs @@ -3,9 +3,11 @@ Created At: 2025.12.31:18:23:09 Contrib: @FL03 */ -use crate::note::NoteBase; +use crate::notes::note_base::NoteBase; use crate::octave::Octave; use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawPitchClass}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; impl core::fmt::Debug for NoteBase where @@ -46,19 +48,19 @@ where self.aspn() == *other } } + #[cfg(feature = "alloc")] impl core::str::FromStr for NoteBase where P: PitchClassRepr, - K: Accidental + core::str::FromStr, -

::Err: core::fmt::Debug, + K: Accidental + core::str::FromStr, { - type Err = crate::error::Error; + type Err = crate::Error; fn from_str(s: &str) -> Result { - let parts: alloc::vec::Vec<&str> = s.split('.').collect(); + let parts: Vec<&str> = s.split('.').collect(); if parts.len() != 2 { - return Err(crate::error::Error::FromStrParseError); + return Err(crate::Error::FromStrParseError); } let lex_class = parts[0]; let lex_octave = parts[1]; diff --git a/core/src/note/impl_note_repr.rs b/core/src/notes/impls/impl_note_repr.rs similarity index 86% rename from core/src/note/impl_note_repr.rs rename to core/src/notes/impls/impl_note_repr.rs index 277dc39..1164cee 100644 --- a/core/src/note/impl_note_repr.rs +++ b/core/src/notes/impls/impl_note_repr.rs @@ -1,4 +1,9 @@ -use crate::note::NoteBase; +/* + Appellation: impl_note_repr + Created At: 2026.01.20:08:59:53 + Contrib: @FL03 +*/ +use crate::notes::note_base::NoteBase; use crate::octave::Octave; use crate::pitch::{Flat, Natural, PitchClass, RawPitchClass, Sharp}; diff --git a/core/src/notes/mod.rs b/core/src/notes/mod.rs new file mode 100644 index 0000000..9dc19b9 --- /dev/null +++ b/core/src/notes/mod.rs @@ -0,0 +1,57 @@ +/* + Appellation: notes + Created At: 2026.01.20:08:56:38 + Contrib: @FL03 +*/ +//! this module contains various implementations and traits for defining and manipulating +//! musical notes. The [`NoteBase`] struct provides a generic representation of a musical note +//! that is parameterized by a pitch class and an octave. +#[doc(inline)] +pub use self::{aspn::*, note_base::*, types::*}; + +mod aspn; +mod note_base; + +mod impls { + mod impl_aspn; + + mod impl_note_base; + mod impl_note_ext; + mod impl_note_repr; +} + +mod traits { + // #[doc(inline)] + // pub use self::*; +} + +mod types { + #[doc(inline)] + pub use self::{accents::*, notes::*}; + + mod accents; + mod notes; +} + +pub(crate) mod prelude { + pub use super::note_base::*; + pub use super::types::*; +} + +#[cfg(test)] +mod tests { + use super::NoteBase; + use crate::octave::Octave; + use crate::pitch::{C, CNote}; + + #[test] + fn test_note_init() { + assert_eq! { NoteBase::::from_octave(Octave(4)), "C.4" } + } + + #[test] + fn test_note_parse_from_str() { + let exp = NoteBase::new(C::default(), Octave(4)); + assert_eq! { "C.4".parse::>().unwrap(), exp } + } +} diff --git a/core/src/notes/note_base.rs b/core/src/notes/note_base.rs new file mode 100644 index 0000000..93c78ba --- /dev/null +++ b/core/src/notes/note_base.rs @@ -0,0 +1,24 @@ +/* + Appellation: aspn + Contrib: @FL03 +*/ + +use crate::octave::Octave; +use crate::pitch::{self, Accidental, PitchClass, RawPitchClass}; + +/// The [`NoteBase`] is a generic representation of a musical note +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case") +)] +#[repr(C)] +pub struct NoteBase

::Tag> +where + P: RawPitchClass, + K: Accidental, +{ + pub(crate) class: PitchClass, + pub(crate) octave: Octave, +} diff --git a/core/src/notes/traits/convert.rs b/core/src/notes/traits/convert.rs new file mode 100644 index 0000000..e69de29 diff --git a/core/src/types/accents.rs b/core/src/notes/types/accents.rs similarity index 100% rename from core/src/types/accents.rs rename to core/src/notes/types/accents.rs diff --git a/core/src/types/notes.rs b/core/src/notes/types/notes.rs similarity index 100% rename from core/src/types/notes.rs rename to core/src/notes/types/notes.rs From a5539e2d04ef3177a718e5e3d92dd9ffbce04b82 Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 10:40:18 -0600 Subject: [PATCH 11/17] update Signed-off-by: FL03 --- nrt/src/impls/impl_triad_base.rs | 20 +-- nrt/src/impls/impl_triad_ext.rs | 17 +- nrt/src/impls/impl_triad_repr.rs | 8 +- nrt/src/motion/impls/impl_motion_planner.rs | 2 +- nrt/src/types/lpr.rs | 177 +++++++++++++++----- nrt/tests/transform.rs | 12 +- traits/src/ops/transform.rs | 32 +--- 7 files changed, 169 insertions(+), 99 deletions(-) diff --git a/nrt/src/impls/impl_triad_base.rs b/nrt/src/impls/impl_triad_base.rs index 73b32dc..de5a8b2 100644 --- a/nrt/src/impls/impl_triad_base.rs +++ b/nrt/src/impls/impl_triad_base.rs @@ -8,7 +8,7 @@ use crate::triad::TriadBase; use crate::traits::{TriadRepr, TriadReprMut, TriadType}; use crate::types::LPR; use num_traits::{Float, FromPrimitive, ToPrimitive, Zero}; -use rstmt_core::{Octave, PitchMod, TryTransform}; +use rstmt_core::{Octave, PitchMod, Transform}; impl TriadBase where @@ -212,30 +212,30 @@ where /// apply the given [`LPR`] transformation onto the triad, returning a new triad classified /// under `Q` where `Q` and `K` are related via the `Rel` associated type. For example, if /// transforming a major triad, then the resulting triad will be minor (and vice versa). - pub fn transform(&self, step: X) -> Y + pub fn transform(self, step: X) -> Y where - Self: TryTransform, + Self: Transform, { - >::try_transform(self, step).expect("transformation failed") + >::transform(self, step) } /// apply the [`Leading`](LPR::Leading) transformation to the triad - pub fn leading(&self) -> Y + pub fn leading(self) -> Y where - Self: TryTransform, + Self: Transform, { self.transform(LPR::Leading) } /// apply the [`Parallel`](LPR::Parallel) transformation to the triad - pub fn parallel(&self) -> Y + pub fn parallel(self) -> Y where - Self: TryTransform, + Self: Transform, { self.transform(LPR::Parallel) } /// apply the [`Relative`](LPR::Relative) transformation to the triad - pub fn relative(&self) -> Y + pub fn relative(self) -> Y where - Self: TryTransform, + Self: Transform, { self.transform(LPR::Relative) } diff --git a/nrt/src/impls/impl_triad_ext.rs b/nrt/src/impls/impl_triad_ext.rs index 8f5c8bb..d070897 100644 --- a/nrt/src/impls/impl_triad_ext.rs +++ b/nrt/src/impls/impl_triad_ext.rs @@ -5,15 +5,15 @@ */ use crate::triad::TriadBase; -use crate::traits::{Relative, TriadRepr, TriadReprMut, TriadType}; +use crate::traits::{TriadRepr, TriadReprMut, TriadType}; use crate::types::{Factors, LPR}; use num_traits::{FromPrimitive, One, Zero}; -use rstmt_core::{PitchMod, TryTransform}; +use rstmt_core::{PitchMod, Transform}; -impl TryTransform for TriadBase +impl Transform for TriadBase where - K: TriadType + Relative, - R: TriadType + Relative, + K: TriadType, + K::Rel: TriadType, S: TriadRepr, T: Copy + FromPrimitive @@ -22,11 +22,10 @@ where + core::ops::Add + core::ops::Sub, { - type Error = crate::TriadError; - type Output = TriadBase; + type Output = TriadBase; - fn try_transform(&self, transformation: LPR) -> Result { - LPR::try_transform(&transformation, self) + fn transform(self, transformation: LPR) -> Self::Output { + LPR::apply(transformation, self) } } diff --git a/nrt/src/impls/impl_triad_repr.rs b/nrt/src/impls/impl_triad_repr.rs index 2ce0d6c..7a20b96 100644 --- a/nrt/src/impls/impl_triad_repr.rs +++ b/nrt/src/impls/impl_triad_repr.rs @@ -8,7 +8,7 @@ use crate::triad::TriadBase; use crate::traits::TriadRepr; use crate::types::{LPR, Triads}; use num_traits::{Float, FromPrimitive, Num, ToPrimitive, Zero}; -use rstmt_core::traits::{PitchMod, TryTransform}; +use rstmt_core::traits::{PitchMod, Transform}; use rstmt_core::{Augmented, Diminished, Major, Minor}; impl TriadBase @@ -137,10 +137,10 @@ where /// otherwise, returns [`None`](Option::None). pub fn is_neighbor(&self, other: &Self) -> Option where - Self: TryTransform, + Self: Transform, T: PartialEq, { - LPR::iter().find(|&dirac| self.try_transform(dirac).ok() == Some(*other)) + LPR::iter().find(|&dirac| self.transform(dirac) == *other) } } impl TriadBase<[T; 3], Triads, T> @@ -154,7 +154,7 @@ where I: IntoIterator, { path.into_iter() - .fold(*self, |triad, dirac| dirac.transform(triad)) + .fold(*self, |triad, dirac| dirac.apply(triad)) } /// apply a chain of transformations to a triad in-place pub fn walk_inplace(&mut self, path: I) diff --git a/nrt/src/motion/impls/impl_motion_planner.rs b/nrt/src/motion/impls/impl_motion_planner.rs index af3e0f2..53627b6 100644 --- a/nrt/src/motion/impls/impl_motion_planner.rs +++ b/nrt/src/motion/impls/impl_motion_planner.rs @@ -640,7 +640,7 @@ where .par_bridge() .filter_map(|transform| { // Try applying the transformation - match transform.transform(&start_triad) { + match transform.apply(&start_triad) { Ok(next_triad) => { // Find edge ID if it exists let next_edge_id = self.tonnetz.triads.iter().find_map(|(&id, facet)| { diff --git a/nrt/src/types/lpr.rs b/nrt/src/types/lpr.rs index 28f54be..9cd61d8 100644 --- a/nrt/src/types/lpr.rs +++ b/nrt/src/types/lpr.rs @@ -7,7 +7,7 @@ use crate::error::TriadError; use crate::traits::{Relative, TriadRepr, TriadType}; use crate::triad::TriadBase; use num_traits::{FromPrimitive, One}; -use rstmt_core::{PitchMod, TryTransform}; +use rstmt_core::{Dirac, PitchMod}; /// The [`LPR`] implementation enumerates the primary transformations considered within the /// Neo-Riemannian theory. Each transformation is its own inverse (meaning consecutive @@ -20,14 +20,14 @@ use rstmt_core::{PitchMod, TryTransform}; /// With The transformations are: /// /// - Leading (L): -/// - [Major] subtract a semitone from the root and move it to the fifth -/// - [Minor] add a semitone to the fifth and move it to the root +/// - [Major] decrement the root by a semitone; move to the fifth +/// - [Minor] increment the fifth by a semitone; move to the root /// - Parallel (P): -/// - [Major] subtract a semitone from the third -/// - [Minor] add a semitone to the third +/// - [Major] decrement the third by a semitone +/// - [Minor] increment the third by a semitone /// - Relative (R): -/// - [Major] add a tone to the fifth and move it to the root -/// - [Minor] subtract a tone from the root and move it to the fifth +/// - [Major] add a whole tone to the fifth; move to the root +/// - [Minor] subtract a whole tone from the root; move to the fifth /// /// Using category theory we could define these transformations to be contravariant functors /// mapping between _categories_ of triads. @@ -86,19 +86,59 @@ impl LPR { use strum::IntoEnumIterator; ::iter() } - /// Apply a transformation to a triad - pub fn transform(&self, triad: X) -> Y + /// a convenience method for applying a transformation onto a triad, panicking on failure. + pub fn apply(self, triad: X) -> Y where - Self: TryTransform, + Self: Dirac, { - >::try_transform(self, triad).expect("transformation failed") + >::apply(self, triad) + } + + fn dirac(self, rhs: &TriadBase) -> TriadBase + where + K: TriadType, + R: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, + { + let major = rhs.class().root() == 4; + let two = T::from_u8(2).unwrap(); + + let &x = rhs.chord().root(); + let &y = rhs.chord().third(); + let &z = rhs.chord().fifth(); + + let notes: [T; 3] = if major { + match self { + LPR::Leading => [y, z, (x - T::one()).pmod()], + LPR::Parallel => [x, (y - T::one()).pmod(), z], + LPR::Relative => [(z + two).pmod(), x, y], + } + } else { + match self { + LPR::Leading => [(z + T::one()).pmod(), x, y], + LPR::Parallel => [x, (y + T::one()).pmod(), z], + LPR::Relative => [y, z, (x - two).pmod()], + } + }; + + TriadBase { + chord: S::from_arr(notes), + class: ::rel(&rhs.class()), + octave: *rhs.octave(), + } } } -impl TryTransform> for LPR +impl Dirac> for LPR where K: TriadType, - K::Rel: TriadType, + K::Rel: TriadType, S: TriadRepr, T: Copy + FromPrimitive @@ -108,17 +148,16 @@ where + core::ops::Sub, { type Output = TriadBase; - type Error = TriadError; - fn try_transform(&self, rhs: TriadBase) -> Result { - self.try_transform(&rhs) + fn apply(self, rhs: TriadBase) -> Self::Output { + self.dirac(&rhs) } } -impl TryTransform<&TriadBase> for LPR +impl Dirac> for &LPR where - K::Rel: TriadType, K: TriadType, + K::Rel: TriadType, S: TriadRepr, T: Copy + FromPrimitive @@ -128,37 +167,85 @@ where + core::ops::Sub, { type Output = TriadBase; - type Error = TriadError; - fn try_transform(&self, rhs: &TriadBase) -> Result { - if rhs.is_augmented() || rhs.is_diminished() { - return Err(TriadError::InvalidTriadClass); - } - let two = T::from_u8(2).unwrap(); + fn apply(self, rhs: TriadBase) -> Self::Output { + self.dirac(&rhs) + } +} - let &x = rhs.chord().root(); - let &y = rhs.chord().third(); - let &z = rhs.chord().fifth(); +impl Dirac> for &mut LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; - let notes: [T; 3] = if rhs.is_major() { - match self { - LPR::Leading => [y, z, (x - T::one()).pmod()], - LPR::Parallel => [x, (y - T::one()).pmod(), z], - LPR::Relative => [(z + two).pmod(), x, y], - } - } else { - match self { - LPR::Leading => [(z + T::one()).pmod(), x, y], - LPR::Parallel => [x, (y + T::one()).pmod(), z], - LPR::Relative => [y, z, (x - two).pmod()], - } - }; + fn apply(self, rhs: TriadBase) -> Self::Output { + self.dirac(&rhs) + } +} - Ok(TriadBase { - chord: S::from_arr(notes), - class: ::rel(&rhs.class()), - octave: *rhs.octave(), - }) +impl Dirac<&TriadBase> for LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; + + fn apply(self, rhs: &TriadBase) -> Self::Output { + self.dirac(rhs) + } +} + +impl Dirac<&TriadBase> for &LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; + + fn apply(self, rhs: &TriadBase) -> Self::Output { + self.dirac(rhs) + } +} + +impl Dirac<&mut TriadBase> for LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; + + fn apply(self, rhs: &mut TriadBase) -> Self::Output { + self.dirac(rhs) } } diff --git a/nrt/tests/transform.rs b/nrt/tests/transform.rs index da75d2f..4715789 100644 --- a/nrt/tests/transform.rs +++ b/nrt/tests/transform.rs @@ -8,21 +8,21 @@ use rstmt_nrt::{LPR, Triad}; #[test] fn test_leading() { let c_major = Triad::major(0); - assert_eq! { Leading.transform(&c_major), [4, 7, 11] } - assert_eq! { Leading.transform(Leading.transform(&c_major)), c_major } + assert_eq! { Leading.apply(&c_major), [4, 7, 11] } + assert_eq! { Leading.apply(Leading.apply(&c_major)), c_major } } #[test] fn test_parallel() { let c_major = Triad::major(0); - assert_eq! { Parallel.transform(&c_major), [0, 3, 7] } - assert_eq! { Parallel.transform(Parallel.transform(&c_major)), c_major } + assert_eq! { Parallel.apply(&c_major), [0, 3, 7] } + assert_eq! { Parallel.apply(Parallel.apply(&c_major)), c_major } } #[test] fn test_relative() { // c-major let c_major = Triad::major(0); - assert_eq! { Relative.transform(&c_major), [9, 0, 4] } - assert_eq! { Relative.transform(Relative.transform(&c_major)), c_major } + assert_eq! { Relative.apply(&c_major), [9, 0, 4] } + assert_eq! { Relative.apply(Relative.apply(&c_major)), c_major } } diff --git a/traits/src/ops/transform.rs b/traits/src/ops/transform.rs index a327af1..4b550b2 100644 --- a/traits/src/ops/transform.rs +++ b/traits/src/ops/transform.rs @@ -4,33 +4,17 @@ Contrib: @FL03 */ -/// The [`Transform`] trait establishes a common interface for objects that can be transformed -/// with respect to a given transformation, input, etc. to produce a new output. -pub trait Transform { +/// [`Dirac`] is used to define a specific transformation operation +pub trait Dirac { type Output; - fn transform(&self, rhs: Rhs) -> Self::Output; + fn apply(self, rhs: Rhs) -> Self::Output; } -/// [`TryTransform`] defines a fallible transformation operation that can fail, producing an -pub trait TryTransform { - type Error: core::error::Error; +/// The [`Transform`] trait establishes a binary operation that for objects capable of being +/// transformed by another object of type `Rhs`, producing some output. +/// +pub trait Transform { type Output; - fn try_transform(&self, rhs: Rhs) -> Result; -} - -/* - ************* Implementations ************* -*/ - -impl TryTransform for A -where - A: Transform, -{ - type Output = Y; - type Error = core::convert::Infallible; - - fn try_transform(&self, rhs: X) -> Result { - Ok(>::transform(self, rhs)) - } + fn transform(self, rhs: Rhs) -> Self::Output; } From b8b7cec7d9b4a9aa80b780d3320436b005be5170 Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 15:05:37 -0600 Subject: [PATCH 12/17] update Cargo.toml Signed-off-by: FL03 --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e54e25c..a577628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,9 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "contained" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a1ab5394ad9555b4ede356ee384f97380d9af10c85292dffbfcc842fae8150" +checksum = "171e57f4f9cf61380ad201fa1016ea5842440adff054c4314973bff1ebe0ae07" dependencies = [ "contained-core", "contained-macros", @@ -169,15 +169,15 @@ dependencies = [ [[package]] name = "contained-core" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cda86cb77598cde9b724e93c10434cf07c3a173703d825fdc3e64d6b46c34b" +checksum = "e390113196f3af3198f351253848f9e3f08ecdfb1401556ec9cbc21c0a2f25dd" dependencies = [ + "getrandom", "hashbrown 0.16.1", "num-complex", "num-traits", "rand 0.9.2", - "rand_core 0.9.5", "rand_distr", "serde", "serde_json", @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "contained-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b24b213c630b8ecea73dc39b48103e631a4953abe852b670faff1d9513a6a96e" +checksum = "0c94f9f4d49568014c259c7ea8d29eaa19c68afc09e2dd92afe06953a784259f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 70834ae..3a5a123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ rstmt-macros = { path = "macros", version = "0.0.13" } rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.13" } rstmt-traits = { default-features = false, path = "traits", version = "0.0.13" } # custom -contained = { default-features = false, features = ["macros"], version = "0.2.3" } +contained = { default-features = false, features = ["derive", "macros"], version = "0.2.4" } rshyper = { default-features = false, features = ["macros"], version = "0.1.9" } rspace-traits = { default-features = false, version = "0.0.9" } variants = { default-features = false, features = ["derive"], version = "0.0.1" } From 1f7cb3e1520a9e8a1551d177c0a2714bef4f4c2e Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 15:34:11 -0600 Subject: [PATCH 13/17] update Signed-off-by: FL03 --- .github/workflows/cargo-bench.yml | 2 +- .github/workflows/cargo-clippy.yml | 2 +- .github/workflows/cargo-publish.yml | 7 +- .github/workflows/cleanup.yml | 2 +- .github/workflows/nix.yml | 2 +- .github/workflows/release.yml | 5 +- .github/workflows/rust.yml | 25 ++--- Cargo.lock | 1 + Cargo.toml | 2 +- core/Cargo.toml | 5 +- core/src/error.rs | 160 +++++++++++++++++++++++++++- 11 files changed, 184 insertions(+), 29 deletions(-) diff --git a/.github/workflows/cargo-bench.yml b/.github/workflows/cargo-bench.yml index c010c93..7d64699 100644 --- a/.github/workflows/cargo-bench.yml +++ b/.github/workflows/cargo-bench.yml @@ -46,7 +46,7 @@ jobs: fetch-depth: 0 repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ github.token }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cargo-clippy.yml b/.github/workflows/cargo-clippy.yml index 8d6a574..f477239 100644 --- a/.github/workflows/cargo-clippy.yml +++ b/.github/workflows/cargo-clippy.yml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ github.token }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cargo-publish.yml b/.github/workflows/cargo-publish.yml index 38795e8..b1562bc 100644 --- a/.github/workflows/cargo-publish.yml +++ b/.github/workflows/cargo-publish.yml @@ -9,6 +9,8 @@ env: RUST_BACKTRACE: full on: + release: + types: [published] repository_dispatch: types: [crates-io, cargo-publish] workflow_dispatch: @@ -34,12 +36,13 @@ jobs: - rstmt-macros - rstmt steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 with: fetch-depth: 0 repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ github.token }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index d7dc99d..58ac448 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -24,6 +24,6 @@ jobs: done echo "Done" env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 3f7ba82..4833e9a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Nix uses: cachix/install-nix-action@v31 with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} + github_access_token: ${{ github.token }} - name: Build run: nix build - name: Check the flake diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b5be74..e8599aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,10 +26,11 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 with: fetch-depth: 0 - ref: ${{ github.ref }} + ref: ${{ github.event.client_payload.ref || github.ref }} repository: ${{ github.repository }} token: ${{ github.token }} - name: Dispatch & Publish to crates.io diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4cd8370..a45d2e7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,7 +39,8 @@ jobs: matrix: target: [x86_64-unknown-linux-gnu] steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -59,7 +60,8 @@ jobs: features: [full, default] target: [x86_64-unknown-linux-gnu] steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -67,26 +69,25 @@ jobs: target: ${{ matrix.target }} toolchain: stable override: true - - name: Test (default) - if: matrix.features == 'default' - run: cargo test -r --locked --workspace --target ${{ matrix.target}} - name: Test (${{ matrix.features }}) - if: matrix.features != 'default' run: cargo test -r --locked --workspace --target ${{ matrix.target}} --features ${{ matrix.features }} test_nightly: if: github.event.inputs.toolchain == 'nightly' || false - continue-on-error: true needs: build runs-on: ubuntu-latest + env: + RUSTFLAGS: "-C panic=abort -Z panic_abort_tests" strategy: fail-fast: false matrix: + package: [rstmt-traits] features: - all - no_std - "alloc,nightly" steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -99,12 +100,8 @@ jobs: - name: Test (no_std) continue-on-error: true if: matrix.features == 'no_std' - run: cargo test -r --locked --workspace --no-default-features --features nightly - env: - RUSTFLAGS: "-C panic=abort -Z panic_abort_tests" + run: cargo test -r --locked --workspace --no-default-features -p ${{ matrix.package }} - name: Test (${{ matrix.features }}) continue-on-error: true if: matrix.features != 'all' && matrix.features != 'no_std' - run: cargo test -r --locked --workspace --no-default-features --features ${{ matrix.features }} - env: - RUSTFLAGS: "-C panic=abort -Z panic_abort_tests" + run: cargo test -r --locked --workspace --no-default-features -p ${{ matrix.package }} -F ${{ matrix.features }} diff --git a/Cargo.lock b/Cargo.lock index a577628..cf4a064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,7 @@ dependencies = [ "rand 0.9.2", "rand_distr", "serde", + "serde_derive", "serde_json", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 3a5a123..c0af74f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ rstmt-macros = { path = "macros", version = "0.0.13" } rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.13" } rstmt-traits = { default-features = false, path = "traits", version = "0.0.13" } # custom -contained = { default-features = false, features = ["derive", "macros"], version = "0.2.4" } +contained = { default-features = false, features = ["macros"], version = "0.2.4" } rshyper = { default-features = false, features = ["macros"], version = "0.1.9" } rspace-traits = { default-features = false, version = "0.0.9" } variants = { default-features = false, features = ["derive"], version = "0.0.1" } diff --git a/core/Cargo.toml b/core/Cargo.toml index 18db8e0..ecda706 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,10 +26,6 @@ tag-name = "v{{version}}" [lib] bench = false -crate-type = ["lib"] -doc = true -doctest = true -test = true [dependencies] rstmt-traits = { workspace = true } @@ -181,6 +177,7 @@ serde = [ "dep:serde", "dep:serde_derive", "serde?/derive", + "contained/serde", "hashbrown?/serde", "num-complex?/serde", "rand?/serde", diff --git a/core/src/error.rs b/core/src/error.rs index 645cb97..be28a1b 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -12,6 +12,7 @@ pub type Result = core::result::Result; /// The [`Error`] enum represents various errors that can occur in the application. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { #[error("Attempted to use an invalid accidental")] InvalidAccidental, @@ -23,8 +24,8 @@ pub enum Error { FromStrParseError, #[error("Unable to parse the string into the desired pitch class: {0}")] InvalidPitchClassParse(&'static str), - #[error("Mismatched symbols: expected {0}, found {1}")] - MismatchedSymbols(char, char), + #[error("Mismatched symbols")] + MismatchedSymbols, #[error("Invalid Chord")] InvalidChord, #[cfg(feature = "alloc")] @@ -92,3 +93,158 @@ impl From for Error { Error::Unknown(s) } } + +#[doc(hidden)] +pub mod custom { + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + + #[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumString, + strum::VariantNames, + )] + #[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case") + )] + #[non_exhaustive] + pub enum ErrorKind { + InvalidAccidental, + ChordError, + NoteError, + PitchError, + InvalidPitchClass, + MismatchedPitchClasses, + FromStrParseError, + InvalidPitchClassParse, + MismatchedSymbols, + InvalidChord, + IncompatibleIntervals, + InvalidNote, + Utf8Error, + AnyError, + JsonError, + AddrParseError, + Infallible, + FmtError, + None, + IOError, + #[default] + Unknown, + } + + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct ErrorBase> { + pub(crate) kind: ErrorKind, + pub(crate) source: Option, + } + + impl ErrorBase + where + E: core::error::Error, + { + pub const fn new(kind: ErrorKind, source: Option) -> Self { + Self { kind, source } + } + + pub const fn unknown(source: E) -> Self { + Self { + kind: ErrorKind::Unknown, + source: Some(source), + } + } + /// consumes the current error to create another of the given kind + pub fn with_kind(self, kind: ErrorKind) -> ErrorBase { + ErrorBase { + kind, + source: self.source, + } + } + /// consumes the current error to create another with the given message + pub fn with_source(self, source: E2) -> ErrorBase + where + E2: core::error::Error, + { + ErrorBase { + kind: self.kind, + source: Some(source), + } + } + } + + impl core::fmt::Display for ErrorBase + where + E: 'static + core::error::Error, + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if let Some(source) = &self.source { + write!(f, "{}: {}", self.kind, source) + } else { + write!(f, "{}", self.kind) + } + } + } + + impl core::error::Error for ErrorBase + where + E: 'static + core::error::Error, + { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.source + .as_ref() + .map(|e| e as &(dyn core::error::Error + 'static)) + } + } + + impl From> for ErrorBase + where + T: core::error::Error + 'static, + { + fn from(opt: Option) -> Self { + match opt { + Some(err) => Self { + kind: ErrorKind::Unknown, + source: Some(Box::new(err)), + }, + None => Self { + kind: ErrorKind::None, + source: None, + }, + } + } + } + + impl From for ErrorBase { + fn from(err: core::str::Utf8Error) -> Self { + Self { + kind: ErrorKind::Utf8Error, + #[cfg(feature = "alloc")] + source: Some(Box::new(err)), + } + } + } + + #[cfg(feature = "serde_json")] + impl From for ErrorBase { + fn from(err: serde_json::Error) -> Self { + Self { + kind: ErrorKind::JsonError, + source: Some(Box::new(err)), + } + } + } +} From abe92c2679595edadf85c626948477c69afedce3 Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 15:43:53 -0600 Subject: [PATCH 14/17] update Signed-off-by: FL03 --- core/src/compose/impls/impl_scale.rs | 15 +++++++++++--- core/src/compose/impls/impl_scale_repr.rs | 25 +++++++++++++++++++++++ core/src/compose/mod.rs | 1 + 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 core/src/compose/impls/impl_scale_repr.rs diff --git a/core/src/compose/impls/impl_scale.rs b/core/src/compose/impls/impl_scale.rs index 8ce4177..78c3429 100644 --- a/core/src/compose/impls/impl_scale.rs +++ b/core/src/compose/impls/impl_scale.rs @@ -15,6 +15,15 @@ impl Scale { root: Frequency(root), } } + /// initialize a new scale using A4 as the root + pub fn a4() -> Self + where + T: FromPrimitive, + { + let root = ::from_usize(440).unwrap(); + Self::new(root) + } + /// creates a new [`Scale`] from the given root frequency pub const fn from_freq(root: Frequency) -> Self { Self { root } @@ -30,7 +39,7 @@ impl Scale { /// classifies the given frequency as a pitch class `n`, using the formula: /// /// ```math - /// n=\text{round}\Bigg(12\cdot\log_{2}\bigg(\frac{F}{\beta}\bigg)\Bigg) + /// n=\text{round}(12\cdot\log_{2}(\frac{F}{\beta})) /// ``` pub fn classify(&self, Frequency(freq): Frequency) -> Option where @@ -42,10 +51,10 @@ impl Scale { } classify_freq_with_scale(freq, self.root().value()) } - /// returns the frequency [Hz] of the given pitch class `n`, using the formula: + /// returns the frequency $F$, in hertz [Hz], of the given pitch class `n`, using: /// /// ```math - /// F= \beta \cdot 2^{\frac{n}{12}} + /// F=\beta\cdot{2^{\frac{n}{12}}} /// ``` pub fn get_freq_of_class(&self, n: isize) -> Frequency where diff --git a/core/src/compose/impls/impl_scale_repr.rs b/core/src/compose/impls/impl_scale_repr.rs new file mode 100644 index 0000000..49ac859 --- /dev/null +++ b/core/src/compose/impls/impl_scale_repr.rs @@ -0,0 +1,25 @@ +/* + Appellation: impl_scale_repr + Created At: 2026.01.20:15:39:15 + Contrib: @FL03 +*/ +use crate::compose::Scale; +use crate::freq::Frequency; + +macro_rules! impl_scale_const { + (@impl $t:ty) => { + impl Scale<$t> { + pub const A4: Frequency<$t> = Frequency(440 as $t); + } + }; + + ($($t:ty),* $(,)?) => { + $(impl_scale_const!(@impl $t);)* + }; +} + +impl_scale_const! { + f32, f64, + u16, u32, u64, u128, usize, + i16, i32, i64, i128, isize, +} diff --git a/core/src/compose/mod.rs b/core/src/compose/mod.rs index 2351acb..2821956 100644 --- a/core/src/compose/mod.rs +++ b/core/src/compose/mod.rs @@ -13,6 +13,7 @@ pub mod scale; mod impls { mod impl_scale; + mod impl_scale_repr; } #[doc(hidden)] From e2fee603537295bd1f4cc27be2e72bca135a860a Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 15:44:38 -0600 Subject: [PATCH 15/17] update Cargo.toml Signed-off-by: FL03 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c0af74f..3a5a123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ rstmt-macros = { path = "macros", version = "0.0.13" } rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.13" } rstmt-traits = { default-features = false, path = "traits", version = "0.0.13" } # custom -contained = { default-features = false, features = ["macros"], version = "0.2.4" } +contained = { default-features = false, features = ["derive", "macros"], version = "0.2.4" } rshyper = { default-features = false, features = ["macros"], version = "0.1.9" } rspace-traits = { default-features = false, version = "0.0.9" } variants = { default-features = false, features = ["derive"], version = "0.0.1" } From cc9be7715ac4c3d19dce6421a5519a86a58d844b Mon Sep 17 00:00:00 2001 From: FL03 Date: Tue, 20 Jan 2026 16:26:03 -0600 Subject: [PATCH 16/17] update Cargo.lock Signed-off-by: FL03 --- Cargo.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index cf4a064..69b58f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171e57f4f9cf61380ad201fa1016ea5842440adff054c4314973bff1ebe0ae07" dependencies = [ "contained-core", + "contained-derive", "contained-macros", ] @@ -185,6 +186,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "contained-derive" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022e04851be80b860c3a70ab3baa7934dcc8291391f97eb0a63e97734afcd28e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "contained-macros" version = "0.2.4" From 6dee4f93e0d56e7566d1eae63dcff16cb8cdc851 Mon Sep 17 00:00:00 2001 From: FL03 Date: Wed, 21 Jan 2026 15:24:43 -0600 Subject: [PATCH 17/17] update Signed-off-by: FL03 --- .envrc | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 39 +++++++----- .github/ISSUE_TEMPLATE/tracking.md | 4 +- .github/workflows/cargo-bench.yml | 2 +- .github/workflows/cargo-clippy.yml | 2 +- .github/workflows/cargo-publish.yml | 4 +- .github/workflows/cleanup.yml | 2 +- .github/workflows/nix.yml | 7 ++- .github/workflows/release.yml | 4 +- .github/workflows/rust.yml | 8 ++- Cargo.lock | 3 +- Cargo.toml | 2 +- clippy.toml | 2 +- core/Cargo.toml | 8 ++- nrt/Cargo.toml | 7 ++- nrt/src/types/factors.rs | 91 +++++++++++++++++----------- traits/Cargo.toml | 3 + 17 files changed, 117 insertions(+), 73 deletions(-) diff --git a/.envrc b/.envrc index a73927e..2506dcc 100644 --- a/.envrc +++ b/.envrc @@ -1,3 +1,3 @@ CARGO_TERM_COLOR=always RUST_BACKTRACE=full -RUST_LOG="debug,rstmt=info,debug" \ No newline at end of file +RUST_LOG="rsfi=info,debug,tower_http=info" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dc90b95..ef25834 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,10 +8,12 @@ title: 'Bug report: ' type: bug --- -**Describe the bug** -A clear and concise description of what the bug is. +## Description + + + +### Reproduction -**To Reproduce** Steps to reproduce the behavior: 1. Go to '...' @@ -19,24 +21,29 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error -**Expected behavior** -A clear and concise description of what you expected to happen. +#### *Expectations* + + + +#### *Actual* -**Screenshots** -If applicable, add screenshots to help explain your problem. + + +### Host Details + + **Desktop (please complete the following information):** +- Device Type: [e.g. iPhone6] - OS: [e.g. iOS] -- Browser [e.g. chrome, safari] -- Version [e.g. 22] +- Browser: [e.g. chrome, safari] +- Version: v0.0.x + +## Discussion -**Smartphone (please complete the following information):** + -- Device: [e.g. iPhone6] -- OS: [e.g. iOS8.1] -- Browser [e.g. stock browser, safari] -- Version [e.g. 22] +### Screenshots -**Additional context** -Add any other context about the problem here. + diff --git a/.github/ISSUE_TEMPLATE/tracking.md b/.github/ISSUE_TEMPLATE/tracking.md index 53b4bec..452f365 100644 --- a/.github/ISSUE_TEMPLATE/tracking.md +++ b/.github/ISSUE_TEMPLATE/tracking.md @@ -7,9 +7,9 @@ title: 'Tracking Issue for ' type: feature --- - + -_**Goals**_ +## Goals The goals of this proposal: diff --git a/.github/workflows/cargo-bench.yml b/.github/workflows/cargo-bench.yml index 7d64699..c010c93 100644 --- a/.github/workflows/cargo-bench.yml +++ b/.github/workflows/cargo-bench.yml @@ -46,7 +46,7 @@ jobs: fetch-depth: 0 repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} - token: ${{ github.token }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cargo-clippy.yml b/.github/workflows/cargo-clippy.yml index f477239..8d6a574 100644 --- a/.github/workflows/cargo-clippy.yml +++ b/.github/workflows/cargo-clippy.yml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} - token: ${{ github.token }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cargo-publish.yml b/.github/workflows/cargo-publish.yml index b1562bc..0c33242 100644 --- a/.github/workflows/cargo-publish.yml +++ b/.github/workflows/cargo-publish.yml @@ -40,9 +40,9 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 - repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} - token: ${{ github.token }} + repository: ${{ github.event.client_payload.repository || github.repository }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 58ac448..d7dc99d 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -24,6 +24,6 @@ jobs: done echo "Done" env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 4833e9a..7bd0856 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -22,11 +22,14 @@ jobs: continue-on-error: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Nix uses: cachix/install-nix-action@v31 with: - github_access_token: ${{ github.token }} + github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: Build run: nix build - name: Check the flake diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e8599aa..1a9bc0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,13 +32,13 @@ jobs: fetch-depth: 0 ref: ${{ github.event.client_payload.ref || github.ref }} repository: ${{ github.repository }} - token: ${{ github.token }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Dispatch & Publish to crates.io uses: peter-evans/repository-dispatch@v4 with: event-type: cargo-publish client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' - token: ${{ github.token }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Create GitHub Release uses: softprops/action-gh-release@v2 continue-on-error: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a45d2e7..164ec76 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -41,6 +41,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -62,6 +64,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -72,7 +76,7 @@ jobs: - name: Test (${{ matrix.features }}) run: cargo test -r --locked --workspace --target ${{ matrix.target}} --features ${{ matrix.features }} test_nightly: - if: github.event.inputs.toolchain == 'nightly' || false + if: github.event_name == 'workflow_dispatch' && github.event.inputs.toolchain == 'nightly' needs: build runs-on: ubuntu-latest env: @@ -88,6 +92,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 69b58f3..901574b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1023,7 +1023,6 @@ dependencies = [ "strum", "thiserror", "tracing", - "variants", "wasm-bindgen", ] @@ -1410,6 +1409,8 @@ dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", + "serde_json", "wasm-bindgen-macro", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 3a5a123..b501bd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ keywords = [ license = "Apache-2.0" readme = "README.md" repository = "https://github.com/FL03/rstmt.git" -rust-version = "1.85.0" +rust-version = "1.86.0" version = "0.0.13" [workspace.dependencies] diff --git a/clippy.toml b/clippy.toml index 57a4982..55787ca 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.85.0" \ No newline at end of file +msrv = "1.86.0" \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index ecda706..678fea4 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -112,6 +112,7 @@ std = [ "strum/std", "tracing?/std", "variants/std", + "wasm-bindgen?/std", ] wasi = [ @@ -139,6 +140,7 @@ alloc = [ "serde?/alloc", "serde_json?/alloc", "variants/alloc", + "wasm-bindgen?/gg-alloc", ] complex = [ @@ -184,9 +186,13 @@ serde = [ "rand_distr?/serde", "rspace-traits/serde", "rstmt-traits/serde", + "wasm-bindgen?/serde", ] -serde_json = ["dep:serde_json"] +serde_json = [ + "dep:serde_json", + "wasm-bindgen?/serde_json", +] tracing = ["dep:tracing"] diff --git a/nrt/Cargo.toml b/nrt/Cargo.toml index abf8fac..0a48f0b 100644 --- a/nrt/Cargo.toml +++ b/nrt/Cargo.toml @@ -36,7 +36,6 @@ rstmt-core = { workspace = true } # custom rshyper = { features = ["hyper_map"], workspace = true } rspace-traits = { workspace = true } -variants = { workspace = true } # data structures hashbrown = { optional = true, workspace = true } # concurrency & parallelism @@ -120,7 +119,7 @@ std = [ "serde?/std", "serde_json?/std", "strum/std", - "variants/std", + "wasm-bindgen?/std", ] wasi = [ @@ -147,7 +146,7 @@ alloc = [ "rstmt-core/alloc", "serde?/alloc", "serde_json?/alloc", - "variants/alloc", + "wasm-bindgen?/gg-alloc", ] complex = [ @@ -205,12 +204,14 @@ serde = [ "rspace-traits/serde", "rshyper/serde", "rstmt-core/serde", + "wasm-bindgen?/serde", ] serde_json = [ "dep:serde_json", "rshyper/serde_json", "rstmt-core/serde_json", + "wasm-bindgen?/serde_json", ] tracing = [ diff --git a/nrt/src/types/factors.rs b/nrt/src/types/factors.rs index 4558a8c..3003099 100644 --- a/nrt/src/types/factors.rs +++ b/nrt/src/types/factors.rs @@ -37,7 +37,6 @@ use strum::IntoEnumIterator; Hash, Ord, PartialOrd, - variants::VariantConstructors, strum::AsRefStr, strum::Display, strum::EnumCount, @@ -45,9 +44,11 @@ use strum::IntoEnumIterator; strum::EnumString, strum::VariantArray, strum::VariantNames - ) + ), + strum(serialize_all = "lowercase") )] #[repr(usize)] +#[strum(serialize_all = "lowercase")] pub enum ChordFactor { #[strum(serialize = "r", serialize = "root")] Root(T) = 0, @@ -57,6 +58,31 @@ pub enum ChordFactor { Fifth(T) = 2, } +impl Factors { + /// a functional constructor for the [`Root`](Self::Root) variant + pub const fn root() -> Self { + Self::Root + } + /// a functional constructor for the [`Third`](Self::Third) variant + pub const fn third() -> Self { + Self::Third + } + /// a functional constructor for the [`Fifth`](Self::Fifth) variant + pub const fn fifth() -> Self { + Self::Fifth + } + /// returns an array of the possible [`Factors`] variants + pub fn factors_as_slice() -> [Self; 3] { + use Factors::*; + [Root, Third, Fifth] + } + #[cfg(feature = "alloc")] + /// returns a collection of all the other variants except the one that is called on + pub fn others(&self) -> alloc::vec::Vec { + Self::iter().filter(|x| x != self).collect() + } +} + impl ChordFactor { pub const fn new(data: T, factor: Factors) -> Self { match factor { @@ -112,46 +138,40 @@ impl ChordFactor { } } -mod impl_factors { - use super::*; - - impl Factors { - /// returns an array of the possible [`Factors`] variants - pub fn factors_as_slice() -> [Self; 3] { - use Factors::*; - [Root, Third, Fifth] - } - #[cfg(feature = "alloc")] - /// returns a collection of all the other variants except the one that is called on - pub fn others(&self) -> alloc::vec::Vec { - Self::iter().filter(|x| x != self).collect() - } - } - - impl Default for Factors { - fn default() -> Self { - Factors::Root - } +impl Default for Factors { + fn default() -> Self { + Factors::Root } +} - impl From for Factors { - fn from(x: usize) -> Self { - use strum::EnumCount; - match x % Self::COUNT { - 0 => Factors::Root, - 1 => Factors::Third, - _ => Factors::Fifth, +macro_rules! impl_from_factor { + (@impl $T:ty) => { + impl From<$T> for Factors { + fn from(x: $T) -> Self { + use strum::EnumCount; + match x % Self::COUNT as $T { + 0 => Factors::Root, + 1 => Factors::Third, + 2 => Factors::Fifth, + _ => unreachable!("Modular arithmetic error"), + } } } - } - impl From for usize { - fn from(x: Factors) -> Self { - x as usize + impl From for $T { + fn from(x: Factors) -> Self { + x as $T + } } + }; + ($($T:ty),* $(,)?) => { + $(impl_from_factor! { @impl $T })* } } +impl_from_factor! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize } + + #[cfg(test)] mod tests { use super::*; @@ -167,9 +187,6 @@ mod tests { use Factors::*; let factors = Factors::factors_as_slice(); - assert_eq!(factors.len(), 3); - assert_eq!(factors[0], Root); - assert_eq!(factors[1], Third); - assert_eq!(factors[2], Fifth); + assert_eq! { factors, [Root, Third, Fifth] } } } diff --git a/traits/Cargo.toml b/traits/Cargo.toml index e639ce3..5e02d3c 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -70,6 +70,7 @@ std = [ "num-complex?/std", "num-traits/std", "serde?/std", + "wasm-bindgen?/std", ] wasi = [ @@ -85,6 +86,7 @@ alloc = [ "hashbrown?/alloc", "rspace-traits/alloc", "serde?/alloc", + "wasm-bindgen?/gg-alloc", ] complex = [ @@ -112,6 +114,7 @@ serde = [ "hashbrown?/serde", "rspace-traits/serde", "num-complex/serde", + "wasm-bindgen?/serde", ] wasm_bindgen = ["dep:wasm-bindgen"]