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