note_pen/
solfege.rs

1//! Solfège is a system of attributing a distinct syllable to each note in a musical scale.
2
3use crate::note::Note;
4use crate::pitch::{Pitch, RelativePitch};
5use crate::{Accidental, Alphabet};
6use std::fmt::{Display, Formatter};
7
8/// This enum represents all possible syllables of the solfège system.
9///
10/// It is important to note that the u8 representation of the syllables
11/// is different from their pitch value, use [`SolfegeSyllable::into_u8`] to convert it properly.
12#[derive(Copy, Clone, Debug, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub enum SolfegeSyllable {
15    Do,
16    Di,
17    Ra,
18    Re,
19    Ri,
20    Me,
21    Mi,
22    Fa,
23    Fi,
24    Se,
25    So,
26    Si,
27    Le,
28    La,
29    Li,
30    Te,
31    Ti,
32}
33
34impl SolfegeSyllable {
35    pub const fn into_u8(self) -> u8 {
36        match self {
37            Self::Do => 0,
38            Self::Di | Self::Ra => 1,
39            Self::Re => 2,
40            Self::Ri | Self::Me => 3,
41            Self::Mi => 4,
42            Self::Fa => 5,
43            Self::Fi | Self::Se => 6,
44            Self::So => 7,
45            Self::Si | Self::Le => 8,
46            Self::La => 9,
47            Self::Li | Self::Te => 10,
48            Self::Ti => 11,
49        }
50    }
51
52    pub const fn decrement(&self) -> Self {
53        match self {
54            Self::Do => Self::Ti,
55            Self::Di | Self::Ra => Self::Do,
56            Self::Re => Self::Di,
57            Self::Ri | Self::Me => Self::Re,
58            Self::Mi => Self::Me,
59            Self::Fa => Self::Mi,
60            Self::Fi | Self::Se => Self::Fa,
61            Self::So => Self::Fi,
62            Self::Si | Self::Le => Self::So,
63            Self::La => Self::Si,
64            Self::Li | Self::Te => Self::La,
65            Self::Ti => Self::Li,
66        }
67    }
68
69    pub const fn increment(&self) -> Self {
70        match self {
71            Self::Do => Self::Di,
72            Self::Di | Self::Ra => Self::Re,
73            Self::Re => Self::Ri,
74            Self::Ri | Self::Me => Self::Mi,
75            Self::Mi => Self::Fa,
76            Self::Fa => Self::Fi,
77            Self::Fi | Self::Se => Self::So,
78            Self::So => Self::Si,
79            Self::Si | Self::Le => Self::La,
80            Self::La => Self::Li,
81            Self::Li | Self::Te => Self::Ti,
82            Self::Ti => Self::Do,
83        }
84    }
85}
86
87impl Display for SolfegeSyllable {
88    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
89        match self {
90            Self::Do => write!(f, "Do"),
91            Self::Di => write!(f, "Di"),
92            Self::Ra => write!(f, "Ra"),
93            Self::Re => write!(f, "Re"),
94            Self::Ri => write!(f, "Ri"),
95            Self::Me => write!(f, "Me"),
96            Self::Mi => write!(f, "Mi"),
97            Self::Fa => write!(f, "Fa"),
98            Self::Fi => write!(f, "Fi"),
99            Self::Se => write!(f, "Se"),
100            Self::So => write!(f, "So"),
101            Self::Si => write!(f, "Si"),
102            Self::Le => write!(f, "Le"),
103            Self::La => write!(f, "La"),
104            Self::Li => write!(f, "Li"),
105            Self::Te => write!(f, "Te"),
106            Self::Ti => write!(f, "Ti"),
107        }
108    }
109}
110
111impl PartialEq for SolfegeSyllable {
112    fn eq(&self, other: &Self) -> bool {
113        self.into_u8() == other.into_u8()
114    }
115}
116
117#[derive(Copy, Clone, Debug)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119#[repr(transparent)]
120pub struct Moveable(pub Note);
121
122#[allow(non_upper_case_globals)]
123/// The fixed solfège root is just a movable solfège with a base of C.
124pub const Fixed: Moveable = Moveable(Note::new(Alphabet::C, Accidental::Natural, 4));
125
126#[derive(Copy, Clone, Debug)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct Solfege {
129    pub syllable: SolfegeSyllable,
130    /// The solfège root.
131    ///
132    /// Note that [`Fixed`] is also of type [`Moveable`].
133    pub kind: Moveable,
134}
135
136impl Solfege {
137    #[inline]
138    pub const fn new(syllable: SolfegeSyllable, kind: Moveable) -> Self {
139        Self { syllable, kind }
140    }
141
142    /// Get the pitch value of the solfège.
143    #[inline]
144    pub const fn id(&self) -> RelativePitch {
145        Pitch(self.kind.0.id().0 + self.syllable.into_u8() as i16).simple()
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_solfege() {
155        let solfege = Solfege::new(SolfegeSyllable::Do, Fixed);
156        assert_eq!(solfege.id().0, 3);
157        assert_eq!(solfege.syllable, SolfegeSyllable::Do);
158        assert_eq!(solfege.kind.0, Fixed.0);
159        assert_eq!(
160            solfege.syllable.increment().into_u8(),
161            SolfegeSyllable::Di.into_u8()
162        );
163        assert_eq!(
164            solfege.syllable.decrement().into_u8(),
165            SolfegeSyllable::Ti.into_u8()
166        );
167    }
168}