1use crate::defaults::{FloatType, IntegerType};
2use crate::duration::Duration;
3use crate::error::Result;
4use crate::interval::Interval;
5use crate::notation::{Beams, Lyric, Notehead, StemDirection, Syllabic, Tie};
6use crate::pitch::Pitch;
7use crate::volume::Volume;
8
9use std::fmt::{Display, Formatter};
10use std::str::FromStr;
11
12#[derive(Clone, Debug)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[must_use]
16pub struct Note {
17 pub(crate) pitch: Pitch,
18 duration: Option<Duration>,
19 #[cfg_attr(feature = "serde", serde(default))]
20 notation: Notation,
21}
22
23#[derive(Clone, Debug, Default, PartialEq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27struct Notation {
28 tie: Option<Tie>,
29 notehead: Notehead,
30 notehead_fill: Option<bool>,
31 notehead_parenthesis: bool,
32 stem_direction: StemDirection,
33 color: Option<String>,
34 volume: Option<Volume>,
35 lyrics: Vec<Lyric>,
36 beams: Beams,
37}
38
39impl Note {
40 pub fn from_name(name: impl Into<String>) -> Result<Self> {
42 Pitch::from_name(name).map(Self::from_pitch)
43 }
44
45 pub fn from_number(number: FloatType) -> Result<Self> {
47 Pitch::from_number(number).map(Self::from_pitch)
48 }
49
50 pub fn from_pitch(pitch: Pitch) -> Self {
52 Self {
53 pitch,
54 duration: None,
55 notation: Notation::default(),
56 }
57 }
58
59 pub fn pitch(&self) -> &Pitch {
61 &self.pitch
62 }
63
64 pub fn set_pitch(&mut self, pitch: Pitch) {
67 self.pitch = pitch;
68 }
69
70 pub fn pitch_name(&self) -> String {
72 self.pitch.name()
73 }
74
75 pub fn pitch_name_with_octave(&self) -> String {
77 self.pitch.name_with_octave()
78 }
79
80 pub fn step(&self) -> char {
82 self.pitch.step().as_char()
83 }
84
85 pub fn octave(&self) -> crate::defaults::Octave {
87 self.pitch.octave()
88 }
89
90 pub fn pitches(&self) -> Vec<Pitch> {
92 vec![self.pitch.clone()]
93 }
94
95 pub fn full_name(&self) -> String {
98 match self.duration.as_ref() {
99 Some(duration) => format!("{} {} Note", self.pitch.full_name(), duration.full_name()),
100 None => format!("{} Note", self.pitch.full_name()),
101 }
102 }
103
104 pub fn duration(&self) -> Option<&Duration> {
106 self.duration.as_ref()
107 }
108
109 pub fn set_duration(&mut self, duration: Duration) {
111 self.duration = Some(duration);
112 }
113
114 pub fn with_duration(mut self, duration: Duration) -> Self {
116 self.set_duration(duration);
117 self
118 }
119
120 pub fn transpose(&self, interval: &Interval) -> Result<Self> {
123 Ok(Self {
124 pitch: interval.transpose_pitch(&self.pitch)?,
125 duration: self.duration.clone(),
126 notation: self.notation.clone(),
127 })
128 }
129
130 pub fn beams(&self) -> &Beams {
135 &self.notation.beams
136 }
137
138 pub fn beams_mut(&mut self) -> &mut Beams {
140 &mut self.notation.beams
141 }
142
143 pub fn set_beams(&mut self, beams: Beams) {
145 self.notation.beams = beams;
146 }
147
148 pub fn tie(&self) -> Option<&Tie> {
150 self.notation.tie.as_ref()
151 }
152
153 pub fn set_tie(&mut self, tie: Option<Tie>) {
155 self.notation.tie = tie;
156 }
157
158 pub fn notehead(&self) -> Notehead {
160 self.notation.notehead
161 }
162
163 pub fn set_notehead(&mut self, notehead: Notehead) {
165 self.notation.notehead = notehead;
166 }
167
168 pub fn notehead_fill(&self) -> Option<bool> {
172 self.notation.notehead_fill
173 }
174
175 pub fn set_notehead_fill(&mut self, fill: Option<bool>) {
177 self.notation.notehead_fill = fill;
178 }
179
180 pub fn notehead_parenthesis(&self) -> bool {
182 self.notation.notehead_parenthesis
183 }
184
185 pub fn set_notehead_parenthesis(&mut self, parenthesis: bool) {
187 self.notation.notehead_parenthesis = parenthesis;
188 }
189
190 pub fn stem_direction(&self) -> StemDirection {
192 self.notation.stem_direction
193 }
194
195 pub fn set_stem_direction(&mut self, direction: StemDirection) {
197 self.notation.stem_direction = direction;
198 }
199
200 pub fn color(&self) -> Option<&str> {
203 self.notation.color.as_deref()
204 }
205
206 pub fn set_color(&mut self, color: Option<String>) {
208 self.notation.color = color;
209 }
210
211 pub fn volume(&self) -> Volume {
215 self.notation.volume.clone().unwrap_or_default()
216 }
217
218 pub fn set_volume(&mut self, volume: Option<Volume>) {
220 self.notation.volume = volume;
221 }
222
223 pub fn has_volume_information(&self) -> bool {
228 self.notation.volume.is_some()
229 }
230
231 pub fn lyrics(&self) -> &[Lyric] {
233 &self.notation.lyrics
234 }
235
236 pub fn lyrics_mut(&mut self) -> &mut Vec<Lyric> {
238 &mut self.notation.lyrics
239 }
240
241 pub fn lyric(&self) -> Option<String> {
244 if self.notation.lyrics.is_empty() {
245 return None;
246 }
247 Some(
248 self.notation
249 .lyrics
250 .iter()
251 .map(Lyric::text)
252 .collect::<Vec<_>>()
253 .join("\n"),
254 )
255 }
256
257 pub fn set_lyric(&mut self, lyric: Option<&str>) -> Result<()> {
261 self.notation.lyrics.clear();
262 let Some(lyric) = lyric else {
263 return Ok(());
264 };
265 for (index, line) in lyric.split('\n').enumerate() {
266 let mut parsed = Lyric::from_raw_text(line);
267 parsed.set_number(index as IntegerType + 1);
268 self.notation.lyrics.push(parsed);
269 }
270 Ok(())
271 }
272
273 pub fn add_lyric(
283 &mut self,
284 text: &str,
285 number: Option<IntegerType>,
286 apply_raw: bool,
287 ) -> Result<()> {
288 let Some(number) = number else {
289 let mut lyric = Self::build_lyric(text, apply_raw);
290 lyric.set_number(self.notation.lyrics.len() as IntegerType + 1);
291 self.notation.lyrics.push(lyric);
292 return Ok(());
293 };
294 if let Some(existing) = self
295 .notation
296 .lyrics
297 .iter_mut()
298 .find(|lyric| lyric.number() == number)
299 {
300 existing.set_text(text);
301 return Ok(());
302 }
303 let mut lyric = Self::build_lyric(text, apply_raw);
304 lyric.set_number(number);
305 self.notation.lyrics.push(lyric);
306 Ok(())
307 }
308
309 pub fn insert_lyric(&mut self, text: &str, index: usize, apply_raw: bool) -> Result<()> {
314 let index = index.min(self.notation.lyrics.len());
315 for (offset, lyric) in self.notation.lyrics[index..].iter_mut().enumerate() {
316 lyric.set_number(index as IntegerType + offset as IntegerType + 2);
317 }
318 let mut lyric = Self::build_lyric(text, apply_raw);
319 lyric.set_number(index as IntegerType + 1);
320 self.notation.lyrics.insert(index, lyric);
321 Ok(())
322 }
323
324 fn build_lyric(text: &str, apply_raw: bool) -> Lyric {
327 if apply_raw {
328 let mut lyric = Lyric::new(text);
329 lyric.set_syllabic(Syllabic::Single);
330 lyric
331 } else {
332 Lyric::from_raw_text(text)
333 }
334 }
335}
336
337impl FromStr for Note {
338 type Err = crate::error::Error;
339
340 fn from_str(value: &str) -> Result<Self> {
341 Self::from_name(value)
342 }
343}
344
345impl TryFrom<&str> for Note {
346 type Error = crate::error::Error;
347
348 fn try_from(value: &str) -> Result<Self> {
349 Self::from_name(value)
350 }
351}
352
353impl TryFrom<String> for Note {
354 type Error = crate::error::Error;
355
356 fn try_from(value: String) -> Result<Self> {
357 Self::from_name(value)
358 }
359}
360
361impl From<Pitch> for Note {
362 fn from(value: Pitch) -> Self {
363 Self::from_pitch(value)
364 }
365}
366
367impl From<&Pitch> for Note {
368 fn from(value: &Pitch) -> Self {
369 Self::from_pitch(value.clone())
370 }
371}
372
373impl TryFrom<IntegerType> for Note {
374 type Error = crate::error::Error;
375
376 fn try_from(value: IntegerType) -> Result<Self> {
377 Self::from_number(value as FloatType)
378 }
379}
380
381impl Display for Note {
382 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383 write!(f, "{}", self.pitch_name_with_octave())
384 }
385}
386
387pub trait IntoNote {
392 const FROM_INTEGER_PITCH: bool = false;
394
395 fn try_into_note(self) -> Result<Note>;
397}
398
399impl IntoNote for Note {
400 fn try_into_note(self) -> Result<Note> {
401 Ok(self)
402 }
403}
404
405impl IntoNote for &Note {
406 fn try_into_note(self) -> Result<Note> {
407 Ok(self.clone())
408 }
409}
410
411impl IntoNote for Pitch {
412 fn try_into_note(self) -> Result<Note> {
413 Ok(Note::from_pitch(self))
414 }
415}
416
417impl IntoNote for &Pitch {
418 fn try_into_note(self) -> Result<Note> {
419 Ok(Note::from_pitch(self.clone()))
420 }
421}
422
423impl IntoNote for String {
424 fn try_into_note(self) -> Result<Note> {
425 Note::from_name(self)
426 }
427}
428
429impl IntoNote for &String {
430 fn try_into_note(self) -> Result<Note> {
431 Note::from_name(self.as_str())
432 }
433}
434
435impl IntoNote for &str {
436 fn try_into_note(self) -> Result<Note> {
437 Note::from_name(self)
438 }
439}
440
441impl IntoNote for IntegerType {
442 const FROM_INTEGER_PITCH: bool = true;
443
444 fn try_into_note(self) -> Result<Note> {
445 Note::from_number(self as FloatType)
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 #[test]
452 fn a_note_is_built_from_a_name_or_a_pitch_and_carries_beams_and_lyrics() {
453 use super::Note;
454 use crate::notation::{BeamType, Beams, Lyric};
455 use crate::pitch::Pitch;
456
457 let note = Note::try_from("E-4".to_string()).unwrap();
458 assert_eq!(note.pitch_name(), "E-");
459 let pitch = Pitch::from_name("G#3").unwrap();
460 let mut from_pitch = Note::from(&pitch);
461 assert_eq!(from_pitch.pitch_name(), "G#");
462 assert_eq!(from_pitch.notehead_fill(), None);
463 from_pitch.set_notehead_fill(Some(true));
464 assert_eq!(from_pitch.notehead_fill(), Some(true));
465
466 assert!(from_pitch.beams().is_empty());
467 from_pitch.beams_mut().append(BeamType::Start, None);
468 assert_eq!(from_pitch.beams().beams().len(), 1);
469 from_pitch.set_beams(Beams::default());
470 assert!(from_pitch.beams().is_empty());
471
472 from_pitch.lyrics_mut().push(Lyric::new("la"));
473 assert_eq!(from_pitch.lyrics().len(), 1);
474 assert_eq!(from_pitch.lyrics()[0].text(), "la");
475 }
476
477 #[test]
478 fn full_name_step_and_octave_match_music21() {
479 let flat = Note::from_name("E-4").unwrap();
480 assert_eq!(flat.full_name(), "E-flat in octave 4 Note");
481 assert_eq!(flat.step(), 'E');
482 assert_eq!(flat.octave(), Some(4));
483 assert_eq!(flat.pitches()[0].name_with_octave(), "E-4");
484 let dotted = Note::from_name("C#5")
485 .unwrap()
486 .with_duration(crate::Duration::new(1.5).unwrap());
487 assert_eq!(
488 dotted.full_name(),
489 "C-sharp in octave 5 Dotted Quarter Note"
490 );
491 let bare = Note::from_name("G").unwrap();
492 assert_eq!(bare.octave(), None);
493 assert_eq!(bare.full_name(), "G Note");
494 }
495 use super::{IntoNote, Note};
496 use crate::defaults::IntegerType;
497 use crate::pitch::Pitch;
498
499 #[test]
500 fn into_note_accepts_note_like_inputs() {
501 fn from_integer_pitch<T: IntoNote>() -> bool {
502 T::FROM_INTEGER_PITCH
503 }
504
505 assert!(!from_integer_pitch::<&str>());
506 assert!(from_integer_pitch::<IntegerType>());
507
508 let note = Note::from_name("C4").unwrap();
509 assert_eq!(
510 note.clone()
511 .try_into_note()
512 .unwrap()
513 .pitch_name_with_octave(),
514 "C4"
515 );
516
517 let borrowed_note = Note::from_name("D4").unwrap();
518 assert_eq!(
519 (&borrowed_note)
520 .try_into_note()
521 .unwrap()
522 .pitch_name_with_octave(),
523 "D4"
524 );
525
526 let pitch = Pitch::from_name("E4").unwrap();
527 assert_eq!(
528 pitch.try_into_note().unwrap().pitch_name_with_octave(),
529 "E4"
530 );
531
532 let borrowed_pitch = Pitch::from_name("F4").unwrap();
533 assert_eq!(
534 (&borrowed_pitch)
535 .try_into_note()
536 .unwrap()
537 .pitch_name_with_octave(),
538 "F4"
539 );
540
541 assert_eq!(
542 "G4".to_string()
543 .try_into_note()
544 .unwrap()
545 .pitch_name_with_octave(),
546 "G4"
547 );
548
549 let owned_name = "A4".to_string();
550 assert_eq!(
551 (&owned_name)
552 .try_into_note()
553 .unwrap()
554 .pitch_name_with_octave(),
555 "A4"
556 );
557
558 assert_eq!("B4".try_into_note().unwrap().pitch_name_with_octave(), "B4");
559
560 assert_eq!(
561 (60 as IntegerType)
562 .try_into_note()
563 .unwrap()
564 .pitch_name_with_octave(),
565 "C4"
566 );
567 }
568
569 #[test]
570 fn transposing_a_note_keeps_its_duration() {
571 let note = Note::from_name("C4")
572 .unwrap()
573 .with_duration(crate::Duration::half());
574 let moved = note
575 .transpose(&crate::Interval::from_name("M3").unwrap())
576 .unwrap();
577 assert_eq!(moved.pitch_name_with_octave(), "E4");
578 assert_eq!(moved.duration().unwrap().quarter_length(), 2.0);
579 }
580
581 #[test]
582 fn setting_a_pitch_keeps_the_duration_and_the_notation() {
583 let mut note = Note::from_name("C4")
584 .unwrap()
585 .with_duration(crate::Duration::half());
586 note.set_notehead(crate::Notehead::Diamond);
587 note.set_pitch(crate::Pitch::from_name("E-5").unwrap());
588 assert_eq!(note.pitch_name_with_octave(), "E-5");
589 assert_eq!(note.duration().unwrap().quarter_length(), 2.0);
590 assert_eq!(note.notehead(), crate::Notehead::Diamond);
591 }
592
593 #[test]
594 fn a_numbered_lyric_replaces_the_verse_that_has_that_number() {
595 let mut note = Note::from_name("C4").unwrap();
596 note.add_lyric("hello", None, false).unwrap();
597 note.add_lyric("bye", Some(3), false).unwrap();
598 assert_eq!(
599 note.lyrics()
600 .iter()
601 .map(|lyric| (lyric.number(), lyric.text()))
602 .collect::<Vec<_>>(),
603 [(1, "hello".to_string()), (3, "bye".to_string())]
604 );
605
606 note.add_lyric("ciao", Some(3), false).unwrap();
609 assert_eq!(note.lyrics().len(), 2);
610 assert_eq!(note.lyrics()[1].text(), "ciao");
611 assert_eq!(note.lyrics()[1].number(), 3);
612
613 let mut hyphenated = Note::from_name("C4").unwrap();
615 hyphenated.set_lyric(Some("hel-")).unwrap();
616 assert_eq!(hyphenated.lyrics()[0].raw_text(), "hel-");
617 assert_eq!(hyphenated.lyric().as_deref(), Some("hel"));
618 }
619
620 #[test]
621 fn inserting_a_lyric_moves_the_verses_after_it_down() {
622 let mut note = Note::from_name("C4").unwrap();
623 note.add_lyric("second", None, false).unwrap();
624 note.insert_lyric("first", 0, false).unwrap();
625 note.insert_lyric("newSecond", 1, false).unwrap();
626 assert_eq!(
627 note.lyrics()
628 .iter()
629 .map(|lyric| (lyric.number(), lyric.text()))
630 .collect::<Vec<_>>(),
631 [
632 (1, "first".to_string()),
633 (2, "newSecond".to_string()),
634 (3, "second".to_string())
635 ]
636 );
637 note.insert_lyric("last", 99, false).unwrap();
639 assert_eq!(note.lyrics()[3].number(), 4);
640 assert_eq!(note.lyrics()[3].text(), "last");
641 }
642
643 #[test]
644 fn note_supports_rust_conversion_traits() {
645 let parsed: Note = "C#4".parse().unwrap();
646 assert_eq!(parsed.to_string(), "C#4");
647
648 let from_pitch = Note::from(Pitch::from_name("D4").unwrap());
649 assert_eq!(from_pitch.pitch_name_with_octave(), "D4");
650
651 let from_integer = Note::try_from(60 as IntegerType).unwrap();
652 assert_eq!(from_integer.pitch_name_with_octave(), "C4");
653 }
654}