Skip to main content

music21_rs/note/
mod.rs

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/// A pitched note.
15#[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/// The notation a note carries besides its pitch and duration: how it is
24/// tied, drawn, stemmed, coloured, sung and sounded.
25#[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    /// Builds a note from a pitch name such as `"C#4"` or `"E-"`.
41    pub fn from_name(name: impl Into<String>) -> Result<Self> {
42        Pitch::from_name(name).map(Self::from_pitch)
43    }
44
45    /// Builds a note from a pitch-space number, where 60 is middle C.
46    pub fn from_number(number: FloatType) -> Result<Self> {
47        Pitch::from_number(number).map(Self::from_pitch)
48    }
49
50    /// Builds a note from an existing [`Pitch`].
51    pub fn from_pitch(pitch: Pitch) -> Self {
52        Self {
53            pitch,
54            duration: None,
55            notation: Notation::default(),
56        }
57    }
58
59    /// Returns the note's pitch.
60    pub fn pitch(&self) -> &Pitch {
61        &self.pitch
62    }
63
64    /// Sets the note's pitch, keeping its duration and notation: music21's
65    /// `Note.pitch` setter.
66    pub fn set_pitch(&mut self, pitch: Pitch) {
67        self.pitch = pitch;
68    }
69
70    /// Returns the pitch name without an octave, such as `"C#"` or `"E-"`.
71    pub fn pitch_name(&self) -> String {
72        self.pitch.name()
73    }
74
75    /// Returns the pitch name with an octave when one is set.
76    pub fn pitch_name_with_octave(&self) -> String {
77        self.pitch.name_with_octave()
78    }
79
80    /// The pitch's step letter.
81    pub fn step(&self) -> char {
82        self.pitch.step().as_char()
83    }
84
85    /// The pitch's octave, if it has one.
86    pub fn octave(&self) -> crate::defaults::Octave {
87        self.pitch.octave()
88    }
89
90    /// The note's one pitch as a list, the shape a chord's `pitches` has.
91    pub fn pitches(&self) -> Vec<Pitch> {
92        vec![self.pitch.clone()]
93    }
94
95    /// music21's `fullName`: `E-flat in octave 4 Quarter Note`, with the
96    /// duration's name left out when the note has none.
97    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    /// Returns the note duration when one has been assigned.
105    pub fn duration(&self) -> Option<&Duration> {
106        self.duration.as_ref()
107    }
108
109    /// Assigns a duration to the note.
110    pub fn set_duration(&mut self, duration: Duration) {
111        self.duration = Some(duration);
112    }
113
114    /// Returns a copy of this note with the supplied duration.
115    pub fn with_duration(mut self, duration: Duration) -> Self {
116        self.set_duration(duration);
117        self
118    }
119
120    /// Returns this note transposed by the interval, keeping everything but
121    /// its pitch.
122    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    // ---- notation --------------------------------------------------------
131
132    /// The beams joining this note's flags to its neighbours': music21's
133    /// `beams`.
134    pub fn beams(&self) -> &Beams {
135        &self.notation.beams
136    }
137
138    /// The same, to be changed.
139    pub fn beams_mut(&mut self) -> &mut Beams {
140        &mut self.notation.beams
141    }
142
143    /// Replaces the beams.
144    pub fn set_beams(&mut self, beams: Beams) {
145        self.notation.beams = beams;
146    }
147
148    /// The tie joining this note to its neighbours, if any.
149    pub fn tie(&self) -> Option<&Tie> {
150        self.notation.tie.as_ref()
151    }
152
153    /// Ties this note, or unties it with `None`.
154    pub fn set_tie(&mut self, tie: Option<Tie>) {
155        self.notation.tie = tie;
156    }
157
158    /// The shape the note head is drawn with.
159    pub fn notehead(&self) -> Notehead {
160        self.notation.notehead
161    }
162
163    /// Sets the shape the note head is drawn with.
164    pub fn set_notehead(&mut self, notehead: Notehead) {
165        self.notation.notehead = notehead;
166    }
167
168    /// Whether the note head is filled in: `Some(true)` filled,
169    /// `Some(false)` hollow, `None` to let the duration decide, which is
170    /// music21's default.
171    pub fn notehead_fill(&self) -> Option<bool> {
172        self.notation.notehead_fill
173    }
174
175    /// Sets whether the note head is filled in.
176    pub fn set_notehead_fill(&mut self, fill: Option<bool>) {
177        self.notation.notehead_fill = fill;
178    }
179
180    /// Whether the note head is written in parentheses.
181    pub fn notehead_parenthesis(&self) -> bool {
182        self.notation.notehead_parenthesis
183    }
184
185    /// Sets whether the note head is written in parentheses.
186    pub fn set_notehead_parenthesis(&mut self, parenthesis: bool) {
187        self.notation.notehead_parenthesis = parenthesis;
188    }
189
190    /// Which way the stem points.
191    pub fn stem_direction(&self) -> StemDirection {
192        self.notation.stem_direction
193    }
194
195    /// Sets which way the stem points.
196    pub fn set_stem_direction(&mut self, direction: StemDirection) {
197        self.notation.stem_direction = direction;
198    }
199
200    /// The colour the note is written in, if one was said: music21 keeps
201    /// this on the note's style.
202    pub fn color(&self) -> Option<&str> {
203        self.notation.color.as_deref()
204    }
205
206    /// Sets the colour the note is written in.
207    pub fn set_color(&mut self, color: Option<String>) {
208        self.notation.color = color;
209    }
210
211    /// How loud the note is. music21 makes a volume on first access, so this
212    /// answers a default one for a note nobody has marked; see
213    /// [`Self::has_volume_information`] to tell the two apart.
214    pub fn volume(&self) -> Volume {
215        self.notation.volume.clone().unwrap_or_default()
216    }
217
218    /// Sets how loud the note is, or clears it.
219    pub fn set_volume(&mut self, volume: Option<Volume>) {
220        self.notation.volume = volume;
221    }
222
223    /// Whether a volume was ever set on this note: music21's
224    /// `hasVolumeInformation`, which asks only whether the object is there
225    /// and not whether a velocity was written on it, so a bare
226    /// `Volume::new()` set on a note counts.
227    pub fn has_volume_information(&self) -> bool {
228        self.notation.volume.is_some()
229    }
230
231    /// The syllables sung on this note, one per verse.
232    pub fn lyrics(&self) -> &[Lyric] {
233        &self.notation.lyrics
234    }
235
236    /// The syllables sung on this note, for editing in place.
237    pub fn lyrics_mut(&mut self) -> &mut Vec<Lyric> {
238        &mut self.notation.lyrics
239    }
240
241    /// The text of the first verse, with the hyphens of every further verse
242    /// joined by newlines: music21's `lyric`.
243    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    /// Replaces every lyric with one verse of text, or clears them with
258    /// `None`. Newlines start further verses, and hyphens say where each
259    /// syllable falls in its word: music21's `lyric` setter.
260    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    /// Adds a syllable as the next verse: music21's `addLyric`. Hyphens in
274    /// the text say where the syllable falls in its word unless `apply_raw`
275    /// is set, which takes the text as written.
276    /// Adds a syllable as a verse: music21's `addLyric`.
277    ///
278    /// With no `number` it becomes the next verse. With one, it *replaces*
279    /// the text of the verse already carrying that number — leaving where
280    /// that syllable falls in its word alone, as music21's plain text
281    /// assignment does — and only becomes a new verse when no verse has it.
282    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    /// Puts a syllable in front of the verse at `index`, moving the verses
310    /// from there on down a line: music21's `insertLyric`.
311    ///
312    /// An index past the end appends, as inserting into a list does.
313    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    /// A syllable read from text, whose hyphens say where it falls in its
325    /// word unless `apply_raw` takes the text as written.
326    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
387/// Converts a single note-like value into a [`Note`].
388///
389/// This is useful when constructing vectors or other collections that are
390/// later passed to APIs such as `Chord::new`.
391pub trait IntoNote {
392    /// Whether this value came from an integer pitch class or MIDI-like number.
393    const FROM_INTEGER_PITCH: bool = false;
394
395    /// Converts the value into a note.
396    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        // the same number again replaces the text, and leaves where the
607        // syllable falls in its word alone
608        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        // and `lyric` reads the syllables, not their hyphenated spellings
614        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        // an index past the end appends, as inserting into a list does
638        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}