Skip to main content

music21_rs/
notation.rs

1//! Notation attached to a note or a chord: ties, noteheads, stem direction,
2//! colour and lyrics.
3//!
4//! These are the parts of music21's `note.NotRest` and `note.GeneralNote`
5//! that carry musical intent rather than layout. A tie says two written notes
6//! sound as one; a diamond notehead says a string harmonic; a lyric is the
7//! text sung on the note. None of them need a stream to mean something, which
8//! is why they live in the data model here alongside pitch and duration.
9
10use std::fmt;
11use std::str::FromStr;
12
13use crate::{
14    defaults::IntegerType,
15    duration::DurationType,
16    error::{Error, Result},
17};
18
19/// How a tie joins this note to its neighbours: music21's `tie.Tie.type`.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum TieType {
23    /// The note begins a tie.
24    Start,
25    /// The note ends a tie.
26    Stop,
27    /// The note is tied both from the one before and to the one after.
28    Continue,
29    /// The note rings on with no written end, as a piano pedal note does.
30    LetRing,
31    /// A `continue` that also lets the sound ring on.
32    ContinueLetRing,
33}
34
35impl TieType {
36    /// Every tie type, in music21's order.
37    pub const ALL: [TieType; 5] = [
38        TieType::Start,
39        TieType::Stop,
40        TieType::Continue,
41        TieType::LetRing,
42        TieType::ContinueLetRing,
43    ];
44
45    /// music21's name for the type.
46    pub fn as_str(self) -> &'static str {
47        match self {
48            TieType::Start => "start",
49            TieType::Stop => "stop",
50            TieType::Continue => "continue",
51            TieType::LetRing => "let-ring",
52            TieType::ContinueLetRing => "continue-let-ring",
53        }
54    }
55
56    /// Reads music21's name for the type.
57    pub fn from_name(name: &str) -> Result<Self> {
58        Self::ALL
59            .into_iter()
60            .find(|candidate| candidate.as_str() == name)
61            .ok_or_else(|| {
62                let valid = Self::ALL
63                    .iter()
64                    .map(|candidate| format!("'{}'", candidate.as_str()))
65                    .collect::<Vec<_>>()
66                    .join(", ");
67                Error::Notation(format!("Type must be one of ({valid}), not {name}"))
68            })
69    }
70}
71
72impl fmt::Display for TieType {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_str(self.as_str())
75    }
76}
77
78/// How the tie is drawn: music21's `tie.Tie.style`.
79#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub enum TieStyle {
82    /// A solid tie.
83    #[default]
84    Normal,
85    /// A dotted tie.
86    Dotted,
87    /// A dashed tie.
88    Dashed,
89    /// A tie that sounds but is not drawn.
90    Hidden,
91}
92
93impl TieStyle {
94    /// Every tie style.
95    pub const ALL: [TieStyle; 4] = [
96        TieStyle::Normal,
97        TieStyle::Dotted,
98        TieStyle::Dashed,
99        TieStyle::Hidden,
100    ];
101
102    /// music21's name for the style.
103    pub fn as_str(self) -> &'static str {
104        match self {
105            TieStyle::Normal => "normal",
106            TieStyle::Dotted => "dotted",
107            TieStyle::Dashed => "dashed",
108            TieStyle::Hidden => "hidden",
109        }
110    }
111
112    /// Reads music21's name for the style.
113    pub fn from_name(name: &str) -> Result<Self> {
114        Self::ALL
115            .into_iter()
116            .find(|candidate| candidate.as_str() == name)
117            .ok_or_else(|| Error::Notation(format!("not a valid tie style: {name}")))
118    }
119}
120
121impl fmt::Display for TieStyle {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.write_str(self.as_str())
124    }
125}
126
127/// Which side of the note a mark sits on.
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130pub enum Placement {
131    /// Above the note.
132    Above,
133    /// Below the note.
134    Below,
135}
136
137impl Placement {
138    /// music21's name for the placement.
139    pub fn as_str(self) -> &'static str {
140        match self {
141            Placement::Above => "above",
142            Placement::Below => "below",
143        }
144    }
145
146    /// Reads music21's name for the placement.
147    pub fn from_name(name: &str) -> Result<Self> {
148        match name {
149            "above" => Ok(Placement::Above),
150            "below" => Ok(Placement::Below),
151            other => Err(Error::Notation(format!("not a valid placement: {other}"))),
152        }
153    }
154}
155
156impl fmt::Display for Placement {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.write_str(self.as_str())
159    }
160}
161
162/// A tie joining a note to its neighbours: music21's `tie.Tie`.
163///
164/// A tie on the first of two notes is enough to say they sound as one; the
165/// matching `Stop` on the second is what a MusicXML writer needs, not what
166/// the music needs.
167#[derive(Clone, Debug, PartialEq, Eq, Hash)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169#[must_use]
170pub struct Tie {
171    tie_type: TieType,
172    style: TieStyle,
173    placement: Option<Placement>,
174}
175
176impl Tie {
177    /// A tie of the given type, drawn normally, with no placement of its own.
178    pub fn new(tie_type: TieType) -> Self {
179        Self {
180            tie_type,
181            style: TieStyle::default(),
182            placement: None,
183        }
184    }
185
186    /// Reads a tie from music21's type name, `"start"` through
187    /// `"continue-let-ring"`.
188    pub fn from_name(name: &str) -> Result<Self> {
189        Ok(Self::new(TieType::from_name(name)?))
190    }
191
192    /// The tie type.
193    pub fn tie_type(&self) -> TieType {
194        self.tie_type
195    }
196
197    /// Replaces the tie type.
198    pub fn set_tie_type(&mut self, tie_type: TieType) {
199        self.tie_type = tie_type;
200    }
201
202    /// How the tie is drawn.
203    pub fn style(&self) -> TieStyle {
204        self.style
205    }
206
207    /// Sets how the tie is drawn.
208    pub fn set_style(&mut self, style: TieStyle) {
209        self.style = style;
210    }
211
212    /// Which side of the note the tie sits on, when it was said.
213    pub fn placement(&self) -> Option<Placement> {
214        self.placement
215    }
216
217    /// Sets which side of the note the tie sits on.
218    pub fn set_placement(&mut self, placement: Option<Placement>) {
219        self.placement = placement;
220    }
221}
222
223impl Default for Tie {
224    /// A starting tie, as music21's `Tie()` is.
225    fn default() -> Self {
226        Self::new(TieType::Start)
227    }
228}
229
230impl fmt::Display for Tie {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        write!(f, "Tie {}", self.tie_type)
233    }
234}
235
236impl FromStr for Tie {
237    type Err = Error;
238
239    fn from_str(name: &str) -> Result<Self> {
240        Self::from_name(name)
241    }
242}
243
244/// The shape drawn for a note head: music21's `noteheadTypeNames`.
245#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247#[must_use]
248pub enum Notehead {
249    /// `arrow down`
250    ArrowDown,
251    /// `arrow up`
252    ArrowUp,
253    /// `back slashed`
254    BackSlashed,
255    /// `circle dot`
256    CircleDot,
257    /// `circle-x`
258    CircleX,
259    /// `circled`
260    Circled,
261    /// `cluster`
262    Cluster,
263    /// `cross`
264    Cross,
265    /// `diamond`, which is how a string harmonic is written.
266    Diamond,
267    /// `do`
268    Do,
269    /// `fa`
270    Fa,
271    /// `fa up`
272    FaUp,
273    /// `inverted triangle`
274    InvertedTriangle,
275    /// `la`
276    La,
277    /// `left triangle`
278    LeftTriangle,
279    /// `mi`
280    Mi,
281    /// `none`
282    NoneShape,
283    /// `normal`, the default.
284    #[default]
285    Normal,
286    /// `other`
287    Other,
288    /// `re`
289    Re,
290    /// `rectangle`
291    Rectangle,
292    /// `slash`
293    Slash,
294    /// `slashed`
295    Slashed,
296    /// `so`
297    So,
298    /// `square`
299    Square,
300    /// `ti`
301    Ti,
302    /// `triangle`
303    Triangle,
304    /// `x`
305    X,
306}
307
308impl Notehead {
309    /// Every notehead shape, in music21's order.
310    pub const ALL: [Notehead; 28] = [
311        Notehead::ArrowDown,
312        Notehead::ArrowUp,
313        Notehead::BackSlashed,
314        Notehead::CircleDot,
315        Notehead::CircleX,
316        Notehead::Circled,
317        Notehead::Cluster,
318        Notehead::Cross,
319        Notehead::Diamond,
320        Notehead::Do,
321        Notehead::Fa,
322        Notehead::FaUp,
323        Notehead::InvertedTriangle,
324        Notehead::La,
325        Notehead::LeftTriangle,
326        Notehead::Mi,
327        Notehead::NoneShape,
328        Notehead::Normal,
329        Notehead::Other,
330        Notehead::Re,
331        Notehead::Rectangle,
332        Notehead::Slash,
333        Notehead::Slashed,
334        Notehead::So,
335        Notehead::Square,
336        Notehead::Ti,
337        Notehead::Triangle,
338        Notehead::X,
339    ];
340
341    /// music21's name for the shape.
342    pub fn as_str(self) -> &'static str {
343        match self {
344            Notehead::ArrowDown => "arrow down",
345            Notehead::ArrowUp => "arrow up",
346            Notehead::BackSlashed => "back slashed",
347            Notehead::CircleDot => "circle dot",
348            Notehead::CircleX => "circle-x",
349            Notehead::Circled => "circled",
350            Notehead::Cluster => "cluster",
351            Notehead::Cross => "cross",
352            Notehead::Diamond => "diamond",
353            Notehead::Do => "do",
354            Notehead::Fa => "fa",
355            Notehead::FaUp => "fa up",
356            Notehead::InvertedTriangle => "inverted triangle",
357            Notehead::La => "la",
358            Notehead::LeftTriangle => "left triangle",
359            Notehead::Mi => "mi",
360            Notehead::NoneShape => "none",
361            Notehead::Normal => "normal",
362            Notehead::Other => "other",
363            Notehead::Re => "re",
364            Notehead::Rectangle => "rectangle",
365            Notehead::Slash => "slash",
366            Notehead::Slashed => "slashed",
367            Notehead::So => "so",
368            Notehead::Square => "square",
369            Notehead::Ti => "ti",
370            Notehead::Triangle => "triangle",
371            Notehead::X => "x",
372        }
373    }
374
375    /// Reads music21's name for the shape.
376    pub fn from_name(name: &str) -> Result<Self> {
377        Self::ALL
378            .into_iter()
379            .find(|candidate| candidate.as_str() == name)
380            .ok_or_else(|| Error::Notation(format!("not a valid notehead type name: '{name}'")))
381    }
382}
383
384impl fmt::Display for Notehead {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        f.write_str(self.as_str())
387    }
388}
389
390/// How one beam of a note joins its neighbours: music21's `beam.Beam`.
391///
392/// A beam is a horizontal line joining the flags of consecutive short notes,
393/// and each note carries one for every level it is beamed at — an eighth has
394/// one, a sixteenth two. Whether the line starts here, carries through, ends
395/// here, or is only a stub is rhythmic grouping written down, which is why
396/// this lives here beside the ties and the noteheads and not among the
397/// things that need a page.
398#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400pub enum BeamType {
401    /// The beam begins on this note.
402    #[default]
403    Start,
404    /// The beam carries through this note.
405    Continue,
406    /// The beam ends on this note.
407    Stop,
408    /// A stub reaching only part of the way, pointing left or right.
409    PartialBeam,
410}
411
412impl BeamType {
413    /// music21's name for the type.
414    pub fn as_str(self) -> &'static str {
415        match self {
416            Self::Start => "start",
417            Self::Continue => "continue",
418            Self::Stop => "stop",
419            Self::PartialBeam => "partial",
420        }
421    }
422
423    /// Reads music21's name.
424    pub fn from_music21_name(name: &str) -> Option<Self> {
425        match name {
426            "start" => Some(Self::Start),
427            "continue" => Some(Self::Continue),
428            "stop" => Some(Self::Stop),
429            "partial" => Some(Self::PartialBeam),
430            _ => None,
431        }
432    }
433}
434
435impl fmt::Display for BeamType {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        f.write_str(self.as_str())
438    }
439}
440
441/// Which way a partial beam points: music21's `direction`.
442#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
443#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
444pub enum BeamDirection {
445    /// Back towards the note before.
446    Left,
447    /// On towards the note after.
448    Right,
449}
450
451impl BeamDirection {
452    /// music21's name for the direction.
453    pub fn as_str(self) -> &'static str {
454        match self {
455            Self::Left => "left",
456            Self::Right => "right",
457        }
458    }
459
460    /// Reads music21's name.
461    pub fn from_music21_name(name: &str) -> Option<Self> {
462        match name {
463            "left" => Some(Self::Left),
464            "right" => Some(Self::Right),
465            _ => None,
466        }
467    }
468}
469
470impl fmt::Display for BeamDirection {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        f.write_str(self.as_str())
473    }
474}
475
476/// One beam at one level: music21's `beam.Beam`.
477#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
478#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
479#[must_use]
480pub struct Beam {
481    /// What the beam does at this note. music21 leaves it unsaid on a beam
482    /// that has been counted but not yet decided — which is what `fill`
483    /// makes, and what a beam built with no arguments is.
484    beam_type: Option<BeamType>,
485    direction: Option<BeamDirection>,
486    number: Option<u32>,
487}
488
489impl Beam {
490    /// A beam of the given type, with a direction only a partial one needs.
491    pub fn new(beam_type: impl Into<Option<BeamType>>, direction: Option<BeamDirection>) -> Self {
492        Self {
493            beam_type: beam_type.into(),
494            direction,
495            number: None,
496        }
497    }
498
499    /// Whether the beam starts, carries on, ends, or is a stub — and nothing
500    /// at all where that has not been decided.
501    pub fn beam_type(&self) -> Option<BeamType> {
502        self.beam_type
503    }
504
505    /// Says what the beam does at this note.
506    pub fn set_beam_type(&mut self, beam_type: Option<BeamType>) {
507        self.beam_type = beam_type;
508    }
509
510    /// Which way a stub points, and nothing for a beam that is not one.
511    pub fn direction(&self) -> Option<BeamDirection> {
512        self.direction
513    }
514
515    /// Points a stub the other way.
516    pub fn set_direction(&mut self, direction: Option<BeamDirection>) {
517        self.direction = direction;
518    }
519
520    /// Which level this beam is: the first is the one an eighth note has.
521    pub fn number(&self) -> Option<u32> {
522        self.number
523    }
524
525    /// Puts the beam at a level.
526    pub fn set_number(&mut self, number: Option<u32>) {
527        self.number = number;
528    }
529}
530
531impl fmt::Display for Beam {
532    /// music21's `_reprInternal`: the level, the type, and the direction of
533    /// a stub.
534    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535        let number = match self.number {
536            Some(number) => number.to_string(),
537            None => "None".to_string(),
538        };
539        let beam_type = match self.beam_type {
540            Some(beam_type) => beam_type.to_string(),
541            None => "None".to_string(),
542        };
543        match self.direction {
544            Some(direction) => write!(f, "{number}/{beam_type}/{direction}"),
545            None => write!(f, "{number}/{beam_type}"),
546        }
547    }
548}
549
550/// The beams of one note, one for each level it is beamed at: music21's
551/// `beam.Beams`.
552#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
553#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
554#[must_use]
555pub struct Beams {
556    beams: Vec<Beam>,
557    /// Whether the beam group is drawn fanned out, for an accelerando.
558    feathered: bool,
559}
560
561/// The written values that can carry a beam at all, and how many levels each
562/// has: music21's `beamableDurationTypes`, an eighth through a 2048th.
563const BEAMABLE: [(DurationType, u32); 9] = [
564    (DurationType::Eighth, 1),
565    (DurationType::Sixteenth, 2),
566    (DurationType::ThirtySecond, 3),
567    (DurationType::SixtyFourth, 4),
568    (DurationType::HundredTwentyEighth, 5),
569    (DurationType::TwoHundredFiftySixth, 6),
570    (DurationType::FiveHundredTwelfth, 7),
571    (DurationType::TenTwentyFourth, 8),
572    (DurationType::TwentyFortyEighth, 9),
573];
574
575impl Beams {
576    /// No beams at all.
577    pub fn new() -> Self {
578        Self::default()
579    }
580
581    /// The beams, in level order.
582    pub fn beams(&self) -> &[Beam] {
583        &self.beams
584    }
585
586    /// How many levels are beamed.
587    pub fn len(&self) -> usize {
588        self.beams.len()
589    }
590
591    /// Whether the note carries no beam at all.
592    pub fn is_empty(&self) -> bool {
593        self.beams.is_empty()
594    }
595
596    /// Whether the group is drawn fanned out.
597    pub fn feathered(&self) -> bool {
598        self.feathered
599    }
600
601    /// Draws the group fanned out, or stops doing so.
602    pub fn set_feathered(&mut self, feathered: bool) {
603        self.feathered = feathered;
604    }
605
606    /// The beams, to be edited in place.
607    pub fn beams_mut(&mut self) -> &mut Vec<Beam> {
608        &mut self.beams
609    }
610
611    /// Replaces the beams outright.
612    pub fn set_beams(&mut self, beams: Vec<Beam>) {
613        self.beams = beams;
614    }
615
616    /// Adds a beam at the next level down: music21's `append`.
617    pub fn append(
618        &mut self,
619        beam_type: impl Into<Option<BeamType>>,
620        direction: Option<BeamDirection>,
621    ) {
622        let mut beam = Beam::new(beam_type, direction);
623        beam.set_number(Some(self.beams.len() as u32 + 1));
624        self.beams.push(beam);
625    }
626
627    /// How many beams a written value can carry at the most: music21's
628    /// `beamableDurationTypes`, an eighth through a 2048th. It is how many
629    /// depths a run of notes is beamed at.
630    pub const LEVELS: usize = BEAMABLE.len();
631
632    /// music21's `beamableDurationTypes` read as a count of levels.
633    pub fn levels_for(duration_type: DurationType) -> Option<u32> {
634        BEAMABLE
635            .into_iter()
636            .find(|(candidate, _)| *candidate == duration_type)
637            .map(|(_, levels)| levels)
638    }
639
640    /// For each element, the fullest set of beams its written value can
641    /// carry, with nothing said about what any of them does: music21's
642    /// `naiveBeams`.
643    ///
644    /// An element carries none where it does not sound -- a rest, or
645    /// anything that is not a note -- or where it is written as a quarter or
646    /// longer, or as more than one value.
647    ///
648    /// ```
649    /// use music21_rs::{Beams, Duration, Note, Rest, StreamElement};
650    ///
651    /// let elements: Vec<StreamElement> = vec![
652    ///     Note::from_name("C4")?.with_duration(Duration::quarter()).into(),
653    ///     Note::from_name("C4")?.with_duration(Duration::eighth()).into(),
654    ///     Note::from_name("C4")?.with_duration(Duration::new(0.25)?).into(),
655    ///     Rest::new(Duration::new(0.125)?).into(),
656    /// ];
657    /// let levels: Vec<Option<usize>> = Beams::naive_beams(&elements)
658    ///     .iter()
659    ///     .map(|beams| beams.as_ref().map(Beams::len))
660    ///     .collect();
661    /// assert_eq!(levels, [None, Some(1), Some(2), None]);
662    /// # Ok::<(), music21_rs::Error>(())
663    /// ```
664    pub fn naive_beams<'a>(
665        elements: impl IntoIterator<Item = &'a crate::stream::StreamElement>,
666    ) -> Vec<Option<Beams>> {
667        use crate::stream::StreamElement;
668        elements
669            .into_iter()
670            .map(|element| {
671                let sounds = matches!(
672                    element,
673                    StreamElement::Note(_)
674                        | StreamElement::Chord(_)
675                        | StreamElement::Unpitched(_)
676                        | StreamElement::PercussionChord(_)
677                        | StreamElement::ChordSymbol(_)
678                );
679                let written = element.duration().and_then(|duration| {
680                    match duration.components().as_slice() {
681                        [(duration_type, _)] => Some(*duration_type),
682                        _ => None,
683                    }
684                });
685                Self::naive(written, sounds)
686            })
687            .collect()
688    }
689
690    /// The beams one element of [`Beams::naive_beams`] carries, given the
691    /// one value it is written as, if it is written as one, and whether it
692    /// sounds: as many as that value has, each left undecided, or none.
693    pub fn naive(written: Option<DurationType>, sounds: bool) -> Option<Beams> {
694        let levels = written.and_then(Self::levels_for).filter(|_| sounds)?;
695        let mut made = Beams::new();
696        made.fill_levels(levels, None).ok()?;
697        Some(made)
698    }
699
700    /// Gives the note as many beams as its written value has, all of the same
701    /// type: music21's `fill`.
702    ///
703    /// A value that carries no beam — anything a quarter or longer — is an
704    /// error, as it is upstream.
705    pub fn fill(&mut self, duration_type: DurationType, beam_type: Option<BeamType>) -> Result<()> {
706        let Some(levels) = Self::levels_for(duration_type) else {
707            return Err(Error::Notation(format!(
708                "cannot fill beams for a {} note",
709                duration_type.music21_name()
710            )));
711        };
712        self.fill_levels(levels, beam_type)
713    }
714
715    /// The same, counting the levels rather than naming the written value:
716    /// music21 takes either, and `fill(2)` is a sixteenth.
717    ///
718    /// The beams are left with nothing said about what they do, unless a type
719    /// is given — music21 counts the lines first and decides them after.
720    pub fn fill_levels(&mut self, levels: u32, beam_type: Option<BeamType>) -> Result<()> {
721        if levels == 0 || levels > BEAMABLE.len() as u32 {
722            return Err(Error::Notation(format!(
723                "cannot fill beams for level {levels}"
724            )));
725        }
726        self.beams.clear();
727        for _ in 0..levels {
728            self.append(None, None);
729        }
730        if let Some(beam_type) = beam_type {
731            self.set_all(beam_type, None);
732        }
733        Ok(())
734    }
735
736    /// Sets every beam to the same type: music21's `setAll`.
737    pub fn set_all(&mut self, beam_type: BeamType, direction: Option<BeamDirection>) {
738        for beam in &mut self.beams {
739            beam.beam_type = Some(beam_type);
740            beam.direction = direction;
741        }
742    }
743
744    /// The beam at a level, counting from one: music21's `getByNumber`.
745    pub fn by_number(&self, number: u32) -> Option<&Beam> {
746        self.beams.iter().find(|beam| beam.number == Some(number))
747    }
748
749    /// Sets the beam at a level, which must already be there.
750    pub fn set_by_number(
751        &mut self,
752        number: u32,
753        beam_type: BeamType,
754        direction: Option<BeamDirection>,
755    ) -> Result<()> {
756        let Some(beam) = self
757            .beams
758            .iter_mut()
759            .find(|beam| beam.number == Some(number))
760        else {
761            return Err(Error::Notation(format!(
762                "beam number {number} does not exist"
763            )));
764        };
765        beam.beam_type = Some(beam_type);
766        beam.direction = direction;
767        Ok(())
768    }
769
770    /// The type of every beam, in level order: music21's `getTypes`.
771    pub fn types(&self) -> Vec<Option<BeamType>> {
772        self.beams.iter().map(Beam::beam_type).collect()
773    }
774
775    /// The level of every beam: music21's `getNumbers`.
776    pub fn numbers(&self) -> Vec<Option<u32>> {
777        self.beams.iter().map(Beam::number).collect()
778    }
779}
780
781impl fmt::Display for Beams {
782    /// music21's `_reprInternal`: every beam, slash-separated.
783    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784        let written: Vec<String> = self
785            .beams
786            .iter()
787            .map(|beam| format!("<music21.beam.Beam {beam}>"))
788            .collect();
789        f.write_str(&written.join("/"))
790    }
791}
792
793/// A note with nothing beamable on either side of it has nothing to beam to,
794/// so it carries no beam at all: music21's `removeSandwichedUnbeamables`.
795///
796/// The list is a run of notes, each entry the beams that note could carry or
797/// nothing where it could carry none.
798pub fn remove_sandwiched_unbeamables(beams: &mut [Option<Beams>]) {
799    let mut previous: Option<Beams> = None;
800    for index in 0..beams.len() {
801        let next = beams.get(index + 1).cloned().flatten();
802        if previous.is_none() && next.is_none() {
803            beams[index] = None;
804        }
805        previous = beams[index].clone();
806    }
807}
808
809/// Beams made only of stubs are no beams at all, and a stub beside a real
810/// beam points towards it: music21's `sanitizePartialBeams`.
811pub fn sanitize_partial_beams(beams: &mut [Option<Beams>]) {
812    for entry in beams.iter_mut() {
813        let Some(group) = entry else {
814            continue;
815        };
816        let joined = group.types().into_iter().flatten().any(|beam_type| {
817            matches!(
818                beam_type,
819                BeamType::Start | BeamType::Stop | BeamType::Continue
820            )
821        });
822        if !joined {
823            *entry = None;
824            continue;
825        }
826        let mut after_start = false;
827        let mut after_stop = false;
828        for beam in group.beams_mut() {
829            match beam.beam_type() {
830                Some(BeamType::Start) => after_start = true,
831                Some(BeamType::Stop) => after_stop = true,
832                Some(BeamType::PartialBeam) => {
833                    if after_start && beam.direction() == Some(BeamDirection::Left) {
834                        beam.set_direction(Some(BeamDirection::Right));
835                    } else if after_stop && beam.direction() == Some(BeamDirection::Right) {
836                        beam.set_direction(Some(BeamDirection::Left));
837                    }
838                }
839                _ => {}
840            }
841        }
842    }
843}
844
845/// A stub pointing right into a stub pointing left is really one beam, and is
846/// written as one: music21's `mergeConnectingPartialBeams`.
847pub fn merge_connecting_partial_beams(beams: &mut [Option<Beams>]) {
848    for index in 0..beams.len().saturating_sub(1) {
849        let Some(numbers) = beams[index]
850            .as_ref()
851            .map(|group| group.numbers().into_iter().flatten().collect::<Vec<_>>())
852        else {
853            continue;
854        };
855        if beams[index + 1].is_none() {
856            continue;
857        }
858        for number in numbers {
859            let this = beams[index]
860                .as_ref()
861                .and_then(|group| group.by_number(number))
862                .copied();
863            let next = beams[index + 1]
864                .as_ref()
865                .and_then(|group| group.by_number(number))
866                .copied();
867            let (Some(this), Some(next)) = (this, next) else {
868                continue;
869            };
870            if this.beam_type() != Some(BeamType::PartialBeam)
871                || this.direction() != Some(BeamDirection::Right)
872            {
873                continue;
874            }
875            if next.beam_type() == Some(BeamType::PartialBeam)
876                && next.direction() == Some(BeamDirection::Right)
877            {
878                continue;
879            }
880            // A partial pointing into a beam that carries on or ends is a
881            // notation nobody can draw; music21 leaves it alone and warns.
882            if matches!(
883                next.beam_type(),
884                Some(BeamType::Continue) | Some(BeamType::Stop)
885            ) {
886                continue;
887            }
888            set_beam(&mut beams[index], number, BeamType::Start, None);
889            let merged = match next.beam_type() {
890                Some(BeamType::PartialBeam) => BeamType::Stop,
891                Some(BeamType::Start) => BeamType::Continue,
892                other => other.unwrap_or(BeamType::Start),
893            };
894            set_beam(&mut beams[index + 1], number, merged, None);
895        }
896    }
897
898    // And a stub pointing left after a beam that has already ended joins it.
899    for index in 1..beams.len() {
900        let Some(numbers) = beams[index]
901            .as_ref()
902            .map(|group| group.numbers().into_iter().flatten().collect::<Vec<_>>())
903        else {
904            continue;
905        };
906        if beams[index - 1].is_none() {
907            continue;
908        }
909        for number in numbers {
910            let this = beams[index]
911                .as_ref()
912                .and_then(|group| group.by_number(number))
913                .copied();
914            let previous = beams[index - 1]
915                .as_ref()
916                .and_then(|group| group.by_number(number))
917                .copied();
918            let (Some(this), Some(previous)) = (this, previous) else {
919                continue;
920            };
921            if this.beam_type() != Some(BeamType::PartialBeam)
922                || this.direction() != Some(BeamDirection::Left)
923                || previous.beam_type() != Some(BeamType::Stop)
924            {
925                continue;
926            }
927            set_beam(&mut beams[index], number, BeamType::Stop, None);
928            set_beam(&mut beams[index - 1], number, BeamType::Continue, None);
929        }
930    }
931}
932
933/// Writes one beam of one group, where the group has one at that level.
934fn set_beam(
935    group: &mut Option<Beams>,
936    number: u32,
937    beam_type: BeamType,
938    direction: Option<BeamDirection>,
939) {
940    if let Some(group) = group {
941        let _ = group.set_by_number(number, beam_type, direction);
942    }
943}
944
945/// How large a note is drawn beside the notes around it: MusicXML's
946/// symbol size, which music21 keeps as the style's `noteSize`.
947#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
948#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
949pub enum NoteSize {
950    /// The ordinary size.
951    Full,
952    /// A cue note, drawn small: another part's line, shown to help an entry.
953    Cue,
954    /// A grace note's size.
955    Grace,
956    /// A cue note's grace note.
957    GraceCue,
958    /// Drawn larger than the rest.
959    Large,
960}
961
962impl NoteSize {
963    /// MusicXML's name for the size: `full`, `cue`, `grace`, `grace-cue` or
964    /// `large`.
965    pub fn as_str(self) -> &'static str {
966        match self {
967            Self::Full => "full",
968            Self::Cue => "cue",
969            Self::Grace => "grace",
970            Self::GraceCue => "grace-cue",
971            Self::Large => "large",
972        }
973    }
974
975    /// Reads MusicXML's name for a size.
976    pub fn from_name(name: &str) -> Option<Self> {
977        [
978            Self::Full,
979            Self::Cue,
980            Self::Grace,
981            Self::GraceCue,
982            Self::Large,
983        ]
984        .into_iter()
985        .find(|size| size.as_str() == name)
986    }
987}
988
989/// Which way the stem points: music21's `stemDirectionNames`.
990#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
991#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
992pub enum StemDirection {
993    /// Stems both up and down, for a divided part.
994    Double,
995    /// Stem down.
996    Down,
997    /// Written with no stem at all, as a harmonic sounding pitch is.
998    NoStem,
999    /// Not said, the default.
1000    #[default]
1001    Unspecified,
1002    /// Stem up.
1003    Up,
1004}
1005
1006impl StemDirection {
1007    /// Every stem direction a note can hold.
1008    ///
1009    /// One shorter than music21's [`StemDirection::NAMES`]: `"none"` is an
1010    /// input spelling of `"noStem"` that music21's setter rewrites, so no
1011    /// note ever reads back as `"none"`.
1012    pub const ALL: [StemDirection; 5] = [
1013        StemDirection::Double,
1014        StemDirection::Down,
1015        StemDirection::NoStem,
1016        StemDirection::Unspecified,
1017        StemDirection::Up,
1018    ];
1019
1020    /// music21's `stemDirectionNames`: the names the setter accepts, which
1021    /// includes the `"none"` spelling of `"noStem"`.
1022    pub const NAMES: [&'static str; 6] = ["double", "down", "noStem", "none", "unspecified", "up"];
1023
1024    /// music21's name for the direction.
1025    pub fn as_str(self) -> &'static str {
1026        match self {
1027            StemDirection::Double => "double",
1028            StemDirection::Down => "down",
1029            StemDirection::NoStem => "noStem",
1030            StemDirection::Unspecified => "unspecified",
1031            StemDirection::Up => "up",
1032        }
1033    }
1034
1035    /// Reads music21's name for the direction.
1036    ///
1037    /// `"none"` reads as [`StemDirection::NoStem`], which is what music21's
1038    /// setter stores for it.
1039    pub fn from_name(name: &str) -> Result<Self> {
1040        if name == "none" {
1041            return Ok(StemDirection::NoStem);
1042        }
1043        Self::ALL
1044            .into_iter()
1045            .find(|candidate| candidate.as_str() == name)
1046            .ok_or_else(|| Error::Notation(format!("not a valid stem direction name: {name}")))
1047    }
1048}
1049
1050impl fmt::Display for StemDirection {
1051    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1052        f.write_str(self.as_str())
1053    }
1054}
1055
1056/// Where a lyric sits in a word: music21's `Lyric.syllabic`.
1057#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1058#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1059pub enum Syllabic {
1060    /// The whole word.
1061    Single,
1062    /// The first syllable of a word, hyphenated to what follows.
1063    Begin,
1064    /// A middle syllable, hyphenated on both sides.
1065    Middle,
1066    /// The last syllable of a word.
1067    End,
1068    /// A syllable that carries no hyphens, written as `text` in MusicXML.
1069    Composite,
1070}
1071
1072impl Syllabic {
1073    /// Every syllabic position.
1074    pub const ALL: [Syllabic; 5] = [
1075        Syllabic::Single,
1076        Syllabic::Begin,
1077        Syllabic::Middle,
1078        Syllabic::End,
1079        Syllabic::Composite,
1080    ];
1081
1082    /// music21's name for the position.
1083    pub fn as_str(self) -> &'static str {
1084        match self {
1085            Syllabic::Single => "single",
1086            Syllabic::Begin => "begin",
1087            Syllabic::Middle => "middle",
1088            Syllabic::End => "end",
1089            Syllabic::Composite => "composite",
1090        }
1091    }
1092
1093    /// Reads music21's name for the position.
1094    pub fn from_name(name: &str) -> Result<Self> {
1095        Self::ALL
1096            .into_iter()
1097            .find(|candidate| candidate.as_str() == name)
1098            .ok_or_else(|| {
1099                // music21 quotes the value and lists what it would have
1100                // taken, `None` included even though it is not a position.
1101                Error::Notation(format!(
1102                    "Syllabic value '{name}' is not in note.SYLLABIC_CHOICES, namely: [None, 'begin', 'single', 'end', 'middle', 'composite']"
1103                ))
1104            })
1105    }
1106}
1107
1108impl fmt::Display for Syllabic {
1109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1110        f.write_str(self.as_str())
1111    }
1112}
1113
1114/// A syllable of text sung on a note: music21's `note.Lyric`.
1115///
1116/// The hyphens in the written text say where the syllable falls in its word,
1117/// which is why [`Self::from_raw_text`] reads `"-ci-"` as a middle syllable
1118/// and [`Self::raw_text`] writes the hyphens back.
1119#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1121#[must_use]
1122pub struct Lyric {
1123    /// The syllable, or nothing at all: music21 leaves the text of a bare
1124    /// `Lyric()` unset, and a lyric nobody has sung anything to is not the
1125    /// same as one sung to silence.
1126    text: Option<String>,
1127    number: IntegerType,
1128    syllabic: Syllabic,
1129    /// Whether anything has said where this syllable falls in its word.
1130    ///
1131    /// music21 leaves `syllabic` unset on a lyric with nothing sung yet, and
1132    /// a lyric that says nothing about its place in a word is not the same
1133    /// as one that says it is a whole word.
1134    said_syllabic: bool,
1135    identifier: Option<String>,
1136    components: Vec<Lyric>,
1137    elision_before: String,
1138    #[cfg_attr(feature = "serde", serde(default))]
1139    color: crate::display::Color,
1140    #[cfg_attr(feature = "serde", serde(default))]
1141    justify: crate::display::Drawn<Option<Justification>>,
1142    #[cfg_attr(feature = "serde", serde(default))]
1143    placement: crate::display::Drawn<Option<Placement>>,
1144    #[cfg_attr(feature = "serde", serde(default))]
1145    hidden: crate::display::Drawn<bool>,
1146}
1147
1148/// How a line of text lines up with where it is placed: music21's
1149/// `TextStyle.justify`.
1150#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1152pub enum Justification {
1153    /// Starting there.
1154    Left,
1155    /// Centred on it.
1156    Center,
1157    /// Ending there.
1158    Right,
1159    /// Spread to fill the line, which MusicXML cannot say.
1160    Full,
1161}
1162
1163impl Justification {
1164    /// music21's name for it: `left`, `center`, `right` or `full`.
1165    pub fn as_str(self) -> &'static str {
1166        match self {
1167            Self::Left => "left",
1168            Self::Center => "center",
1169            Self::Right => "right",
1170            Self::Full => "full",
1171        }
1172    }
1173
1174    /// Reads music21's name for a justification, whatever its case.
1175    ///
1176    /// # Errors
1177    ///
1178    /// Anything else, as music21's `TextFormatException`.
1179    pub fn from_name(name: &str) -> Result<Self> {
1180        match name.to_lowercase().as_str() {
1181            "left" => Ok(Self::Left),
1182            "center" => Ok(Self::Center),
1183            "right" => Ok(Self::Right),
1184            "full" => Ok(Self::Full),
1185            _ => Err(Error::Notation(format!(
1186                "Not a supported justification: '{name}'"
1187            ))),
1188        }
1189    }
1190}
1191
1192impl fmt::Display for Justification {
1193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1194        f.write_str(self.as_str())
1195    }
1196}
1197
1198/// What music21's `Lyric.elisionBefore` starts as: a space between this
1199/// syllable and the one before it inside a composite lyric.
1200const DEFAULT_ELISION: &str = " ";
1201
1202impl Lyric {
1203    /// A lyric on the first verse, spelled as a whole word.
1204    pub fn new(text: impl Into<String>) -> Self {
1205        let mut lyric = Self::unsung();
1206        lyric.text = Some(text.into());
1207        lyric.said_syllabic = true;
1208        lyric
1209    }
1210
1211    /// A lyric with nothing sung to it yet, which is music21's bare
1212    /// `Lyric()`: it says nothing about where it falls in a word, and
1213    /// writing its text does not make it say so.
1214    pub fn unsung() -> Self {
1215        Self {
1216            text: Some(String::new()),
1217            number: 1,
1218            syllabic: Syllabic::Single,
1219            said_syllabic: false,
1220            identifier: None,
1221            components: Vec::new(),
1222            elision_before: DEFAULT_ELISION.to_string(),
1223            color: crate::display::Drawn(None),
1224            justify: crate::display::Drawn(None),
1225            placement: crate::display::Drawn(None),
1226            hidden: crate::display::Drawn(false),
1227        }
1228    }
1229
1230    /// The colour the syllable is written in, where a score says.
1231    pub fn color(&self) -> Option<&str> {
1232        self.color.0.as_deref()
1233    }
1234
1235    /// Says what colour the syllable is written in.
1236    pub fn set_color(&mut self, color: Option<String>) {
1237        self.color.0 = color;
1238    }
1239
1240    /// How the syllable lines up under its note, where a score says.
1241    pub fn justify(&self) -> Option<Justification> {
1242        self.justify.0
1243    }
1244
1245    /// Says how the syllable lines up under its note.
1246    pub fn set_justify(&mut self, justify: Option<Justification>) {
1247        self.justify.0 = justify;
1248    }
1249
1250    /// Which side of the staff the syllable is written on, where a score
1251    /// says.
1252    pub fn placement(&self) -> Option<Placement> {
1253        self.placement.0
1254    }
1255
1256    /// Says which side of the staff the syllable is written on.
1257    pub fn set_placement(&mut self, placement: Option<Placement>) {
1258        self.placement.0 = placement;
1259    }
1260
1261    /// Whether the syllable is left off the page, though it is still sung:
1262    /// music21's `style.hideObjectOnPrint`.
1263    pub fn is_hidden(&self) -> bool {
1264        self.hidden.0
1265    }
1266
1267    /// Says whether the syllable is left off the page.
1268    pub fn set_hidden(&mut self, hidden: bool) {
1269        self.hidden.0 = hidden;
1270    }
1271
1272    /// Reads a lyric from text whose hyphens say where it falls in its word:
1273    /// `"-ci-"` is a middle syllable, `"ci-"` a beginning, `"-us"` an end.
1274    pub fn from_raw_text(raw_text: &str) -> Self {
1275        let mut lyric = Self::unsung();
1276        // No text at all leaves the lyric as it starts out.
1277        if !raw_text.is_empty() {
1278            lyric.set_raw_text(raw_text);
1279        }
1280        lyric
1281    }
1282
1283    /// The syllable without its hyphens.
1284    ///
1285    /// A composite lyric has no text of its own: it reads as its components
1286    /// run together, each joined on by its own [`Self::elision_before`].
1287    pub fn text(&self) -> String {
1288        let Some((first, rest)) = self.components.split_first() else {
1289            return self.text.clone().unwrap_or_default();
1290        };
1291        let mut text = first.text();
1292        for component in rest {
1293            text.push_str(&component.elision_before);
1294            text.push_str(&component.text());
1295        }
1296        text
1297    }
1298
1299    /// Replaces the syllable text, leaving its position in the word alone.
1300    ///
1301    /// Setting the text of a composite lyric drops its components, which is
1302    /// what music21 does: the text you gave it is now the whole of it.
1303    pub fn set_text(&mut self, text: impl Into<String>) {
1304        self.components.clear();
1305        self.text = Some(text.into());
1306    }
1307
1308    /// The syllable as music21's `text` reads it, which may be nothing at
1309    /// all: music21 starts a lyric off sung to the empty string, and setting
1310    /// its text to nothing is how a score says the syllable was taken away.
1311    pub fn explicit_text(&self) -> Option<String> {
1312        if self.is_composite() {
1313            return Some(self.text());
1314        }
1315        self.text.clone()
1316    }
1317
1318    /// Takes the text away, leaving a lyric with nothing sung to it at all —
1319    /// which music21 tells apart from one sung to the empty string, and
1320    /// writes out as neither.
1321    pub fn clear_text(&mut self) {
1322        self.components.clear();
1323        self.text = None;
1324    }
1325
1326    /// Whether this lyric is several lyrics sung together rather than one
1327    /// syllable: music21's `isComposite`.
1328    pub fn is_composite(&self) -> bool {
1329        !self.components.is_empty()
1330    }
1331
1332    /// The lyrics this one is made of, empty unless it is composite.
1333    pub fn components(&self) -> &[Lyric] {
1334        &self.components
1335    }
1336
1337    /// Makes this lyric the several lyrics given, run together. An empty
1338    /// list makes it an ordinary one again, keeping the text it had.
1339    pub fn set_components(&mut self, components: Vec<Lyric>) {
1340        self.components = components;
1341    }
1342
1343    /// What joins this syllable to the one before it inside a composite
1344    /// lyric — a space by default, an underscore for an elision.
1345    pub fn elision_before(&self) -> &str {
1346        &self.elision_before
1347    }
1348
1349    /// Sets what joins this syllable to the one before it.
1350    pub fn set_elision_before(&mut self, elision: impl Into<String>) {
1351        self.elision_before = elision.into();
1352    }
1353
1354    /// The verse number, counting from one.
1355    pub fn number(&self) -> IntegerType {
1356        self.number
1357    }
1358
1359    /// Sets the verse number, which must be positive as music21 requires.
1360    /// Sets which verse this lyric belongs to.
1361    ///
1362    /// Any whole number will do, as music21's own setter allows: its
1363    /// MusicXML reader numbers a verse `0` when the score named it with
1364    /// something that is not a number at all, and keeps the name as the
1365    /// identifier.
1366    pub fn set_number(&mut self, number: IntegerType) {
1367        self.number = number;
1368    }
1369
1370    /// Where the syllable falls in its word. A composite lyric reads as
1371    /// [`Syllabic::Composite`] whatever it was set to.
1372    pub fn syllabic(&self) -> Syllabic {
1373        if self.is_composite() {
1374            return Syllabic::Composite;
1375        }
1376        self.syllabic
1377    }
1378
1379    /// The same, where something has actually said it: music21 leaves it
1380    /// unset on a lyric nobody has sung anything to.
1381    pub fn explicit_syllabic(&self) -> Option<Syllabic> {
1382        if self.is_composite() {
1383            return Some(Syllabic::Composite);
1384        }
1385        self.said_syllabic.then_some(self.syllabic)
1386    }
1387
1388    /// Sets where the syllable falls in its word.
1389    pub fn set_syllabic(&mut self, syllabic: Syllabic) {
1390        self.syllabic = syllabic;
1391        self.said_syllabic = true;
1392    }
1393
1394    /// The name this verse goes by, which music21 falls back to the number
1395    /// for when none was given.
1396    pub fn identifier(&self) -> String {
1397        self.identifier
1398            .clone()
1399            .unwrap_or_else(|| self.number.to_string())
1400    }
1401
1402    /// The name this verse was given, and nothing when it goes by its
1403    /// number. music21's `identifier` answers the number itself in that
1404    /// case, which is a different type, so a caller that cares asks here.
1405    pub fn explicit_identifier(&self) -> Option<&str> {
1406        self.identifier.as_deref()
1407    }
1408
1409    /// Names this verse, or clears the name so the number stands in.
1410    pub fn set_identifier(&mut self, identifier: Option<String>) {
1411        self.identifier = identifier;
1412    }
1413
1414    /// The syllable written with the hyphens its position implies.
1415    ///
1416    /// A composite lyric takes its hyphens from the ends of the run: the
1417    /// first component decides whether one leads, the last whether one
1418    /// trails.
1419    pub fn raw_text(&self) -> String {
1420        if self.explicit_text().is_none() {
1421            return String::new();
1422        }
1423        let text = self.text();
1424        let (Some(first), Some(last)) = (self.components.first(), self.components.last()) else {
1425            return match self.syllabic {
1426                Syllabic::Begin => format!("{text}-"),
1427                Syllabic::Middle => format!("-{text}-"),
1428                Syllabic::End => format!("-{text}"),
1429                Syllabic::Single | Syllabic::Composite => text,
1430            };
1431        };
1432        let leading = matches!(first.syllabic(), Syllabic::Middle | Syllabic::End);
1433        let trailing = matches!(last.syllabic(), Syllabic::Begin | Syllabic::Middle);
1434        format!(
1435            "{}{text}{}",
1436            if leading { "-" } else { "" },
1437            if trailing { "-" } else { "" }
1438        )
1439    }
1440
1441    /// Reads the syllable and its position out of hyphenated text. Like
1442    /// [`Self::set_text`], this drops any components.
1443    pub fn set_raw_text(&mut self, raw_text: &str) {
1444        self.set_text_and_syllabic(raw_text, false);
1445    }
1446
1447    /// music21's `setTextAndSyllabic`: [`Self::set_raw_text`], unless
1448    /// `apply_raw` says the hyphens are the text itself. Then nothing about
1449    /// the word is read out of them and the position stands as it was,
1450    /// except that a lyric that had said nothing about where it falls is now
1451    /// a whole word. Either way drops any components.
1452    pub fn set_text_and_syllabic(&mut self, raw_text: &str, apply_raw: bool) {
1453        if apply_raw {
1454            self.set_text(raw_text);
1455            if !self.said_syllabic {
1456                self.set_syllabic(Syllabic::Single);
1457            }
1458            return;
1459        }
1460        self.components.clear();
1461        // A hyphen alone both starts and ends the text, and is the middle
1462        // of a word with nothing sung to it.
1463        let starts = raw_text.starts_with('-');
1464        let ends = raw_text.ends_with('-');
1465        let trimmed = raw_text.strip_prefix('-').unwrap_or(raw_text);
1466        let trimmed = if ends {
1467            trimmed.strip_suffix('-').unwrap_or(trimmed)
1468        } else {
1469            trimmed
1470        }
1471        .to_string();
1472        self.set_syllabic(match (starts, ends) {
1473            (true, true) => Syllabic::Middle,
1474            (true, false) => Syllabic::End,
1475            (false, true) => Syllabic::Begin,
1476            (false, false) => Syllabic::Single,
1477        });
1478        self.text = Some(trimmed);
1479    }
1480}
1481
1482impl fmt::Display for Lyric {
1483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1484        f.write_str(&self.text())
1485    }
1486}
1487
1488/// The verses sung on anything that can be sung to — a note, a chord, a
1489/// rest — and the rules music21 writes once on `GeneralNote` for editing them.
1490pub(crate) mod verses {
1491    use super::{Lyric, Syllabic};
1492    use crate::defaults::IntegerType;
1493
1494    /// The text of every verse, one per line: music21's `lyric`.
1495    pub(crate) fn joined(lyrics: &[Lyric]) -> Option<String> {
1496        if lyrics.is_empty() {
1497            return None;
1498        }
1499        Some(
1500            lyrics
1501                .iter()
1502                .map(Lyric::text)
1503                .collect::<Vec<_>>()
1504                .join("\n"),
1505        )
1506    }
1507
1508    /// Replaces every verse with one per line of the text, or clears them
1509    /// with `None`; hyphens say where each syllable falls in its word:
1510    /// music21's `lyric` setter.
1511    pub(crate) fn set(lyrics: &mut Vec<Lyric>, text: Option<&str>) {
1512        lyrics.clear();
1513        let Some(text) = text else {
1514            return;
1515        };
1516        for (index, line) in text.split('\n').enumerate() {
1517            let mut parsed = Lyric::from_raw_text(line);
1518            parsed.set_number(index as IntegerType + 1);
1519            lyrics.push(parsed);
1520        }
1521    }
1522
1523    /// Adds a syllable as a verse: music21's `addLyric`. With no `number`
1524    /// it becomes the next verse; with one it replaces the text of the verse
1525    /// already carrying that number, leaving where that syllable falls in its
1526    /// word alone, and only becomes a new verse when no verse has it.
1527    pub(crate) fn add(
1528        lyrics: &mut Vec<Lyric>,
1529        text: &str,
1530        number: Option<IntegerType>,
1531        apply_raw: bool,
1532    ) {
1533        let Some(number) = number else {
1534            let mut lyric = built(text, apply_raw);
1535            lyric.set_number(lyrics.len() as IntegerType + 1);
1536            lyrics.push(lyric);
1537            return;
1538        };
1539        if let Some(existing) = lyrics.iter_mut().find(|lyric| lyric.number() == number) {
1540            existing.set_text(text);
1541            return;
1542        }
1543        let mut lyric = built(text, apply_raw);
1544        lyric.set_number(number);
1545        lyrics.push(lyric);
1546    }
1547
1548    /// Puts a syllable in front of the verse at `index`, moving the verses
1549    /// from there on down a line: music21's `insertLyric`. An index past the
1550    /// end appends, as inserting into a list does.
1551    pub(crate) fn insert(lyrics: &mut Vec<Lyric>, text: &str, index: usize, apply_raw: bool) {
1552        let index = index.min(lyrics.len());
1553        for (offset, lyric) in lyrics[index..].iter_mut().enumerate() {
1554            lyric.set_number(index as IntegerType + offset as IntegerType + 2);
1555        }
1556        let mut lyric = built(text, apply_raw);
1557        lyric.set_number(index as IntegerType + 1);
1558        lyrics.insert(index, lyric);
1559    }
1560
1561    /// A syllable read from text, whose hyphens say where it falls in its
1562    /// word unless `apply_raw` takes the text as written.
1563    fn built(text: &str, apply_raw: bool) -> Lyric {
1564        if apply_raw && !text.is_empty() {
1565            let mut lyric = Lyric::new(text);
1566            lyric.set_syllabic(Syllabic::Single);
1567            lyric
1568        } else {
1569            Lyric::from_raw_text(text)
1570        }
1571    }
1572}
1573
1574#[cfg(test)]
1575mod tests {
1576    #[test]
1577    fn ties_beams_and_placements_are_written_and_read_by_name() {
1578        use super::{
1579            Beam, BeamDirection, BeamType, Beams, Lyric, Placement, Syllabic, Tie, TieStyle,
1580            TieType,
1581        };
1582        use std::str::FromStr;
1583
1584        let mut tie = Tie::from_str("start").unwrap();
1585        assert_eq!(tie.tie_type(), TieType::Start);
1586        tie.set_tie_type(TieType::Stop);
1587        assert_eq!(tie.tie_type(), TieType::Stop);
1588        assert!(Tie::from_str("sideways").is_err());
1589        assert_eq!(TieStyle::Dotted.to_string(), "dotted");
1590        assert_eq!(Placement::Above.to_string(), "above");
1591        assert_eq!(Syllabic::Middle.to_string(), "middle");
1592
1593        assert_eq!(
1594            BeamType::from_music21_name("partial"),
1595            Some(BeamType::PartialBeam)
1596        );
1597        assert_eq!(BeamType::from_music21_name("sideways"), None);
1598        assert_eq!(
1599            BeamDirection::from_music21_name("left"),
1600            Some(BeamDirection::Left)
1601        );
1602        assert_eq!(BeamDirection::from_music21_name("up"), None);
1603
1604        let mut beam = Beam::new(BeamType::Start, None);
1605        beam.set_beam_type(Some(BeamType::Continue));
1606        beam.set_direction(Some(BeamDirection::Right));
1607        assert_eq!(beam.beam_type(), Some(BeamType::Continue));
1608        assert_eq!(beam.direction(), Some(BeamDirection::Right));
1609
1610        let mut beams = Beams::default();
1611        assert!(beams.is_empty());
1612        beams.set_beams(vec![beam]);
1613        assert_eq!(beams.beams().len(), 1);
1614        beams.beams_mut()[0].set_beam_type(Some(BeamType::Stop));
1615        assert_eq!(beams.beams()[0].beam_type(), Some(BeamType::Stop));
1616        assert!(!beams.feathered());
1617        beams.set_feathered(true);
1618        assert!(beams.feathered());
1619
1620        let mut lyric = Lyric::new("hel");
1621        assert_eq!(lyric.elision_before(), " ");
1622        assert_eq!(lyric.explicit_syllabic(), Some(Syllabic::Single));
1623        lyric.clear_text();
1624        assert_eq!(lyric.explicit_text(), None);
1625        assert_eq!(Lyric::unsung().explicit_syllabic(), None);
1626    }
1627
1628    /// The three passes music21 runs over a bar's naive beams, on the
1629    /// shapes each one exists for.
1630    #[test]
1631    fn the_beam_passes_tidy_a_run_of_beams() {
1632        use super::{
1633            BeamDirection, BeamType, Beams, merge_connecting_partial_beams,
1634            remove_sandwiched_unbeamables, sanitize_partial_beams,
1635        };
1636        use crate::duration::DurationType;
1637
1638        let filled = |beam_type: BeamType| {
1639            let mut beams = Beams::default();
1640            beams.fill(DurationType::Eighth, Some(beam_type)).unwrap();
1641            Some(beams)
1642        };
1643        let partial = |direction: BeamDirection| {
1644            let mut beams = Beams::default();
1645            beams
1646                .fill(DurationType::Eighth, Some(BeamType::PartialBeam))
1647                .unwrap();
1648            beams.beams_mut()[0].set_direction(Some(direction));
1649            Some(beams)
1650        };
1651
1652        // A beamable note between two unbeamable ones cannot be beamed.
1653        let mut run = vec![
1654            None,
1655            filled(BeamType::Start),
1656            None,
1657            filled(BeamType::Start),
1658            filled(BeamType::Stop),
1659        ];
1660        remove_sandwiched_unbeamables(&mut run);
1661        assert!(run[1].is_none());
1662        assert!(run[3].is_some());
1663
1664        // A group of nothing but partials is dropped; a partial after a
1665        // start points right and one after a stop points left.
1666        let mut lone = vec![partial(BeamDirection::Left)];
1667        sanitize_partial_beams(&mut lone);
1668        assert!(lone[0].is_none());
1669        let mut mixed = Beams::default();
1670        mixed.append(BeamType::Start, None);
1671        mixed.append(BeamType::PartialBeam, Some(BeamDirection::Left));
1672        let mut run = vec![Some(mixed)];
1673        sanitize_partial_beams(&mut run);
1674        let group = run[0].as_ref().unwrap();
1675        assert_eq!(group.beams()[1].direction(), Some(BeamDirection::Right));
1676
1677        // Two partials pointing at each other are one beam.
1678        let mut run = vec![partial(BeamDirection::Right), partial(BeamDirection::Left)];
1679        merge_connecting_partial_beams(&mut run);
1680        assert_eq!(
1681            run[0].as_ref().unwrap().beams()[0].beam_type(),
1682            Some(BeamType::Start)
1683        );
1684        assert_eq!(
1685            run[1].as_ref().unwrap().beams()[0].beam_type(),
1686            Some(BeamType::Stop)
1687        );
1688        // A partial pointing into a beam that starts carries it on.
1689        let mut run = vec![partial(BeamDirection::Right), filled(BeamType::Start)];
1690        merge_connecting_partial_beams(&mut run);
1691        assert_eq!(
1692            run[1].as_ref().unwrap().beams()[0].beam_type(),
1693            Some(BeamType::Continue)
1694        );
1695        // One pointing into a beam that ends is left alone.
1696        let mut run = vec![partial(BeamDirection::Right), filled(BeamType::Stop)];
1697        merge_connecting_partial_beams(&mut run);
1698        assert_eq!(
1699            run[0].as_ref().unwrap().beams()[0].beam_type(),
1700            Some(BeamType::PartialBeam)
1701        );
1702    }
1703
1704    /// music21's own `setTextAndSyllabic` examples.
1705    #[test]
1706    fn text_and_syllabic_are_read_from_the_hyphens_unless_told_not_to() {
1707        use super::{Lyric, Syllabic};
1708
1709        let mut lyric = Lyric::unsung();
1710        lyric.set_text_and_syllabic("hel-", false);
1711        assert_eq!(
1712            (lyric.text(), lyric.syllabic()),
1713            ("hel".to_string(), Syllabic::Begin)
1714        );
1715        lyric.set_text_and_syllabic("-lo", false);
1716        assert_eq!(
1717            (lyric.text(), lyric.syllabic()),
1718            ("lo".to_string(), Syllabic::End)
1719        );
1720        lyric.set_text_and_syllabic("the", false);
1721        assert_eq!(
1722            (lyric.text(), lyric.syllabic()),
1723            ("the".to_string(), Syllabic::Single)
1724        );
1725
1726        let mut raw = Lyric::unsung();
1727        raw.set_text_and_syllabic("hel-", true);
1728        assert_eq!(
1729            (raw.text(), raw.syllabic()),
1730            ("hel-".to_string(), Syllabic::Single)
1731        );
1732        raw.set_syllabic(Syllabic::Begin);
1733        raw.set_text_and_syllabic("-lo", true);
1734        assert_eq!(
1735            (raw.text(), raw.syllabic()),
1736            ("-lo".to_string(), Syllabic::Begin)
1737        );
1738    }
1739
1740    #[test]
1741    fn beams_fill_one_level_for_each_flag() {
1742        // music21's own example: a sixteenth is beamed at two levels.
1743        let mut beams = Beams::new();
1744        beams
1745            .fill(DurationType::Sixteenth, Some(BeamType::Start))
1746            .unwrap();
1747        assert_eq!(beams.len(), 2);
1748        assert_eq!(
1749            beams.types(),
1750            [Some(BeamType::Start), Some(BeamType::Start)]
1751        );
1752        assert_eq!(beams.numbers(), [Some(1), Some(2)]);
1753        assert_eq!(
1754            beams.to_string(),
1755            "<music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>"
1756        );
1757
1758        beams.set_all(BeamType::Stop, None);
1759        assert_eq!(beams.types(), [Some(BeamType::Stop), Some(BeamType::Stop)]);
1760        assert_eq!(
1761            beams.by_number(1).and_then(Beam::beam_type),
1762            Some(BeamType::Stop)
1763        );
1764
1765        // Counted but not decided: `fill` alone says how many lines there
1766        // are and leaves what each does unsaid, as music21 does.
1767        let mut counted = Beams::new();
1768        counted.fill(DurationType::Sixteenth, None).unwrap();
1769        assert_eq!(counted.types(), [None, None]);
1770        assert_eq!(
1771            counted.to_string(),
1772            "<music21.beam.Beam 1/None>/<music21.beam.Beam 2/None>"
1773        );
1774        assert!(Beams::new().fill_levels(12, None).is_err());
1775        assert!(beams.set_by_number(3, BeamType::Start, None).is_err());
1776
1777        // A quarter carries no beam at all, and music21 refuses to fill one.
1778        assert!(Beams::new().fill(DurationType::Quarter, None).is_err());
1779
1780        // A stub says which way it points.
1781        let mut stub = Beams::new();
1782        stub.append(BeamType::PartialBeam, Some(BeamDirection::Left));
1783        assert_eq!(stub.to_string(), "<music21.beam.Beam 1/partial/left>");
1784    }
1785    use super::*;
1786
1787    #[test]
1788    fn tie_types_round_trip_and_reject_others() {
1789        for tie_type in TieType::ALL {
1790            assert_eq!(TieType::from_name(tie_type.as_str()).unwrap(), tie_type);
1791        }
1792        assert_eq!(Tie::default().tie_type(), TieType::Start);
1793        assert_eq!(Tie::from_name("stop").unwrap().to_string(), "Tie stop");
1794        let error = TieType::from_name("hello").unwrap_err().to_string();
1795        assert!(error.contains("Type must be one of"), "{error}");
1796        assert!(error.ends_with("not hello"), "{error}");
1797    }
1798
1799    #[test]
1800    fn tie_carries_style_and_placement() {
1801        let mut tie = Tie::new(TieType::Continue);
1802        assert_eq!(tie.style(), TieStyle::Normal);
1803        assert_eq!(tie.placement(), None);
1804        tie.set_style(TieStyle::Dashed);
1805        tie.set_placement(Some(Placement::Above));
1806        assert_eq!(tie.style().as_str(), "dashed");
1807        assert_eq!(tie.placement().map(Placement::as_str), Some("above"));
1808        assert!(TieStyle::from_name("wavy").is_err());
1809        assert!(Placement::from_name("sideways").is_err());
1810    }
1811
1812    #[test]
1813    fn notehead_and_stem_names_round_trip() {
1814        for notehead in Notehead::ALL {
1815            assert_eq!(Notehead::from_name(notehead.as_str()).unwrap(), notehead);
1816        }
1817        for direction in StemDirection::ALL {
1818            assert_eq!(
1819                StemDirection::from_name(direction.as_str()).unwrap(),
1820                direction
1821            );
1822        }
1823        assert_eq!(Notehead::default(), Notehead::Normal);
1824        // "none" is an input spelling of "noStem", never a state of its own.
1825        assert_eq!(
1826            StemDirection::from_name("none").unwrap(),
1827            StemDirection::NoStem
1828        );
1829        assert_eq!(StemDirection::NAMES.len(), StemDirection::ALL.len() + 1);
1830        assert_eq!(StemDirection::default(), StemDirection::Unspecified);
1831        assert_eq!(Notehead::Diamond.to_string(), "diamond");
1832        assert_eq!(StemDirection::NoStem.to_string(), "noStem");
1833        assert!(Notehead::from_name("blah").is_err());
1834        assert!(StemDirection::from_name("sideways").is_err());
1835    }
1836
1837    #[test]
1838    fn lyric_hyphens_say_where_the_syllable_falls() {
1839        let cases = [
1840            ("hello", Syllabic::Single, "hello"),
1841            ("dic-", Syllabic::Begin, "dic"),
1842            ("-ci-", Syllabic::Middle, "ci"),
1843            ("-us", Syllabic::End, "us"),
1844        ];
1845        for (raw, syllabic, text) in cases {
1846            let lyric = Lyric::from_raw_text(raw);
1847            assert_eq!(lyric.syllabic(), syllabic, "{raw}");
1848            assert_eq!(lyric.text(), text, "{raw}");
1849            assert_eq!(lyric.raw_text(), raw, "{raw}");
1850        }
1851        let mut lyric = Lyric::new("shine");
1852        assert_eq!(lyric.number(), 1);
1853        assert_eq!(lyric.identifier(), "1");
1854        lyric.set_number(3);
1855        assert_eq!(lyric.identifier(), "3");
1856        lyric.set_identifier(Some("chorus".to_string()));
1857        assert_eq!(lyric.identifier(), "chorus");
1858        // Any whole number, as music21 allows: a verse the score named
1859        // with something that is not a number at all is numbered nought.
1860        lyric.set_number(0);
1861        assert_eq!(lyric.number(), 0);
1862        assert_eq!(lyric.to_string(), "shine");
1863        assert_eq!(Syllabic::from_name("middle").unwrap(), Syllabic::Middle);
1864        assert!(Syllabic::from_name("half").is_err());
1865    }
1866
1867    #[test]
1868    fn a_composite_lyric_reads_as_its_components_run_together() {
1869        // music21's own example: "bianco" sung as "co" then "e".
1870        let mut co = Lyric::new("co");
1871        co.set_syllabic(Syllabic::End);
1872        let mut e = Lyric::new("e");
1873        e.set_syllabic(Syllabic::Single);
1874
1875        let mut bianco = Lyric::new("");
1876        assert!(!bianco.is_composite());
1877        bianco.set_components(vec![co, e]);
1878        assert!(bianco.is_composite());
1879        assert_eq!(bianco.text(), "co e");
1880        assert_eq!(bianco.syllabic(), Syllabic::Composite);
1881        // The first component leads with a hyphen; the last ends the word.
1882        assert_eq!(bianco.raw_text(), "-co e");
1883
1884        // An elision joins the two rather than a space, and a middle
1885        // syllable at the end leaves the word open.
1886        let mut components = bianco.components().to_vec();
1887        components[1].set_elision_before("_");
1888        components[1].set_syllabic(Syllabic::Middle);
1889        bianco.set_components(components);
1890        assert_eq!(bianco.text(), "co_e");
1891        assert_eq!(bianco.raw_text(), "-co_e-");
1892
1893        // Setting the text outright is music21's way of un-compositing.
1894        bianco.set_text("bianco");
1895        assert!(!bianco.is_composite());
1896        assert_eq!(bianco.text(), "bianco");
1897        assert_eq!(bianco.syllabic(), Syllabic::Single);
1898    }
1899
1900    #[test]
1901    fn an_identifier_is_only_there_when_it_was_named() {
1902        let mut lyric = Lyric::new("shine");
1903        assert_eq!(lyric.explicit_identifier(), None);
1904        assert_eq!(lyric.identifier(), "1");
1905        lyric.set_identifier(Some("chorus".to_string()));
1906        assert_eq!(lyric.explicit_identifier(), Some("chorus"));
1907    }
1908
1909    #[test]
1910    fn a_lone_hyphen_is_the_middle_of_a_word() {
1911        // music21: `Lyric('-')` both starts and ends with a hyphen, so it is
1912        // a middle syllable with nothing sung to it, as `'--'` is.
1913        for raw in ["-", "--"] {
1914            let lyric = Lyric::from_raw_text(raw);
1915            assert_eq!(lyric.syllabic(), Syllabic::Middle);
1916            assert_eq!(lyric.text(), "");
1917        }
1918        assert_eq!(Lyric::from_raw_text("-a").syllabic(), Syllabic::End);
1919        assert_eq!(Lyric::from_raw_text("a-").syllabic(), Syllabic::Begin);
1920    }
1921}