rstmt_core/notes/
aspn.rs

1/*
2    Appellation: aspn <module>
3    Contrib: @FL03
4*/
5use crate::PitchMod;
6use crate::octave::Octave;
7
8/// An american scientific pitch notation ([`Aspn`]) representation of a musical note; this
9/// standard is used to represent notes in a way that is consistent with the
10/// American scientific pitch notation system, which uses a combination of a pitch class
11/// (represented as an integer) and an octave (represented as an [`Octave`]) to uniquely
12/// identify a musical note. The pitch class is the note's position in the chromatic scale,
13/// while the octave indicates the note's position in the musical range.
14#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
15#[cfg_attr(
16    feature = "serde",
17    derive(serde_derive::Deserialize, serde_derive::Serialize),
18    serde(deny_unknown_fields, default, rename_all = "snake_case")
19)]
20#[repr(C)]
21pub struct Aspn {
22    pub(crate) class: usize,
23    pub(crate) octave: Octave,
24}
25
26impl Aspn {
27    pub fn new(class: usize, Octave(octave): Octave) -> Self {
28        Self {
29            class,
30            octave: Octave(octave),
31        }
32    }
33    /// returns a new note from a pitch value
34    pub fn from_pitch(pitch: usize) -> Self {
35        Self::new(pitch.pmod(), Octave(4))
36    }
37    /// returns a copy to the index of the note's class
38    pub const fn class(&self) -> usize {
39        self.class
40    }
41    /// returns a mutable reference to the index of the note's class
42    pub fn class_mut(&mut self) -> &mut usize {
43        &mut self.class
44    }
45    /// returns a copy to the octave of the note
46    pub const fn octave(&self) -> Octave {
47        self.octave
48    }
49    /// returns a mutable reference to the current octave
50    pub const fn octave_mut(&mut self) -> &mut Octave {
51        &mut self.octave
52    }
53    /// set the pitch class of the note
54    pub fn set_class(&mut self, class: usize) -> &mut Self {
55        self.class = class.pmod();
56        self
57    }
58    /// set the octave of the note
59    pub fn set_octave(&mut self, octave: Octave) -> &mut Self {
60        self.octave = octave;
61        self
62    }
63    /// consumes the current instance to create another with the given pitch class
64    pub fn with_class(self, class: usize) -> Self {
65        Self { class, ..self }
66    }
67    /// consumes the current instance to create another with the given octave
68    pub fn with_octave(self, octave: Octave) -> Self {
69        Self { octave, ..self }
70    }
71}
72
73impl core::fmt::Display for Aspn {
74    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
75        write!(f, "{}.{}", self.class, self.octave)
76    }
77}
78
79impl PartialEq<usize> for Aspn {
80    fn eq(&self, other: &usize) -> bool {
81        self.class() == *other
82    }
83}
84
85impl PartialEq<Aspn> for usize {
86    fn eq(&self, other: &Aspn) -> bool {
87        *self == other.class()
88    }
89}
90
91impl PartialOrd<usize> for Aspn {
92    fn partial_cmp(&self, other: &usize) -> Option<core::cmp::Ordering> {
93        self.class().partial_cmp(other)
94    }
95}
96
97impl PartialOrd<Aspn> for usize {
98    fn partial_cmp(&self, other: &Aspn) -> Option<core::cmp::Ordering> {
99        self.partial_cmp(&other.class())
100    }
101}
102
103impl core::ops::Add<Aspn> for Aspn {
104    type Output = Self;
105
106    fn add(self, rhs: Aspn) -> Self::Output {
107        let class = (self.class + rhs.class).pmod();
108        let octave = self.octave + rhs.octave;
109
110        Self::new(class, octave)
111    }
112}
113
114impl core::ops::AddAssign<Aspn> for Aspn {
115    fn add_assign(&mut self, rhs: Aspn) {
116        self.class += rhs.class;
117        self.octave += rhs.octave;
118    }
119}
120
121impl core::ops::Add<usize> for Aspn {
122    type Output = Self;
123
124    fn add(self, rhs: usize) -> Self::Output {
125        Self::new(self.class + rhs, self.octave)
126    }
127}
128
129impl core::ops::AddAssign<usize> for Aspn {
130    fn add_assign(&mut self, rhs: usize) {
131        self.class = (self.class + rhs).pmod();
132    }
133}
134
135impl core::ops::Sub<usize> for Aspn {
136    type Output = Self;
137
138    fn sub(self, rhs: usize) -> Self::Output {
139        let class = self.class as isize - rhs as isize;
140        Self {
141            class: class.pmod() as usize,
142            ..self
143        }
144    }
145}
146
147impl core::ops::SubAssign<usize> for Aspn {
148    fn sub_assign(&mut self, rhs: usize) {
149        self.class = (self.class as isize - rhs as isize).pmod() as usize;
150    }
151}
152
153macro_rules! impl_note_from {
154    ($($t:ty),*) => {
155        $(
156            impl From<$t> for Aspn {
157                fn from(class: $t) -> Self {
158                    Self::from_pitch(class.pmod() as usize)
159                }
160            }
161
162            impl From<($t, Octave)> for Aspn {
163                fn from((class, octave): ($t, Octave)) -> Self {
164                    Self::new(class.pmod() as usize, octave)
165                }
166            }
167        )*
168    };
169}
170
171impl_note_from!(
172    usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128
173);