1pub(crate) mod generalnote;
2pub(crate) mod notrest;
3
4use crate::defaults::IntegerType;
5use crate::duration::Duration;
6use crate::error::Result;
7use crate::pitch::Pitch;
8
9use generalnote::GeneralNoteTrait;
10use notrest::NotRest;
11use std::collections::HashMap;
12use std::fmt::{Display, Formatter};
13use std::str::FromStr;
14use std::sync::{Arc, Mutex};
15
16#[derive(Clone, Debug)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Note {
20 notrest: NotRest,
21 pub(crate) _pitch: Pitch,
22 #[cfg_attr(feature = "serde", serde(skip))]
23 _cache: Arc<Mutex<HashMap<String, String>>>,
24}
25
26impl Note {
27 pub fn from_name(name: impl Into<String>) -> Result<Self> {
29 Self::new(Option::<Pitch>::None, None, None, Some(name.into()))
30 }
31
32 pub fn from_pitch(pitch: Pitch) -> Result<Self> {
34 Self::new(Some(pitch), None, None, None)
35 }
36
37 pub fn pitch(&self) -> &Pitch {
39 &self._pitch
40 }
41
42 pub fn pitch_name(&self) -> String {
44 self._pitch.name()
45 }
46
47 pub fn pitch_name_with_octave(&self) -> String {
49 self._pitch.name_with_octave()
50 }
51
52 pub fn duration(&self) -> Option<&Duration> {
54 self.notrest.duration().as_ref()
55 }
56
57 pub fn set_duration(&mut self, duration: Duration) {
59 self.notrest.set_duration(&duration);
60 }
61
62 pub fn with_duration(mut self, duration: Duration) -> Self {
64 self.set_duration(duration);
65 self
66 }
67
68 pub(crate) fn new<T>(
69 pitch: Option<T>,
70 duration: Option<Duration>,
71 name: Option<String>,
72 name_with_octave: Option<String>,
73 ) -> Result<Self>
74 where
75 T: IntoPitch,
76 {
77 let _pitch = match pitch {
78 Some(pitch) => pitch.into_pitch(),
79 None => Ok({
80 let name = match name_with_octave {
81 Some(name_with_octave) => name_with_octave,
82 None => match name {
83 Some(name) => name,
84 None => "C4".to_string(),
85 },
86 };
87
88 Pitch::new(
89 Some(name),
90 None,
91 None,
92 Option::<IntegerType>::None,
93 Option::<IntegerType>::None,
94 None,
95 None,
96 None,
97 None,
98 )?
99 }),
100 }?;
101
102 Ok(Self {
103 notrest: NotRest::new(duration),
104 _pitch,
105 _cache: Arc::new(Mutex::new(HashMap::new())),
106 })
107 }
108
109 pub(crate) fn get_super(&self) -> &NotRest {
110 &self.notrest
111 }
112
113 pub(crate) fn pitch_changed(&self) {
114 {
115 let mut cache = match self._cache.lock() {
116 Ok(cache) => cache,
117 Err(err) => err.into_inner(),
118 };
119 cache.clear();
120 }
121
122 if let Some(chord) = &self.notrest._chord_attached {
123 chord.clear_cache();
124 }
125 }
126
127 #[cfg(test)]
128 fn insert_cache_value_for_test(&self, key: &str, value: &str) {
129 let mut cache = match self._cache.lock() {
130 Ok(cache) => cache,
131 Err(err) => err.into_inner(),
132 };
133 cache.insert(key.to_string(), value.to_string());
134 }
135
136 #[cfg(test)]
137 fn cache_len_for_test(&self) -> usize {
138 let cache = match self._cache.lock() {
139 Ok(cache) => cache,
140 Err(err) => err.into_inner(),
141 };
142 cache.len()
143 }
144}
145
146impl GeneralNoteTrait for Note {
147 fn duration(&self) -> &Option<Duration> {
148 self.notrest.duration()
149 }
150
151 fn set_duration(&mut self, duration: &Duration) {
152 self.notrest.set_duration(duration);
153 }
154}
155
156impl FromStr for Note {
157 type Err = crate::error::Error;
158
159 fn from_str(value: &str) -> Result<Self> {
160 Self::from_name(value)
161 }
162}
163
164impl TryFrom<&str> for Note {
165 type Error = crate::error::Error;
166
167 fn try_from(value: &str) -> Result<Self> {
168 Self::from_name(value)
169 }
170}
171
172impl TryFrom<String> for Note {
173 type Error = crate::error::Error;
174
175 fn try_from(value: String) -> Result<Self> {
176 Self::from_name(value)
177 }
178}
179
180impl TryFrom<Pitch> for Note {
181 type Error = crate::error::Error;
182
183 fn try_from(value: Pitch) -> Result<Self> {
184 Self::from_pitch(value)
185 }
186}
187
188impl TryFrom<&Pitch> for Note {
189 type Error = crate::error::Error;
190
191 fn try_from(value: &Pitch) -> Result<Self> {
192 Self::from_pitch(value.clone())
193 }
194}
195
196impl TryFrom<IntegerType> for Note {
197 type Error = crate::error::Error;
198
199 fn try_from(value: IntegerType) -> Result<Self> {
200 Note::new(Some(value), None, None, None)
201 }
202}
203
204impl Display for Note {
205 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
206 write!(f, "{}", self.pitch_name_with_octave())
207 }
208}
209
210pub trait IntoNote {
215 const FROM_INTEGER_PITCH: bool = false;
217
218 fn try_into_note(self) -> Result<Note>;
220}
221
222impl IntoNote for Note {
223 fn try_into_note(self) -> Result<Note> {
224 Ok(self)
225 }
226}
227
228impl IntoNote for &Note {
229 fn try_into_note(self) -> Result<Note> {
230 Ok(self.clone())
231 }
232}
233
234impl IntoNote for Pitch {
235 fn try_into_note(self) -> Result<Note> {
236 Note::new(Some(self), None, None, None)
237 }
238}
239
240impl IntoNote for &Pitch {
241 fn try_into_note(self) -> Result<Note> {
242 Note::new(Some(self.clone()), None, None, None)
243 }
244}
245
246impl IntoNote for String {
247 fn try_into_note(self) -> Result<Note> {
248 Note::new(Some(self), None, None, None)
249 }
250}
251
252impl IntoNote for &String {
253 fn try_into_note(self) -> Result<Note> {
254 Note::new(Some(self.to_string()), None, None, None)
255 }
256}
257
258impl IntoNote for &str {
259 fn try_into_note(self) -> Result<Note> {
260 Note::new(Some(self), None, None, None)
261 }
262}
263
264impl IntoNote for IntegerType {
265 const FROM_INTEGER_PITCH: bool = true;
266
267 fn try_into_note(self) -> Result<Note> {
268 Note::new(Some(self), None, None, None)
269 }
270}
271
272pub(crate) trait IntoPitch {
273 fn into_pitch(self) -> Result<Pitch>;
274}
275
276impl IntoPitch for Pitch {
277 fn into_pitch(self) -> Result<Pitch> {
278 Ok(self.clone())
279 }
280}
281
282impl IntoPitch for String {
283 fn into_pitch(self) -> Result<Pitch> {
284 Pitch::new(
285 Some(self),
286 None,
287 None,
288 Option::<IntegerType>::None,
289 Option::<IntegerType>::None,
290 None,
291 None,
292 None,
293 None,
294 )
295 }
296}
297
298impl IntoPitch for &str {
299 fn into_pitch(self) -> Result<Pitch> {
300 Pitch::new(
301 Some(self),
302 None,
303 None,
304 Option::<IntegerType>::None,
305 Option::<IntegerType>::None,
306 None,
307 None,
308 None,
309 None,
310 )
311 }
312}
313
314impl IntoPitch for IntegerType {
315 fn into_pitch(self) -> Result<Pitch> {
316 Pitch::new(
317 Some(self),
318 None,
319 None,
320 Option::<IntegerType>::None,
321 Option::<IntegerType>::None,
322 None,
323 None,
324 None,
325 None,
326 )
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::{IntoNote, Note};
333 use crate::chord::chordbase::ChordBase;
334 use crate::defaults::IntegerType;
335 use crate::pitch::Pitch;
336 use std::sync::Arc;
337
338 #[test]
339 fn pitch_changed_clears_note_cache() {
340 let note = Note::new(Some("C4"), None, None, None).unwrap();
341
342 note.insert_cache_value_for_test("pitchName", "C");
343 assert_eq!(note.cache_len_for_test(), 1);
344
345 note.pitch_changed();
346
347 assert_eq!(note.cache_len_for_test(), 0);
348 }
349
350 #[test]
351 fn pitch_changed_clears_attached_chord_cache() {
352 let chord = ChordBase::new(Some("C E G"), &None).unwrap();
353 chord.insert_cache_value_for_test("analysis", "major triad");
354 assert_eq!(chord.cache_len_for_test(), 1);
355
356 let mut note = Note::new(Some("E4"), None, None, None).unwrap();
357 note.notrest._chord_attached = Some(Arc::clone(&chord));
358
359 note.pitch_changed();
360
361 assert_eq!(chord.cache_len_for_test(), 0);
362 }
363
364 #[test]
365 fn into_note_accepts_note_like_inputs() {
366 fn from_integer_pitch<T: IntoNote>() -> bool {
367 T::FROM_INTEGER_PITCH
368 }
369
370 assert!(!from_integer_pitch::<&str>());
371 assert!(from_integer_pitch::<IntegerType>());
372
373 let note = Note::from_name("C4").unwrap();
374 assert_eq!(
375 note.clone()
376 .try_into_note()
377 .unwrap()
378 .pitch_name_with_octave(),
379 "C4"
380 );
381
382 let borrowed_note = Note::from_name("D4").unwrap();
383 assert_eq!(
384 (&borrowed_note)
385 .try_into_note()
386 .unwrap()
387 .pitch_name_with_octave(),
388 "D4"
389 );
390
391 let pitch = Pitch::from_name("E4").unwrap();
392 assert_eq!(
393 pitch.try_into_note().unwrap().pitch_name_with_octave(),
394 "E4"
395 );
396
397 let borrowed_pitch = Pitch::from_name("F4").unwrap();
398 assert_eq!(
399 (&borrowed_pitch)
400 .try_into_note()
401 .unwrap()
402 .pitch_name_with_octave(),
403 "F4"
404 );
405
406 assert_eq!(
407 "G4".to_string()
408 .try_into_note()
409 .unwrap()
410 .pitch_name_with_octave(),
411 "G4"
412 );
413
414 let owned_name = "A4".to_string();
415 assert_eq!(
416 (&owned_name)
417 .try_into_note()
418 .unwrap()
419 .pitch_name_with_octave(),
420 "A4"
421 );
422
423 assert_eq!("B4".try_into_note().unwrap().pitch_name_with_octave(), "B4");
424
425 assert_eq!(
426 (60 as IntegerType)
427 .try_into_note()
428 .unwrap()
429 .pitch_name_with_octave(),
430 "C4"
431 );
432 }
433
434 #[test]
435 fn note_supports_rust_conversion_traits() {
436 let parsed: Note = "C#4".parse().unwrap();
437 assert_eq!(parsed.to_string(), "C#4");
438
439 let from_pitch = Note::try_from(Pitch::from_name("D4").unwrap()).unwrap();
440 assert_eq!(from_pitch.pitch_name_with_octave(), "D4");
441
442 let from_integer = Note::try_from(60 as IntegerType).unwrap();
443 assert_eq!(from_integer.pitch_name_with_octave(), "C4");
444 }
445}