1use crate::note::Note;
4use crate::pitch::{Pitch, RelativePitch};
5use crate::{Accidental, Alphabet};
6use std::fmt::{Display, Formatter};
7
8#[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)]
123pub 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 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 #[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}