1pub(crate) mod generalnote;
2pub(crate) mod notrest;
3
4use crate::defaults::{FloatType, IntegerType};
5use crate::duration::Duration;
6use crate::error::Result;
7use crate::pitch::Pitch;
8
9use generalnote::GeneralNoteTrait;
10use notrest::NotRest;
11use std::fmt::{Display, Formatter};
12use std::str::FromStr;
13
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Note {
18 notrest: NotRest,
19 pub(crate) _pitch: Pitch,
20}
21
22impl Note {
23 pub fn from_name(name: impl Into<String>) -> Result<Self> {
25 Self::new(Option::<Pitch>::None, None, None, Some(name.into()))
26 }
27
28 pub fn from_pitch(pitch: Pitch) -> Result<Self> {
30 Self::new(Some(pitch), None, None, None)
31 }
32
33 pub fn pitch(&self) -> &Pitch {
35 &self._pitch
36 }
37
38 pub fn pitch_name(&self) -> String {
40 self._pitch.name()
41 }
42
43 pub fn pitch_name_with_octave(&self) -> String {
45 self._pitch.name_with_octave()
46 }
47
48 pub fn duration(&self) -> Option<&Duration> {
50 self.notrest.duration().as_ref()
51 }
52
53 pub fn set_duration(&mut self, duration: Duration) {
55 self.notrest.set_duration(&duration);
56 }
57
58 pub fn with_duration(mut self, duration: Duration) -> Self {
60 self.set_duration(duration);
61 self
62 }
63
64 pub(crate) fn new<T>(
65 pitch: Option<T>,
66 duration: Option<Duration>,
67 name: Option<String>,
68 name_with_octave: Option<String>,
69 ) -> Result<Self>
70 where
71 T: IntoPitch,
72 {
73 let _pitch = match pitch {
74 Some(pitch) => pitch.into_pitch(),
75 None => Ok({
76 let name = match name_with_octave {
77 Some(name_with_octave) => name_with_octave,
78 None => match name {
79 Some(name) => name,
80 None => "C4".to_string(),
81 },
82 };
83
84 Pitch::from_name(name)?
85 }),
86 }?;
87
88 Ok(Self {
89 notrest: NotRest::new(duration),
90 _pitch,
91 })
92 }
93}
94
95impl GeneralNoteTrait for Note {
96 fn duration(&self) -> &Option<Duration> {
97 self.notrest.duration()
98 }
99
100 fn set_duration(&mut self, duration: &Duration) {
101 self.notrest.set_duration(duration);
102 }
103}
104
105impl FromStr for Note {
106 type Err = crate::error::Error;
107
108 fn from_str(value: &str) -> Result<Self> {
109 Self::from_name(value)
110 }
111}
112
113impl TryFrom<&str> for Note {
114 type Error = crate::error::Error;
115
116 fn try_from(value: &str) -> Result<Self> {
117 Self::from_name(value)
118 }
119}
120
121impl TryFrom<String> for Note {
122 type Error = crate::error::Error;
123
124 fn try_from(value: String) -> Result<Self> {
125 Self::from_name(value)
126 }
127}
128
129impl TryFrom<Pitch> for Note {
130 type Error = crate::error::Error;
131
132 fn try_from(value: Pitch) -> Result<Self> {
133 Self::from_pitch(value)
134 }
135}
136
137impl TryFrom<&Pitch> for Note {
138 type Error = crate::error::Error;
139
140 fn try_from(value: &Pitch) -> Result<Self> {
141 Self::from_pitch(value.clone())
142 }
143}
144
145impl TryFrom<IntegerType> for Note {
146 type Error = crate::error::Error;
147
148 fn try_from(value: IntegerType) -> Result<Self> {
149 Note::new(Some(value), None, None, None)
150 }
151}
152
153impl Display for Note {
154 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155 write!(f, "{}", self.pitch_name_with_octave())
156 }
157}
158
159pub trait IntoNote {
164 const FROM_INTEGER_PITCH: bool = false;
166
167 fn try_into_note(self) -> Result<Note>;
169}
170
171impl IntoNote for Note {
172 fn try_into_note(self) -> Result<Note> {
173 Ok(self)
174 }
175}
176
177impl IntoNote for &Note {
178 fn try_into_note(self) -> Result<Note> {
179 Ok(self.clone())
180 }
181}
182
183impl IntoNote for Pitch {
184 fn try_into_note(self) -> Result<Note> {
185 Note::new(Some(self), None, None, None)
186 }
187}
188
189impl IntoNote for &Pitch {
190 fn try_into_note(self) -> Result<Note> {
191 Note::new(Some(self.clone()), None, None, None)
192 }
193}
194
195impl IntoNote for String {
196 fn try_into_note(self) -> Result<Note> {
197 Note::new(Some(self), None, None, None)
198 }
199}
200
201impl IntoNote for &String {
202 fn try_into_note(self) -> Result<Note> {
203 Note::new(Some(self.to_string()), None, None, None)
204 }
205}
206
207impl IntoNote for &str {
208 fn try_into_note(self) -> Result<Note> {
209 Note::new(Some(self), None, None, None)
210 }
211}
212
213impl IntoNote for IntegerType {
214 const FROM_INTEGER_PITCH: bool = true;
215
216 fn try_into_note(self) -> Result<Note> {
217 Note::new(Some(self), None, None, None)
218 }
219}
220
221pub(crate) trait IntoPitch {
222 fn into_pitch(self) -> Result<Pitch>;
223}
224
225impl IntoPitch for Pitch {
226 fn into_pitch(self) -> Result<Pitch> {
227 Ok(self.clone())
228 }
229}
230
231impl IntoPitch for String {
232 fn into_pitch(self) -> Result<Pitch> {
233 Pitch::from_name(self)
234 }
235}
236
237impl IntoPitch for &str {
238 fn into_pitch(self) -> Result<Pitch> {
239 Pitch::from_name(self)
240 }
241}
242
243impl IntoPitch for IntegerType {
244 fn into_pitch(self) -> Result<Pitch> {
245 Pitch::from_number(self as FloatType)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::{IntoNote, Note};
252 use crate::defaults::IntegerType;
253 use crate::pitch::Pitch;
254
255 #[test]
256 fn into_note_accepts_note_like_inputs() {
257 fn from_integer_pitch<T: IntoNote>() -> bool {
258 T::FROM_INTEGER_PITCH
259 }
260
261 assert!(!from_integer_pitch::<&str>());
262 assert!(from_integer_pitch::<IntegerType>());
263
264 let note = Note::from_name("C4").unwrap();
265 assert_eq!(
266 note.clone()
267 .try_into_note()
268 .unwrap()
269 .pitch_name_with_octave(),
270 "C4"
271 );
272
273 let borrowed_note = Note::from_name("D4").unwrap();
274 assert_eq!(
275 (&borrowed_note)
276 .try_into_note()
277 .unwrap()
278 .pitch_name_with_octave(),
279 "D4"
280 );
281
282 let pitch = Pitch::from_name("E4").unwrap();
283 assert_eq!(
284 pitch.try_into_note().unwrap().pitch_name_with_octave(),
285 "E4"
286 );
287
288 let borrowed_pitch = Pitch::from_name("F4").unwrap();
289 assert_eq!(
290 (&borrowed_pitch)
291 .try_into_note()
292 .unwrap()
293 .pitch_name_with_octave(),
294 "F4"
295 );
296
297 assert_eq!(
298 "G4".to_string()
299 .try_into_note()
300 .unwrap()
301 .pitch_name_with_octave(),
302 "G4"
303 );
304
305 let owned_name = "A4".to_string();
306 assert_eq!(
307 (&owned_name)
308 .try_into_note()
309 .unwrap()
310 .pitch_name_with_octave(),
311 "A4"
312 );
313
314 assert_eq!("B4".try_into_note().unwrap().pitch_name_with_octave(), "B4");
315
316 assert_eq!(
317 (60 as IntegerType)
318 .try_into_note()
319 .unwrap()
320 .pitch_name_with_octave(),
321 "C4"
322 );
323 }
324
325 #[test]
326 fn note_supports_rust_conversion_traits() {
327 let parsed: Note = "C#4".parse().unwrap();
328 assert_eq!(parsed.to_string(), "C#4");
329
330 let from_pitch = Note::try_from(Pitch::from_name("D4").unwrap()).unwrap();
331 assert_eq!(from_pitch.pitch_name_with_octave(), "D4");
332
333 let from_integer = Note::try_from(60 as IntegerType).unwrap();
334 assert_eq!(from_integer.pitch_name_with_octave(), "C4");
335 }
336}