Skip to main content

music21/note/
mod.rs

1use crate::articulations::Articulation;
2use crate::defaults::{FloatType, IntegerType};
3use crate::duration::Duration;
4use crate::error::Result;
5use crate::expressions::Expression;
6use crate::instrument::Instrument;
7use crate::interval::Interval;
8use crate::notation::{Beams, Lyric, NoteSize, Notehead, StemDirection, Tie, verses};
9use crate::pitch::Pitch;
10use crate::volume::Volume;
11
12use std::fmt::{Display, Formatter};
13use std::str::FromStr;
14
15#[derive(Clone, Debug)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17/// A pitched note.
18#[must_use]
19pub struct Note {
20    pub(crate) pitch: Pitch,
21    duration: Option<Duration>,
22    #[cfg_attr(feature = "serde", serde(default))]
23    notation: Notation,
24}
25
26/// The notation a note carries besides its pitch and duration: how it is
27/// tied, drawn, stemmed, coloured, sung and sounded.
28#[derive(Clone, Debug, Default, PartialEq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30struct Notation {
31    tie: Option<Tie>,
32    notehead: Notehead,
33    notehead_fill: Option<bool>,
34    notehead_parenthesis: bool,
35    stem_direction: StemDirection,
36    color: Option<String>,
37    #[cfg_attr(feature = "serde", serde(default))]
38    size: Option<NoteSize>,
39    volume: Option<Volume>,
40    lyrics: Vec<Lyric>,
41    beams: Beams,
42    /// Boxed: an instrument is large and most notes carry none.
43    stored_instrument: Option<Box<Instrument>>,
44    expressions: Vec<Expression>,
45    articulations: Vec<Articulation>,
46    hidden: bool,
47    #[cfg_attr(feature = "serde", serde(default))]
48    id: Option<String>,
49    /// The tick a MIDI file starts it on, before it is quantized: what
50    /// music21 keeps as `editorial.midiTickStart`.
51    #[cfg_attr(feature = "serde", serde(default))]
52    midi_tick_start: crate::display::Drawn<Option<u64>>,
53    /// How far quantizing moved its offset and its length: music21's
54    /// `editorial.offsetQuantizationError` and
55    /// `quarterLengthQuantizationError`.
56    #[cfg_attr(feature = "serde", serde(default))]
57    quantization_errors: crate::display::Drawn<(Option<FloatType>, Option<FloatType>)>,
58}
59
60impl Note {
61    /// Builds a note from a pitch name such as `"C#4"` or `"E-"`.
62    pub fn from_name(name: impl AsRef<str>) -> Result<Self> {
63        Pitch::from_name(name).map(Self::from_pitch)
64    }
65
66    /// Builds a note from a pitch-space number, where 60 is middle C.
67    pub fn from_number(number: FloatType) -> Result<Self> {
68        Pitch::from_number(number).map(Self::from_pitch)
69    }
70
71    /// Builds a note from an existing [`Pitch`].
72    pub fn from_pitch(pitch: Pitch) -> Self {
73        Self {
74            pitch,
75            duration: None,
76            notation: Notation::default(),
77        }
78    }
79
80    /// Returns the note's pitch.
81    pub fn pitch(&self) -> &Pitch {
82        &self.pitch
83    }
84
85    /// Sets the note's pitch, keeping its duration and notation: music21's
86    /// `Note.pitch` setter.
87    pub fn set_pitch(&mut self, pitch: Pitch) {
88        self.pitch = pitch;
89    }
90
91    /// Returns the pitch name without an octave, such as `"C#"` or `"E-"`.
92    pub fn pitch_name(&self) -> String {
93        self.pitch.name()
94    }
95
96    /// Returns the pitch name with an octave when one is set.
97    pub fn pitch_name_with_octave(&self) -> String {
98        self.pitch.name_with_octave()
99    }
100
101    /// The pitch's step letter.
102    pub fn step(&self) -> char {
103        self.pitch.step().as_char()
104    }
105
106    /// The pitch's octave, if it has one.
107    pub fn octave(&self) -> crate::defaults::Octave {
108        self.pitch.octave()
109    }
110
111    /// The note's one pitch as a list, the shape a chord's `pitches` has.
112    pub fn pitches(&self) -> Vec<Pitch> {
113        vec![self.pitch.clone()]
114    }
115
116    /// music21's `fullName`: `E-flat in octave 4 Quarter Note`, with the
117    /// duration's name left out when the note has none.
118    pub fn full_name(&self) -> String {
119        match self.duration.as_ref() {
120            Some(duration) => format!("{} {} Note", self.pitch.full_name(), duration.full_name()),
121            None => format!("{} Note", self.pitch.full_name()),
122        }
123    }
124
125    /// Returns the note duration when one has been assigned.
126    pub fn duration(&self) -> Option<&Duration> {
127        self.duration.as_ref()
128    }
129
130    /// Assigns a duration to the note.
131    pub fn set_duration(&mut self, duration: Duration) {
132        self.duration = Some(duration);
133    }
134
135    /// Returns a copy of this note with the supplied duration.
136    pub fn with_duration(mut self, duration: Duration) -> Self {
137        self.set_duration(duration);
138        self
139    }
140
141    /// This note as a grace note: written as it is, sounding for no time,
142    /// music21's `getGrace`. A grace note is slashed and takes no time, and
143    /// an `appoggiatura` is unslashed and takes its time from the note it
144    /// leans on.
145    ///
146    /// ```
147    /// use music21::{Duration, DurationType, Note};
148    ///
149    /// let note = Note::from_name("G4")?.with_duration(Duration::half());
150    /// let grace = note.grace_note(false);
151    /// let duration = grace.duration().unwrap();
152    /// assert_eq!(duration.quarter_length(), 0.0);
153    /// assert_eq!(duration.components(), [(DurationType::Half, 0)]);
154    /// assert!(duration.grace().unwrap().slash());
155    /// assert!(!note.grace_note(true).duration().unwrap().grace().unwrap().slash());
156    /// # Ok::<(), music21::Error>(())
157    /// ```
158    pub fn grace_note(&self, appoggiatura: bool) -> Self {
159        let written = self.duration.clone().unwrap_or_default();
160        self.clone()
161            .with_duration(written.grace_duration_as(appoggiatura))
162    }
163
164    /// Returns this note transposed by the interval, keeping everything but
165    /// its pitch.
166    pub fn transpose(&self, interval: &Interval) -> Result<Self> {
167        Ok(Self {
168            pitch: interval.transpose_pitch(&self.pitch)?,
169            duration: self.duration.clone(),
170            notation: self.notation.clone(),
171        })
172    }
173
174    // ---- notation --------------------------------------------------------
175
176    /// The beams joining this note's flags to its neighbours': music21's
177    /// `beams`.
178    pub fn beams(&self) -> &Beams {
179        &self.notation.beams
180    }
181
182    /// The same, to be changed.
183    pub fn beams_mut(&mut self) -> &mut Beams {
184        &mut self.notation.beams
185    }
186
187    /// Replaces the beams.
188    pub fn set_beams(&mut self, beams: Beams) {
189        self.notation.beams = beams;
190    }
191
192    /// The tie joining this note to its neighbours, if any.
193    pub fn tie(&self) -> Option<&Tie> {
194        self.notation.tie.as_ref()
195    }
196
197    /// Ties this note, or unties it with `None`.
198    pub fn set_tie(&mut self, tie: Option<Tie>) {
199        self.notation.tie = tie;
200    }
201
202    /// The shape the note head is drawn with.
203    pub fn notehead(&self) -> Notehead {
204        self.notation.notehead
205    }
206
207    /// Sets the shape the note head is drawn with.
208    pub fn set_notehead(&mut self, notehead: Notehead) {
209        self.notation.notehead = notehead;
210    }
211
212    /// Whether the note head is filled in: `Some(true)` filled,
213    /// `Some(false)` hollow, `None` to let the duration decide, which is
214    /// music21's default.
215    pub fn notehead_fill(&self) -> Option<bool> {
216        self.notation.notehead_fill
217    }
218
219    /// Sets whether the note head is filled in.
220    pub fn set_notehead_fill(&mut self, fill: Option<bool>) {
221        self.notation.notehead_fill = fill;
222    }
223
224    /// Whether the note head is written in parentheses.
225    pub fn notehead_parenthesis(&self) -> bool {
226        self.notation.notehead_parenthesis
227    }
228
229    /// Sets whether the note head is written in parentheses.
230    pub fn set_notehead_parenthesis(&mut self, parenthesis: bool) {
231        self.notation.notehead_parenthesis = parenthesis;
232    }
233
234    /// Which way the stem points.
235    pub fn stem_direction(&self) -> StemDirection {
236        self.notation.stem_direction
237    }
238
239    /// Sets which way the stem points.
240    pub fn set_stem_direction(&mut self, direction: StemDirection) {
241        self.notation.stem_direction = direction;
242    }
243
244    /// The colour the note is written in, if one was said: music21 keeps
245    /// this on the note's style.
246    pub fn color(&self) -> Option<&str> {
247        self.notation.color.as_deref()
248    }
249
250    /// Sets the colour the note is written in.
251    pub fn set_color(&mut self, color: Option<String>) {
252        self.notation.color = color;
253    }
254
255    /// How large the note is drawn, where a score says: music21 keeps this
256    /// on the note's style as `noteSize`.
257    pub fn size(&self) -> Option<NoteSize> {
258        self.notation.size
259    }
260
261    /// Says how large the note is drawn.
262    pub fn set_size(&mut self, size: Option<NoteSize>) {
263        self.notation.size = size;
264    }
265
266    /// How loud the note is. music21 makes a volume on first access, so this
267    /// answers a default one for a note nobody has marked; see
268    /// [`Self::has_volume_information`] to tell the two apart.
269    pub fn volume(&self) -> Volume {
270        self.notation.volume.clone().unwrap_or_default()
271    }
272
273    /// Sets how loud the note is, or clears it.
274    pub fn set_volume(&mut self, volume: Option<Volume>) {
275        self.notation.volume = volume;
276    }
277
278    /// The instrument this one note is played on, over whichever is in force
279    /// where it stands: music21's `storedInstrument`.
280    pub fn stored_instrument(&self) -> Option<&Instrument> {
281        self.notation.stored_instrument.as_deref()
282    }
283
284    /// Sets the instrument this one note is played on, or clears it.
285    pub fn set_stored_instrument(&mut self, instrument: Option<Instrument>) {
286        self.notation.stored_instrument = instrument.map(Box::new);
287    }
288
289    /// The instrument that plays this note, as far as the note itself can
290    /// say: music21's `getInstrument`, whose search of the streams around a
291    /// note is a question for whoever holds the stream.
292    pub fn instrument(&self) -> Option<&Instrument> {
293        self.stored_instrument()
294    }
295
296    /// Whether a volume was ever set on this note: music21's
297    /// `hasVolumeInformation`, which asks only whether the object is there
298    /// and not whether a velocity was written on it, so a bare
299    /// `Volume::new()` set on a note counts.
300    pub fn has_volume_information(&self) -> bool {
301        self.notation.volume.is_some()
302    }
303
304    /// The fermatas, ornaments and other marks written on the note:
305    /// music21's `expressions`.
306    pub fn expressions(&self) -> &[Expression] {
307        &self.notation.expressions
308    }
309
310    /// The same, to be changed.
311    pub fn expressions_mut(&mut self) -> &mut Vec<Expression> {
312        &mut self.notation.expressions
313    }
314
315    /// Whether it is left off the printed page, keeping its place and its
316    /// time: music21's `style.hideObjectOnPrint`, which a score uses for a
317    /// rest that only fills out a voice.
318    pub fn hidden(&self) -> bool {
319        self.notation.hidden
320    }
321
322    /// Says whether it is left off the printed page.
323    pub fn set_hidden(&mut self, hidden: bool) {
324        self.notation.hidden = hidden;
325    }
326
327    /// The name the note is known by, where it has been given one:
328    /// music21's `id`, when that is a name and not a number of its own
329    /// making.
330    pub fn id(&self) -> Option<&str> {
331        self.notation.id.as_deref()
332    }
333
334    /// Names the note, or takes its name away.
335    pub fn set_id(&mut self, id: Option<String>) {
336        self.notation.id = id;
337    }
338
339    /// The tick a MIDI file starts the note on: music21's
340    /// `editorial.midiTickStart`.
341    pub fn midi_tick_start(&self) -> Option<u64> {
342        self.notation.midi_tick_start.0
343    }
344
345    /// Says which tick a MIDI file starts the note on.
346    pub fn set_midi_tick_start(&mut self, tick: Option<u64>) {
347        self.notation.midi_tick_start.0 = tick;
348    }
349
350    /// How far quantizing moved the note's offset and its length, where it
351    /// moved them: music21's `editorial.offsetQuantizationError` and
352    /// `quarterLengthQuantizationError`.
353    pub fn quantization_errors(&self) -> (Option<FloatType>, Option<FloatType>) {
354        self.notation.quantization_errors.0
355    }
356
357    /// Says how far quantizing moved the note.
358    pub fn set_quantization_errors(&mut self, errors: (Option<FloatType>, Option<FloatType>)) {
359        self.notation.quantization_errors.0 = errors;
360    }
361
362    /// How the note is played: its accents, staccatos, bowings and
363    /// fingerings, music21's `articulations`.
364    pub fn articulations(&self) -> &[Articulation] {
365        &self.notation.articulations
366    }
367
368    /// The same, to be changed.
369    pub fn articulations_mut(&mut self) -> &mut Vec<Articulation> {
370        &mut self.notation.articulations
371    }
372
373    /// The syllables sung on this note, one per verse.
374    pub fn lyrics(&self) -> &[Lyric] {
375        &self.notation.lyrics
376    }
377
378    /// The syllables sung on this note, for editing in place.
379    pub fn lyrics_mut(&mut self) -> &mut Vec<Lyric> {
380        &mut self.notation.lyrics
381    }
382
383    /// The text of the first verse, with the hyphens of every further verse
384    /// joined by newlines: music21's `lyric`.
385    pub fn lyric(&self) -> Option<String> {
386        verses::joined(&self.notation.lyrics)
387    }
388
389    /// Replaces every lyric with one verse of text, or clears them with
390    /// `None`. Newlines start further verses, and hyphens say where each
391    /// syllable falls in its word: music21's `lyric` setter.
392    pub fn set_lyric(&mut self, lyric: Option<&str>) -> Result<()> {
393        verses::set(&mut self.notation.lyrics, lyric);
394        Ok(())
395    }
396
397    /// Adds a syllable as a verse: music21's `addLyric`.
398    ///
399    /// With no `number` it becomes the next verse. With one, it *replaces*
400    /// the text of the verse already carrying that number — leaving where
401    /// that syllable falls in its word alone, as music21's plain text
402    /// assignment does — and only becomes a new verse when no verse has it.
403    /// Hyphens in the text say where the syllable falls in its word unless
404    /// `apply_raw` is set, which takes the text as written.
405    pub fn add_lyric(
406        &mut self,
407        text: &str,
408        number: Option<IntegerType>,
409        apply_raw: bool,
410    ) -> Result<()> {
411        verses::add(&mut self.notation.lyrics, text, number, apply_raw);
412        Ok(())
413    }
414
415    /// Puts a syllable in front of the verse at `index`, moving the verses
416    /// from there on down a line: music21's `insertLyric`.
417    ///
418    /// An index past the end appends, as inserting into a list does.
419    pub fn insert_lyric(&mut self, text: &str, index: usize, apply_raw: bool) -> Result<()> {
420        verses::insert(&mut self.notation.lyrics, text, index, apply_raw);
421        Ok(())
422    }
423}
424
425impl FromStr for Note {
426    type Err = crate::error::Error;
427
428    fn from_str(value: &str) -> Result<Self> {
429        Self::from_name(value)
430    }
431}
432
433impl TryFrom<&str> for Note {
434    type Error = crate::error::Error;
435
436    fn try_from(value: &str) -> Result<Self> {
437        Self::from_name(value)
438    }
439}
440
441impl TryFrom<String> for Note {
442    type Error = crate::error::Error;
443
444    fn try_from(value: String) -> Result<Self> {
445        Self::from_name(value)
446    }
447}
448
449impl From<Pitch> for Note {
450    fn from(value: Pitch) -> Self {
451        Self::from_pitch(value)
452    }
453}
454
455impl From<&Pitch> for Note {
456    fn from(value: &Pitch) -> Self {
457        Self::from_pitch(value.clone())
458    }
459}
460
461impl TryFrom<IntegerType> for Note {
462    type Error = crate::error::Error;
463
464    fn try_from(value: IntegerType) -> Result<Self> {
465        Self::from_number(value as FloatType)
466    }
467}
468
469impl Display for Note {
470    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
471        write!(f, "{}", self.pitch_name_with_octave())
472    }
473}
474
475/// Converts a single note-like value into a [`Note`].
476///
477/// This is useful when constructing vectors or other collections that are
478/// later passed to APIs such as `Chord::new`.
479pub trait IntoNote {
480    /// Whether this value came from an integer pitch class or MIDI-like number.
481    const FROM_INTEGER_PITCH: bool = false;
482
483    /// Converts the value into a note.
484    fn try_into_note(self) -> Result<Note>;
485}
486
487impl IntoNote for Note {
488    fn try_into_note(self) -> Result<Note> {
489        Ok(self)
490    }
491}
492
493impl IntoNote for &Note {
494    fn try_into_note(self) -> Result<Note> {
495        Ok(self.clone())
496    }
497}
498
499impl IntoNote for Pitch {
500    fn try_into_note(self) -> Result<Note> {
501        Ok(Note::from_pitch(self))
502    }
503}
504
505impl IntoNote for &Pitch {
506    fn try_into_note(self) -> Result<Note> {
507        Ok(Note::from_pitch(self.clone()))
508    }
509}
510
511impl IntoNote for String {
512    fn try_into_note(self) -> Result<Note> {
513        Note::from_name(self)
514    }
515}
516
517impl IntoNote for &String {
518    fn try_into_note(self) -> Result<Note> {
519        Note::from_name(self.as_str())
520    }
521}
522
523impl IntoNote for &str {
524    fn try_into_note(self) -> Result<Note> {
525        Note::from_name(self)
526    }
527}
528
529impl IntoNote for IntegerType {
530    const FROM_INTEGER_PITCH: bool = true;
531
532    fn try_into_note(self) -> Result<Note> {
533        Note::from_number(self as FloatType)
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    #[test]
540    fn a_note_keeps_the_instrument_it_is_played_on() {
541        let mut note = super::Note::from_name("C4").unwrap();
542        assert!(note.instrument().is_none());
543        let piccolo = crate::Instrument::of_kind("Piccolo").unwrap();
544        note.set_stored_instrument(Some(piccolo.clone()));
545        assert_eq!(note.stored_instrument(), Some(&piccolo));
546        assert_eq!(note.instrument(), Some(&piccolo));
547        note.set_stored_instrument(None);
548        assert!(note.instrument().is_none());
549    }
550
551    #[test]
552    fn a_note_is_built_from_a_name_or_a_pitch_and_carries_beams_and_lyrics() {
553        use super::Note;
554        use crate::notation::{BeamType, Beams, Lyric};
555        use crate::pitch::Pitch;
556
557        let note = Note::try_from("Eb4".to_string()).unwrap();
558        assert_eq!(note.pitch_name(), "Eb");
559        let pitch = Pitch::from_name("G#3").unwrap();
560        let mut from_pitch = Note::from(&pitch);
561        assert_eq!(from_pitch.pitch_name(), "G#");
562        assert_eq!(from_pitch.notehead_fill(), None);
563        from_pitch.set_notehead_fill(Some(true));
564        assert_eq!(from_pitch.notehead_fill(), Some(true));
565
566        assert!(from_pitch.beams().is_empty());
567        from_pitch.beams_mut().append(BeamType::Start, None);
568        assert_eq!(from_pitch.beams().beams().len(), 1);
569        from_pitch.set_beams(Beams::default());
570        assert!(from_pitch.beams().is_empty());
571
572        from_pitch.lyrics_mut().push(Lyric::new("la"));
573        assert_eq!(from_pitch.lyrics().len(), 1);
574        assert_eq!(from_pitch.lyrics()[0].text(), "la");
575    }
576
577    #[test]
578    fn full_name_step_and_octave_match_music21() {
579        let flat = Note::from_name("Eb4").unwrap();
580        assert_eq!(flat.full_name(), "E-flat in octave 4 Note");
581        assert_eq!(flat.step(), 'E');
582        assert_eq!(flat.octave(), Some(4));
583        assert_eq!(flat.pitches()[0].name_with_octave(), "Eb4");
584        let dotted = Note::from_name("C#5")
585            .unwrap()
586            .with_duration(crate::Duration::new(1.5).unwrap());
587        assert_eq!(
588            dotted.full_name(),
589            "C-sharp in octave 5 Dotted Quarter Note"
590        );
591        let bare = Note::from_name("G").unwrap();
592        assert_eq!(bare.octave(), None);
593        assert_eq!(bare.full_name(), "G Note");
594    }
595    use super::{IntoNote, Note};
596    use crate::defaults::IntegerType;
597    use crate::pitch::Pitch;
598
599    #[test]
600    fn into_note_accepts_note_like_inputs() {
601        fn from_integer_pitch<T: IntoNote>() -> bool {
602            T::FROM_INTEGER_PITCH
603        }
604
605        assert!(!from_integer_pitch::<&str>());
606        assert!(from_integer_pitch::<IntegerType>());
607
608        let note = Note::from_name("C4").unwrap();
609        assert_eq!(
610            note.clone()
611                .try_into_note()
612                .unwrap()
613                .pitch_name_with_octave(),
614            "C4"
615        );
616
617        let borrowed_note = Note::from_name("D4").unwrap();
618        assert_eq!(
619            (&borrowed_note)
620                .try_into_note()
621                .unwrap()
622                .pitch_name_with_octave(),
623            "D4"
624        );
625
626        let pitch = Pitch::from_name("E4").unwrap();
627        assert_eq!(
628            pitch.try_into_note().unwrap().pitch_name_with_octave(),
629            "E4"
630        );
631
632        let borrowed_pitch = Pitch::from_name("F4").unwrap();
633        assert_eq!(
634            (&borrowed_pitch)
635                .try_into_note()
636                .unwrap()
637                .pitch_name_with_octave(),
638            "F4"
639        );
640
641        assert_eq!(
642            "G4".to_string()
643                .try_into_note()
644                .unwrap()
645                .pitch_name_with_octave(),
646            "G4"
647        );
648
649        let owned_name = "A4".to_string();
650        assert_eq!(
651            (&owned_name)
652                .try_into_note()
653                .unwrap()
654                .pitch_name_with_octave(),
655            "A4"
656        );
657
658        assert_eq!("B4".try_into_note().unwrap().pitch_name_with_octave(), "B4");
659
660        assert_eq!(
661            (60 as IntegerType)
662                .try_into_note()
663                .unwrap()
664                .pitch_name_with_octave(),
665            "C4"
666        );
667    }
668
669    #[test]
670    fn transposing_a_note_keeps_its_duration() {
671        let note = Note::from_name("C4")
672            .unwrap()
673            .with_duration(crate::Duration::half());
674        let moved = note
675            .transpose(&crate::Interval::from_name("M3").unwrap())
676            .unwrap();
677        assert_eq!(moved.pitch_name_with_octave(), "E4");
678        assert_eq!(moved.duration().unwrap().quarter_length(), 2.0);
679    }
680
681    #[test]
682    fn setting_a_pitch_keeps_the_duration_and_the_notation() {
683        let mut note = Note::from_name("C4")
684            .unwrap()
685            .with_duration(crate::Duration::half());
686        note.set_notehead(crate::Notehead::Diamond);
687        note.set_pitch(crate::Pitch::from_name("Eb5").unwrap());
688        assert_eq!(note.pitch_name_with_octave(), "Eb5");
689        assert_eq!(note.duration().unwrap().quarter_length(), 2.0);
690        assert_eq!(note.notehead(), crate::Notehead::Diamond);
691    }
692
693    #[test]
694    fn a_numbered_lyric_replaces_the_verse_that_has_that_number() {
695        let mut note = Note::from_name("C4").unwrap();
696        note.add_lyric("hello", None, false).unwrap();
697        note.add_lyric("bye", Some(3), false).unwrap();
698        assert_eq!(
699            note.lyrics()
700                .iter()
701                .map(|lyric| (lyric.number(), lyric.text()))
702                .collect::<Vec<_>>(),
703            [(1, "hello".to_string()), (3, "bye".to_string())]
704        );
705
706        // the same number again replaces the text, and leaves where the
707        // syllable falls in its word alone
708        note.add_lyric("ciao", Some(3), false).unwrap();
709        assert_eq!(note.lyrics().len(), 2);
710        assert_eq!(note.lyrics()[1].text(), "ciao");
711        assert_eq!(note.lyrics()[1].number(), 3);
712
713        // and `lyric` reads the syllables, not their hyphenated spellings
714        let mut hyphenated = Note::from_name("C4").unwrap();
715        hyphenated.set_lyric(Some("hel-")).unwrap();
716        assert_eq!(hyphenated.lyrics()[0].raw_text(), "hel-");
717        assert_eq!(hyphenated.lyric().as_deref(), Some("hel"));
718    }
719
720    #[test]
721    fn inserting_a_lyric_moves_the_verses_after_it_down() {
722        let mut note = Note::from_name("C4").unwrap();
723        note.add_lyric("second", None, false).unwrap();
724        note.insert_lyric("first", 0, false).unwrap();
725        note.insert_lyric("newSecond", 1, false).unwrap();
726        assert_eq!(
727            note.lyrics()
728                .iter()
729                .map(|lyric| (lyric.number(), lyric.text()))
730                .collect::<Vec<_>>(),
731            [
732                (1, "first".to_string()),
733                (2, "newSecond".to_string()),
734                (3, "second".to_string())
735            ]
736        );
737        // an index past the end appends, as inserting into a list does
738        note.insert_lyric("last", 99, false).unwrap();
739        assert_eq!(note.lyrics()[3].number(), 4);
740        assert_eq!(note.lyrics()[3].text(), "last");
741    }
742
743    #[test]
744    fn note_supports_rust_conversion_traits() {
745        let parsed: Note = "C#4".parse().unwrap();
746        assert_eq!(parsed.to_string(), "C#4");
747
748        let from_pitch = Note::from(Pitch::from_name("D4").unwrap());
749        assert_eq!(from_pitch.pitch_name_with_octave(), "D4");
750
751        let from_integer = Note::try_from(60 as IntegerType).unwrap();
752        assert_eq!(from_integer.pitch_name_with_octave(), "C4");
753    }
754}