1use std::{fmt, ops::{AddAssign, SubAssign}};
2
3#[derive(serde::Deserialize, serde::Serialize)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum Solfa {
18 C,
20 D,
22 E,
24 F,
26 G,
28 A,
30 B,
32}
33
34impl AddAssign<i32> for Solfa {
35 fn add_assign(&mut self, rhs: i32) {
36 let so = self.score_offset() + rhs;
37 if Solfa::B.score_offset() < so {
38 panic!("Solfa overflow");
39 }
40 *self = Solfa::from_score_offset(so);
41 }
42}
43
44impl SubAssign<i32> for Solfa {
45 fn sub_assign(&mut self, rhs: i32) {
46 let so = self.score_offset() - rhs;
47 if so < Solfa::C.score_offset() {
48 panic!("Solfa overflow");
49 }
50 *self = Solfa::from_score_offset(so);
51 }
52}
53
54impl Solfa {
55 pub const ALL: &'static [Solfa] = &[Self::C, Self::D, Self::E, Self::F, Self::G, Self::A, Self::B];
57
58 pub const fn score_offset(self) -> i32 {
62 match self {
63 Self::C => 0,
64 Self::D => 1,
65 Self::E => 2,
66 Self::F => 3,
67 Self::G => 4,
68 Self::A => 5,
69 Self::B => 6,
70 }
71 }
72
73 pub const fn pitch_offset(self) -> i32 {
78 match self {
79 Self::C => 0,
80 Self::D => 2,
81 Self::E => 4,
82 Self::F => 5,
83 Self::G => 7,
84 Self::A => 9,
85 Self::B => 11,
86 }
87 }
88
89 pub fn from_score_offset(offset: i32) -> Solfa {
97 if offset < Self::C.score_offset() {
98 Self::C
99 } else if offset > Self::B.score_offset() {
100 Self::B
101 } else {
102 Self::ALL[offset as usize]
103 }
104 }
105}
106
107impl fmt::Display for Solfa {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match *self {
110 Solfa::C => write!(f, "C"),
111 Solfa::D => write!(f, "D"),
112 Solfa::E => write!(f, "E"),
113 Solfa::F => write!(f, "F"),
114 Solfa::G => write!(f, "G"),
115 Solfa::A => write!(f, "A"),
116 Solfa::B => write!(f, "B"),
117 }
118 }
119}
120#[cfg(test)]
121mod tests {
122 use crate::solfa::Solfa;
123
124 #[test]
125 fn score_offset_is_valid() {
126 assert_eq!(Solfa::C.score_offset(), 0);
127 assert_eq!(Solfa::D.score_offset(), 1);
128 assert_eq!(Solfa::E.score_offset(), 2);
129 assert_eq!(Solfa::F.score_offset(), 3);
130 assert_eq!(Solfa::G.score_offset(), 4);
131 assert_eq!(Solfa::A.score_offset(), 5);
132 assert_eq!(Solfa::B.score_offset(), 6);
133 }
134
135 #[test]
136 fn pitch_offset_is_valid() {
137 assert_eq!(Solfa::C.pitch_offset(), 0);
138 assert_eq!(Solfa::D.pitch_offset(), 2);
139 assert_eq!(Solfa::E.pitch_offset(), 4);
140 assert_eq!(Solfa::F.pitch_offset(), 5);
141 assert_eq!(Solfa::G.pitch_offset(), 7);
142 assert_eq!(Solfa::A.pitch_offset(), 9);
143 assert_eq!(Solfa::B.pitch_offset(), 11);
144 }
145
146 #[test]
147 fn from_score_offset() {
148 assert_eq!(Solfa::from_score_offset(-1), Solfa::C);
149 assert_eq!(Solfa::from_score_offset(0), Solfa::C);
150 assert_eq!(Solfa::from_score_offset(1), Solfa::D);
151 assert_eq!(Solfa::from_score_offset(2), Solfa::E);
152 assert_eq!(Solfa::from_score_offset(3), Solfa::F);
153 assert_eq!(Solfa::from_score_offset(4), Solfa::G);
154 assert_eq!(Solfa::from_score_offset(5), Solfa::A);
155 assert_eq!(Solfa::from_score_offset(6), Solfa::B);
156 assert_eq!(Solfa::from_score_offset(7), Solfa::B);
157 }
158
159 #[test]
160 fn all() {
161 assert_eq!(Solfa::ALL[0], Solfa::C);
162 assert_eq!(Solfa::ALL[1], Solfa::D);
163 assert_eq!(Solfa::ALL[2], Solfa::E);
164 assert_eq!(Solfa::ALL[3], Solfa::F);
165 assert_eq!(Solfa::ALL[4], Solfa::G);
166 assert_eq!(Solfa::ALL[5], Solfa::A);
167 assert_eq!(Solfa::ALL[6], Solfa::B);
168 assert_eq!(Solfa::ALL.len(), 7);
169 }
170
171 #[test]
172 fn add_assign() {
173 let mut solfa = Solfa::C;
174 solfa += 1;
175 assert_eq!(solfa, Solfa::D);
176 solfa += 2;
177 assert_eq!(solfa, Solfa::F);
178 }
179
180 #[test]
181 #[should_panic]
182 fn add_assign_error() {
183 let mut solfa = Solfa::B;
184 solfa += 1;
185 }
186
187 #[test]
188 fn sub_assign() {
189 let mut solfa = Solfa::B;
190 solfa -= 1;
191 assert_eq!(solfa, Solfa::A);
192 solfa -= 2;
193 assert_eq!(solfa, Solfa::F);
194 }
195
196 #[test]
197 #[should_panic]
198 fn sub_assign_error() {
199 let mut solfa = Solfa::C;
200 solfa -= 1;
201 }
202}