Skip to main content

music21_rs/musicxml/
read.rs

1//! Reading MusicXML: music21's `musicxml.xmlToM21`.
2//!
3//! The document is read measure by measure into a list of elements, each
4//! with its offset, the voice and staff it was written in and the order it
5//! was met in, and the score is put together from those at the end -- which
6//! is where music21 sorts its streams and splits a part written on several
7//! staves into one part for each.
8//!
9//! What the crate does not model is passed over as music21's reader passes
10//! over what it does not know: page layout, fonts, positions, credits.
11
12use crate::articulations::{Articulation, ArticulationKind, Finger};
13use crate::bar::{Barline, BarlineType, Ending, RepeatDirection};
14use crate::chord::Chord;
15use crate::chordsymbol::{ChordStepModification, ChordSymbol};
16use crate::clef::Clef;
17use crate::defaults::{FloatType, IntegerType};
18use crate::duration::{
19    Duration, DurationType, Grace, Tuplet, TupletBracket, TupletShow, TupletType,
20};
21use crate::dynamics::Dynamic;
22use crate::error::{Error, Result};
23use crate::expressions::{
24    ArpeggioType, Expression, Fermata, FermataType, Ornament, OrnamentDelay, OrnamentKind,
25    RehearsalMark, TextExpression,
26};
27use crate::instrument::{Instrument, SearchLanguage};
28use crate::interval::Interval;
29use crate::key::KeySignature;
30use crate::layout::{
31    Layout, PageLayout, ScoreLayout, StaffLayout, StaffType, SystemLayout, Tenths,
32};
33use crate::metadata::{Metadata, MetadataValue};
34use crate::meter::TimeSignature;
35use crate::notation::{
36    Beam, BeamDirection, BeamType, Beams, Justification, Lyric, NoteSize, Notehead, Placement,
37    StemDirection, Syllabic, Tie, TieStyle, TieType,
38};
39use crate::note::Note;
40use crate::percussion::{PercussionChord, PercussionNote, Unpitched};
41use crate::pitch::{Accidental, Pitch};
42use crate::repeat::{RepeatExpression, RepeatExpressionKind};
43use crate::rest::Rest;
44use crate::spanner::{
45    Glissando, LineEnd, LineEnds, OctaveShift, Pedal, PedalForm, PedalObject, PedalObjectKind,
46    PedalType, SlideType, Spanner, SpannerKind,
47};
48use crate::stream::{BarTogether, StaffGroup, Stream, StreamElement, StreamEvent, StreamKind};
49use crate::style::{StyleValue, TextStyle};
50use crate::tempo::{MetricModulation, MetronomeMark};
51use crate::text::TextBox;
52use crate::volume::Volume;
53use crate::xml::Xml;
54
55/// What may be drawn round a mark: music21's `style.Enclosure`.
56const ENCLOSURES: [&str; 15] = [
57    "rectangle",
58    "square",
59    "oval",
60    "circle",
61    "bracket",
62    "triangle",
63    "diamond",
64    "pentagon",
65    "hexagon",
66    "heptagon",
67    "octagon",
68    "nonagon",
69    "decagon",
70    "inverted-bracket",
71    "none",
72];
73
74/// The staff an element is written on where nothing says: music21's
75/// `NO_STAFF_ASSIGNED`.
76const NO_STAFF: i32 = 0;
77
78fn import_error(message: impl Into<String>) -> Error {
79    Error::MusicXml(message.into())
80}
81
82/// Reads a MusicXML document -- the text of a `.musicxml` file, or of the
83/// score inside a compressed `.mxl` -- into a score: music21's
84/// `converter.parse` for the format.
85///
86/// ```
87/// use music21_rs::musicxml::from_musicxml;
88///
89/// let score = from_musicxml(
90///     "<score-partwise><part-list><score-part id=\"P1\"><part-name>Flute</part-name>\
91///      </score-part></part-list><part id=\"P1\"><measure number=\"1\">\
92///      <attributes><divisions>1</divisions></attributes>\
93///      <note><pitch><step>C</step><octave>4</octave></pitch><duration>4</duration>\
94///      <type>whole</type></note></measure></part></score-partwise>",
95/// )?;
96/// let part = score.parts()[0];
97/// assert_eq!(part.name(), Some("Flute"));
98/// assert_eq!(part.measures()[0].pitches()[0].name_with_octave(), "C4");
99/// # Ok::<(), music21_rs::Error>(())
100/// ```
101pub fn from_musicxml(document: &str) -> Result<Stream> {
102    let root = Xml::parse(document)?;
103    if root.tag != "score-partwise" {
104        return Err(import_error(format!(
105            "only a score-partwise document can be read, not <{}>",
106            root.tag
107        )));
108    }
109    Importer::default().score(&root)
110}
111
112// ------------------------------------------------------------------ numbers
113
114/// A number snapped to the nearest fraction music21 would hold it as:
115/// `opFrac`, which keeps a float whose denominator is a small power of two
116/// and otherwise takes the closest fraction with a denominator up to 65535.
117fn op_frac(value: FloatType) -> FloatType {
118    if (value * 32768.0).fract() == 0.0 {
119        return value;
120    }
121    let negative = value < 0.0;
122    let target = value.abs();
123    // Continued fractions, stopping at the last convergent under the limit.
124    let (mut p0, mut q0, mut p1, mut q1) = (0i64, 1i64, 1i64, 0i64);
125    let mut x = target;
126    let (mut best_p, mut best_q) = (target.round() as i64, 1i64);
127    for _ in 0..40 {
128        let a = x.floor();
129        let (p2, q2) = (a as i64 * p1 + p0, a as i64 * q1 + q0);
130        if q2 > 65535 {
131            // The best semiconvergent under the limit, as Python's
132            // `limit_denominator` takes it.
133            let k = (65535 - q0) / q1.max(1);
134            let (sp, sq) = (p0 + k * p1, q0 + k * q1);
135            let semi = (sp as FloatType / sq as FloatType - target).abs();
136            let conv = (p1 as FloatType / q1 as FloatType - target).abs();
137            (best_p, best_q) = if semi < conv { (sp, sq) } else { (p1, q1) };
138            break;
139        }
140        (p0, q0, p1, q1) = (p1, q1, p2, q2);
141        (best_p, best_q) = (p1, q1);
142        let rest = x - a;
143        if rest.abs() < 1e-12 {
144            break;
145        }
146        x = 1.0 / rest;
147    }
148    let snapped = best_p as FloatType / best_q as FloatType;
149    if negative { -snapped } else { snapped }
150}
151
152fn parse_float(text: &str) -> Option<FloatType> {
153    text.trim().parse::<FloatType>().ok()
154}
155
156fn parse_int(text: &str) -> Option<IntegerType> {
157    let text = text.trim();
158    text.parse::<IntegerType>().ok().or_else(|| {
159        // Python's `int(float_text)` is not accepted by music21 either, but
160        // a whole number written with a point is common in the wild.
161        text.parse::<FloatType>()
162            .ok()
163            .filter(|value| value.fract() == 0.0)
164            .map(|value| value as IntegerType)
165    })
166}
167
168/// How many strokes a `<tremolo>` says, three where it says nothing a
169/// number can be read from: music21's `xmlToTremolo`.
170fn tremolo_marks(mark: &Xml) -> Result<u8> {
171    let marks = parse_int(mark.stripped()).unwrap_or(3);
172    u8::try_from(marks)
173        .ok()
174        .filter(|marks| *marks <= 8)
175        .ok_or_else(|| import_error("Number of marks must be a number from 0 to 8"))
176}
177
178fn yes(value: Option<&str>) -> bool {
179    value == Some("yes")
180}
181
182fn placement_of(element: &Xml) -> Option<Placement> {
183    element
184        .get("placement")
185        .and_then(|name| Placement::from_name(name).ok())
186}
187
188/// music21's `musicXMLTypeToType`.
189fn duration_type(name: &str) -> Result<DurationType> {
190    let name = match name {
191        "long" => "longa",
192        "32th" => "32nd",
193        other => other,
194    };
195    DurationType::from_music21_name(name)
196        .ok_or_else(|| import_error(format!("found unknown MusicXML type: {name}")))
197}
198
199// ------------------------------------------------------------ what is read
200
201/// One element of a measure as it was read.
202#[derive(Clone, Debug)]
203struct Item {
204    offset: FloatType,
205    /// The order it was put into its stream, which settles the order of
206    /// two elements music21 sorts alike.
207    seq: usize,
208    staff: i32,
209    /// What spanners know it by; nothing for a copy.
210    uid: Option<usize>,
211    element: StreamElement,
212}
213
214impl Item {
215    /// music21's `classSortOrder`.
216    fn order(&self) -> i32 {
217        self.element.class_sort_order()
218    }
219
220    fn is_grace(&self) -> bool {
221        self.element.duration().is_some_and(Duration::is_grace)
222    }
223
224    /// Whether it lasts, as a note, chord or rest does: music21's
225    /// `GeneralNote` that is not a chord symbol.
226    fn is_note_or_rest(&self) -> bool {
227        matches!(
228            self.element,
229            StreamElement::Note(_)
230                | StreamElement::Chord(_)
231                | StreamElement::Rest(_)
232                | StreamElement::Unpitched(_)
233                | StreamElement::PercussionChord(_)
234        )
235    }
236}
237
238/// Elements in the order music21 sorts a stream into: by offset, then by
239/// class, grace notes ahead of the note they lean on, then as inserted.
240fn sorted(mut items: Vec<Item>) -> Vec<Item> {
241    items.sort_by(|left, right| {
242        left.offset
243            .total_cmp(&right.offset)
244            .then(left.order().cmp(&right.order()))
245            .then(right.is_grace().cmp(&left.is_grace()))
246            .then(left.seq.cmp(&right.seq))
247    });
248    items
249}
250
251#[derive(Clone, Debug, Default)]
252struct VoiceIr {
253    id: String,
254    items: Vec<Item>,
255}
256
257#[derive(Clone, Debug, Default)]
258struct MeasureIr {
259    offset: FloatType,
260    number: IntegerType,
261    suffix: Option<String>,
262    hidden: bool,
263    left: Option<Barline>,
264    right: Option<Barline>,
265    ending: Option<Ending>,
266    padding_left: FloatType,
267    padding_right: FloatType,
268    measure_numbering: Option<String>,
269    items: Vec<Item>,
270    voices: Vec<VoiceIr>,
271}
272
273impl MeasureIr {
274    /// Where the last thing in the measure ends: music21's `highestTime`.
275    fn highest_time(&self) -> FloatType {
276        let end = |item: &Item| item.offset + item.element.quarter_length();
277        self.items
278            .iter()
279            .chain(self.voices.iter().flat_map(|voice| &voice.items))
280            .map(end)
281            .fold(0.0, FloatType::max)
282    }
283
284    /// Where the last thing standing in the measure itself ends, its voices
285    /// counted as empty: what music21's `highestTime` answers while the
286    /// measure is still being read, each voice's length having been worked
287    /// out before anything was put in it.
288    fn highest_time_outside_voices(&self) -> FloatType {
289        self.items
290            .iter()
291            .map(|item| item.offset + item.element.quarter_length())
292            .fold(0.0, FloatType::max)
293    }
294}
295
296/// A spanner while its ends are still being met.
297#[derive(Clone, Debug)]
298struct Building {
299    kind: SpannerKind,
300    id_local: Option<String>,
301    complete: bool,
302    uids: Vec<usize>,
303    placement: Option<Placement>,
304    line_type: Option<String>,
305    ends: LineEnds,
306    shift: Option<OctaveShift>,
307    pedal: Pedal,
308    arpeggio: ArpeggioType,
309    slide: SlideType,
310    marks: u8,
311    /// Whether it has been put into a part already.
312    placed: bool,
313}
314
315impl Building {
316    fn new(kind: SpannerKind, id_local: Option<&str>) -> Self {
317        Self {
318            kind,
319            id_local: id_local.map(str::to_string),
320            complete: false,
321            uids: Vec::new(),
322            placement: match kind {
323                SpannerKind::Crescendo | SpannerKind::Diminuendo => Some(Placement::Below),
324                SpannerKind::Ottava | SpannerKind::Line => Some(Placement::Above),
325                _ => None,
326            },
327            line_type: (kind == SpannerKind::Glissando).then(|| "wavy".to_string()),
328            ends: LineEnds::default(),
329            shift: None,
330            pedal: Pedal::default(),
331            arpeggio: ArpeggioType::Normal,
332            slide: SlideType::Chromatic,
333            marks: 3,
334            placed: false,
335        }
336    }
337
338    fn add(&mut self, uid: usize) {
339        if !self.uids.contains(&uid) {
340            self.uids.push(uid);
341        }
342    }
343
344    /// music21's `insertFirstSpannedElement`.
345    fn add_first(&mut self, uid: usize) {
346        if !self.uids.contains(&uid) {
347            self.uids.insert(0, uid);
348        }
349    }
350
351    /// The finished spanner, its elements named by where they stand.
352    fn finish(&self, position_of: &dyn Fn(usize) -> Option<usize>) -> Spanner {
353        let positions: Vec<Option<usize>> = self.uids.iter().map(|uid| position_of(*uid)).collect();
354        let mut spanner = match self.kind {
355            SpannerKind::Crescendo | SpannerKind::Diminuendo => {
356                let mut wedge = Spanner::wedge(self.kind, Vec::new());
357                wedge = with_positions(wedge, positions);
358                wedge
359            }
360            SpannerKind::Line => {
361                let mut line = with_positions(Spanner::line(Vec::new()), positions);
362                line.set_ends(self.ends);
363                line
364            }
365            _ => Spanner::with_unplaced(self.kind, positions),
366        };
367        spanner.set_placement(self.placement);
368        if self.kind != SpannerKind::Crescendo && self.kind != SpannerKind::Diminuendo {
369            spanner.set_line_type(self.line_type.clone());
370        }
371        if self.kind == SpannerKind::Line {
372            spanner.set_line_type(Some(
373                self.line_type
374                    .clone()
375                    .unwrap_or_else(|| "solid".to_string()),
376            ));
377        }
378        spanner.set_shift(self.shift);
379        if self.kind == SpannerKind::PedalMark {
380            spanner.set_pedal(Some(self.pedal));
381        }
382        if self.kind == SpannerKind::Glissando {
383            spanner.set_glissando_details(Some(Glissando {
384                slide_type: self.slide,
385                label: None,
386            }));
387        }
388        if self.kind == SpannerKind::TremoloSpanner {
389            // Read already within the range a tremolo allows.
390            let _ = spanner.set_number_of_marks(Some(self.marks));
391        }
392        spanner.set_arpeggio(self.arpeggio);
393        spanner
394    }
395}
396
397/// A spanner of the same kind and drawing over other positions.
398fn with_positions(template: Spanner, positions: Vec<Option<usize>>) -> Spanner {
399    let mut spanner = Spanner::with_unplaced(template.kind(), positions);
400    spanner.set_placement(template.placement());
401    spanner.set_line_type(template.line_type().map(str::to_string));
402    spanner.set_spread(template.spread());
403    spanner
404}
405
406/// A first or second ending while its measures are still being met:
407/// music21's `RepeatBracket`.
408#[derive(Clone, Debug)]
409struct Bracket {
410    numbers: Vec<u32>,
411    measures: Vec<usize>,
412    complete: bool,
413}
414
415/// music21's `RepeatBracket.number` setter: nothing at all is nought,
416/// which is written as no number; otherwise `1`, `1, 2` or `1-3`.
417fn bracket_numbers(text: &str) -> Vec<u32> {
418    if text.is_empty() {
419        return vec![0];
420    }
421    let number = |piece: &str| piece.trim().parse::<u32>().ok();
422    let read = if let Some((low, high)) = text.split_once('-') {
423        number(low)
424            .zip(number(high))
425            .map(|(low, high)| (low..=high).collect())
426    } else if text.contains(',') {
427        text.split(',').map(number).collect::<Option<Vec<u32>>>()
428    } else {
429        number(text).map(|number| vec![number])
430    };
431    // music21 falls back to a first ending where it cannot read one.
432    read.unwrap_or_else(|| vec![1])
433}
434
435// ----------------------------------------------------------------- importer
436
437#[derive(Default)]
438struct Importer {
439    spanners: Vec<Building>,
440    /// The spanners waiting for the next note, chord or rest, which becomes
441    /// the first thing the one at the front joins.
442    pending: std::collections::VecDeque<usize>,
443    /// Spanners in the order music21's score holds them once it is read, by
444    /// where each stands in `spanners`: one finished in a part may still be
445    /// handed the first note of the next by the queue.
446    finished: Vec<usize>,
447    next_uid: usize,
448    finale: bool,
449}
450
451struct PartGroup<'a> {
452    element: &'a Xml,
453    number: IntegerType,
454    part_ids: Vec<String>,
455}
456
457/// A part as it goes into the score, with the uid of each leaf in the order
458/// the score's leaves run.
459struct BuiltPart {
460    /// The id the part list knows it by, a staff of a part written on
461    /// several being `<part>-Staff<n>`.
462    key: String,
463    stream: Stream,
464    uids: Vec<Option<usize>>,
465}
466
467impl Importer {
468    fn uid(&mut self) -> usize {
469        self.next_uid += 1;
470        self.next_uid
471    }
472
473    /// music21's `xmlRootToScore`.
474    fn score(mut self, root: &Xml) -> Result<Stream> {
475        let metadata = self.metadata(root);
476        let defaults = root.find("defaults").map(score_layout);
477        let credits = root
478            .find_all("credit")
479            .map(text_box)
480            .collect::<Result<Vec<_>>>()?;
481        let (score_parts, groups) = part_list(root);
482
483        let mut built: Vec<BuiltPart> = Vec::new();
484        let mut staff_groups: Vec<StaffGroup> = Vec::new();
485        for part in root.find_all("part") {
486            let id = match part.get("id") {
487                Some(id) => id.to_string(),
488                None => match score_parts.first() {
489                    Some((id, _)) => id.clone(),
490                    None => continue,
491                },
492            };
493            let Some((_, score_part)) = score_parts.iter().find(|(known, _)| *known == id) else {
494                continue;
495            };
496            let first = built.len();
497            let staves = PartParser::new(&mut self, part, score_part, &id).parse()?;
498            let name = staves
499                .first()
500                .and_then(|staff| staff.stream.name().map(str::to_string));
501            let count = staves.len();
502            let split = staves
503                .first()
504                .is_some_and(|staff| staff.stream.kind() == StreamKind::PartStaff && count > 0)
505                && staves.iter().all(|staff| staff.key != id);
506            built.extend(staves);
507            if split {
508                let mut group = StaffGroup::new((first..first + count).collect());
509                group.set_name(name);
510                group.set_symbol(Some("brace"))?;
511                group.set_bar_together(Some(BarTogether::Yes));
512                group.set_name_hidden(true);
513                staff_groups.push(group);
514            }
515        }
516
517        // music21's `partGroups`.
518        for group in &groups {
519            let mut positions = Vec::new();
520            for part_id in &group.part_ids {
521                match built.iter().position(|part| part.key == *part_id) {
522                    Some(position) => positions.push(position),
523                    None => {
524                        let prefix = format!("{part_id}-Staff");
525                        let mut staves: Vec<(usize, &str)> = built
526                            .iter()
527                            .enumerate()
528                            .filter(|(_, part)| part.key.starts_with(&prefix))
529                            .map(|(position, part)| (position, part.key.as_str()))
530                            .collect();
531                        staves.sort_by(|left, right| left.1.cmp(right.1));
532                        if staves.is_empty() {
533                            return Err(import_error(format!(
534                                "Cannot find part in m21PartObjectsById dictionary by Id: {part_id}"
535                            )));
536                        }
537                        positions.extend(staves.into_iter().map(|(position, _)| position));
538                    }
539                }
540            }
541            let mut read = StaffGroup::new(positions);
542            let text = |tag: &str| {
543                group
544                    .element
545                    .find(tag)
546                    .and_then(Xml::text)
547                    .filter(|text| !text.is_empty())
548                    .map(str::to_string)
549            };
550            read.set_name(text("group-name"));
551            read.set_abbreviation(text("group-abbreviation"));
552            match group.element.find("group-symbol") {
553                Some(_) => {
554                    if let Some(symbol) = text("group-symbol") {
555                        read.set_symbol(Some(&symbol))?;
556                    }
557                }
558                None => read.set_symbol(Some("brace"))?,
559            }
560            read.set_bar_together(Some(match text("group-barline").as_deref() {
561                Some("no") => BarTogether::No,
562                Some("Mensurstrich") => BarTogether::Mensurstrich,
563                _ => BarTogether::Yes,
564            }));
565            staff_groups.push(read);
566        }
567
568        // Whatever was completed and never put into a part goes into the
569        // score, an arpeggio across parts among them.
570        for building in &mut self.spanners {
571            if building.kind == SpannerKind::ArpeggioMark {
572                building.complete = true;
573            }
574        }
575        let late: Vec<usize> = (0..self.spanners.len())
576            .filter(|index| self.spanners[*index].complete && !self.spanners[*index].placed)
577            .collect();
578        self.finished.extend(late);
579
580        // The credits sort before the parts, as music21 sorts its text boxes,
581        // and stand before the parts' leaves where spanners count them.
582        let mut uids: Vec<Option<usize>> = vec![None; credits.len()];
583        let mut events: Vec<StreamEvent> = credits
584            .into_iter()
585            .map(|credit| StreamEvent::new(0.0, StreamElement::TextBox(Box::new(credit))))
586            .collect();
587        for part in built {
588            uids.extend(part.uids);
589            events.push(StreamEvent::new(0.0, part.stream));
590        }
591        if let Some(defaults) = defaults {
592            // music21 inserts it first, and sorts it after the parts.
593            events.push(StreamEvent::new(
594                0.0,
595                StreamElement::Layout(Layout::Score(Box::new(defaults))),
596            ));
597        }
598        let mut assembled = Stream::from_events(events);
599        assembled.set_kind(StreamKind::Score);
600        assembled.set_metadata(Some(metadata));
601        for group in staff_groups {
602            assembled.add_staff_group(group);
603        }
604        let position_of = |uid: usize| uids.iter().position(|held| *held == Some(uid));
605        for index in &self.finished {
606            assembled.add_spanner(self.spanners[*index].finish(&position_of));
607        }
608        Ok(assembled)
609    }
610
611    /// music21's `xmlMetadata`.
612    fn metadata(&mut self, root: &Xml) -> Metadata {
613        let mut metadata = Metadata::new();
614        let add = |metadata: &mut Metadata, element: &Xml, tag: &str, name: &str| {
615            if let Some(text) = element.find(tag).and_then(Xml::text)
616                && !text.is_empty()
617            {
618                metadata.add_text(name, text);
619            }
620        };
621        if let Some(work) = root.find("work") {
622            add(&mut metadata, work, "work-title", "title");
623            add(&mut metadata, work, "work-number", "number");
624            add(&mut metadata, work, "opus", "opusNumber");
625        }
626        add(&mut metadata, root, "movement-number", "movementNumber");
627        add(&mut metadata, root, "movement-title", "movementName");
628        // A title that only repeats the movement name was put there by a
629        // writer for the sake of renderers, and is taken out again.
630        let texts = |metadata: &Metadata, name: &str| -> Vec<String> {
631            metadata
632                .get(name)
633                .iter()
634                .map(|value| value.text().to_string())
635                .collect()
636        };
637        if texts(&metadata, "title") == texts(&metadata, "movementName") {
638            metadata.set("title", Vec::new());
639        }
640
641        let Some(identification) = root.find("identification") else {
642            return metadata;
643        };
644        for creator in identification.find_all("creator") {
645            let name = creator.stripped();
646            match creator.get("type") {
647                Some(role) if crate::metadata::is_contributor_unique_name(role) => {
648                    metadata.add(role, MetadataValue::with_role(name, role));
649                }
650                Some(role) => {
651                    metadata.add("otherContributor", MetadataValue::with_role(name, role));
652                }
653                None => metadata.add("otherContributor", MetadataValue::new(name)),
654            }
655        }
656        if let Some(rights) = identification.find("rights") {
657            // music21 writes a copyright that says nothing as `None`.
658            let text = match rights.text() {
659                Some(text) => text.trim(),
660                None => "None",
661            };
662            metadata.add(
663                "copyright",
664                match rights.get("type") {
665                    Some(role) => MetadataValue::with_role(text, role),
666                    None => MetadataValue::new(text),
667                },
668            );
669        }
670        if let Some(encoding) = identification.find("encoding") {
671            let mut found = false;
672            for software in encoding.find_all("software") {
673                let text = software.stripped();
674                if text.is_empty() {
675                    continue;
676                }
677                if !found && text.contains("Finale") {
678                    self.finale = true;
679                }
680                found = true;
681                metadata.add_text("software", text);
682            }
683        }
684        if let Some(miscellaneous) = identification.find("miscellaneous") {
685            for field in miscellaneous.find_all("miscellaneous-field") {
686                let Some(name) = field.get("name") else {
687                    continue;
688                };
689                let value = field.text().unwrap_or("");
690                // A namespaced name is kept under the unique name it has.
691                let unique = crate::metadata::STANDARD_PROPERTIES
692                    .iter()
693                    .find(|(unique, namespaced, _)| *unique == name || *namespaced == name)
694                    .map_or(name, |(unique, _, _)| unique);
695                metadata.add_text(unique, value);
696            }
697        }
698        metadata
699    }
700}
701
702/// music21's `parsePartList`: each `<score-part>` by id, and the groups.
703fn part_list(root: &Xml) -> (Vec<(String, &Xml)>, Vec<PartGroup<'_>>) {
704    let mut score_parts = Vec::new();
705    let mut groups: Vec<PartGroup<'_>> = Vec::new();
706    let mut open: Vec<usize> = Vec::new();
707    let Some(list) = root.find("part-list") else {
708        return (score_parts, groups);
709    };
710    for element in &list.children {
711        match element.tag.as_str() {
712            "score-part" => {
713                let id = element.get("id").unwrap_or_default().to_string();
714                for index in &open {
715                    groups[*index].part_ids.push(id.clone());
716                }
717                score_parts.push((id, element));
718            }
719            "part-group" => {
720                let number = element.get("number").and_then(parse_int).unwrap_or(1);
721                match element.get("type") {
722                    Some("start") => {
723                        open.push(groups.len());
724                        groups.push(PartGroup {
725                            element,
726                            number,
727                            part_ids: Vec::new(),
728                        });
729                    }
730                    Some("stop") => open.retain(|index| groups[*index].number != number),
731                    _ => {}
732                }
733            }
734            _ => {}
735        }
736    }
737    (score_parts, groups)
738}
739
740// --------------------------------------------------------------------- part
741
742/// music21's `PartParser`.
743struct PartParser<'a, 'x> {
744    importer: &'a mut Importer,
745    part: &'x Xml,
746    score_part: &'x Xml,
747    id: String,
748    name: Option<String>,
749    abbreviation: Option<String>,
750    name_hidden: bool,
751    abbreviation_hidden: bool,
752    /// The part's instruments with where each starts.
753    instruments: Vec<(FloatType, Instrument)>,
754    measures: Vec<MeasureIr>,
755    brackets: Vec<Bracket>,
756    multi_staff: bool,
757    max_staves: i32,
758    last_divisions: FloatType,
759    last_meter: Option<TimeSignature>,
760    last_measure_was_short: bool,
761    last_measure_offset: FloatType,
762    last_measure_number: IntegerType,
763    last_number_suffix: Option<String>,
764    last_clefs: Vec<(i32, Clef)>,
765    active_tuplets: [Option<Tuplet>; 7],
766    first_measure_parsed: bool,
767    /// music21's `PartParser.atSoundingPitch`: a part is at sounding pitch
768    /// until a `<transpose>` says otherwise.
769    at_sounding_pitch: bool,
770    /// The arpeggios across chords, by their number and the offset in the
771    /// part they stand at, each as where it stands among the spanners.
772    arpeggios: Vec<((String, FloatType), usize)>,
773}
774
775impl<'a, 'x> PartParser<'a, 'x> {
776    fn new(importer: &'a mut Importer, part: &'x Xml, score_part: &'x Xml, id: &str) -> Self {
777        let multi_staff = part.find_all("measure").any(|measure| {
778            measure.find_all("attributes").any(|attributes| {
779                attributes
780                    .find_all("staves")
781                    .any(|staves| parse_int(staves.stripped()).is_some_and(|count| count > 1))
782            })
783        });
784        Self {
785            importer,
786            part,
787            score_part,
788            id: id.to_string(),
789            name: None,
790            abbreviation: None,
791            name_hidden: false,
792            abbreviation_hidden: false,
793            instruments: Vec::new(),
794            measures: Vec::new(),
795            brackets: Vec::new(),
796            multi_staff,
797            max_staves: 1,
798            last_divisions: 10080.0,
799            last_meter: None,
800            last_measure_was_short: false,
801            last_measure_offset: 0.0,
802            last_measure_number: 0,
803            last_number_suffix: None,
804            last_clefs: Vec::new(),
805            active_tuplets: Default::default(),
806            first_measure_parsed: false,
807            at_sounding_pitch: true,
808            arpeggios: Vec::new(),
809        }
810    }
811
812    /// music21's `PartParser.parse`: the part, or one part for each of its
813    /// staves.
814    fn parse(mut self) -> Result<Vec<BuiltPart>> {
815        self.score_part_info()?;
816        let part = self.part;
817        let mut last_forward: Option<(usize, bool)> = None;
818        for measure in part.find_all("measure") {
819            last_forward = self.measure(measure)?;
820        }
821        self.remove_finale_ending_rest(last_forward);
822
823        // Endings, from the brackets over the measures.
824        for bracket in &self.brackets {
825            for (place, index) in bracket.measures.iter().enumerate() {
826                let measure = &mut self.measures[*index];
827                if measure.ending.is_none() {
828                    measure.ending = Some(Ending::new(
829                        bracket.numbers.clone(),
830                        place == 0,
831                        place + 1 == bracket.measures.len(),
832                    ));
833                }
834            }
835        }
836
837        // Spanners completed by now belong to this part, octave lines last.
838        let mut ours: Vec<usize> = Vec::new();
839        for pass in [false, true] {
840            for (index, building) in self.importer.spanners.iter_mut().enumerate() {
841                let ottava = building.kind == SpannerKind::Ottava;
842                if building.complete && !building.placed && ottava == pass {
843                    building.placed = true;
844                    ours.push(index);
845                }
846            }
847        }
848        self.importer.finished.extend(ours);
849
850        if self.max_staves > 1 {
851            self.separate_staves()
852        } else {
853            let kind = if self.multi_staff {
854                StreamKind::PartStaff
855            } else {
856                StreamKind::Part
857            };
858            let measures = std::mem::take(&mut self.measures);
859            let built = self.build(kind, None, measures, &self.instruments.clone());
860            Ok(vec![BuiltPart {
861                key: self.id.clone(),
862                stream: built.0,
863                uids: built.1,
864            }])
865        }
866    }
867
868    /// Puts a part together out of its measures.
869    fn build(
870        &self,
871        kind: StreamKind,
872        id: Option<String>,
873        measures: Vec<MeasureIr>,
874        instruments: &[(FloatType, Instrument)],
875    ) -> (Stream, Vec<Option<usize>>) {
876        let mut items: Vec<(Item, Vec<Option<usize>>)> = Vec::new();
877        let mut seq = 0;
878        for (offset, instrument) in instruments {
879            items.push((
880                Item {
881                    offset: *offset,
882                    seq,
883                    staff: NO_STAFF,
884                    uid: None,
885                    element: instrument.clone().into(),
886                },
887                vec![None],
888            ));
889            seq += 1;
890        }
891        for measure in measures {
892            let offset = measure.offset;
893            let (stream, uids) = build_measure(measure);
894            items.push((
895                Item {
896                    offset,
897                    seq,
898                    staff: NO_STAFF,
899                    uid: None,
900                    element: stream.into(),
901                },
902                uids,
903            ));
904            seq += 1;
905        }
906        items.sort_by(|(left, _), (right, _)| {
907            left.offset
908                .total_cmp(&right.offset)
909                .then(left.order().cmp(&right.order()))
910                .then(left.seq.cmp(&right.seq))
911        });
912        let mut uids = Vec::new();
913        let mut events = Vec::new();
914        for (item, leaf_uids) in items {
915            uids.extend(leaf_uids);
916            events.push(StreamEvent::new(item.offset, item.element));
917        }
918        let mut stream = Stream::from_events(events);
919        stream.set_kind(kind);
920        stream.set_id(id.or_else(|| {
921            self.instruments
922                .first()
923                .and_then(|(_, instrument)| instrument.best_name().map(str::to_string))
924        }));
925        stream.set_name(self.name.clone());
926        stream.set_abbreviation(self.abbreviation.clone());
927        // A staff split off a part is music21's `template` of it, which
928        // copies this.
929        stream.set_at_sounding_pitch(Some(self.at_sounding_pitch));
930        // A staff split off a part is a fresh stream, which prints both.
931        if kind != StreamKind::PartStaff || self.max_staves <= 1 {
932            stream.set_name_hidden(self.name_hidden);
933            stream.set_abbreviation_hidden(self.abbreviation_hidden);
934        }
935        (stream, uids)
936    }
937
938    /// music21's `parseXmlScorePart` and `getDefaultInstrument`.
939    fn score_part_info(&mut self) -> Result<()> {
940        let clean = |text: &str| text.trim().replace('\n', " ");
941        let score_part = self.score_part;
942        let named = |tag: &str| {
943            score_part
944                .find(tag)
945                .and_then(Xml::text)
946                .filter(|text| !text.is_empty())
947                .map(clean)
948        };
949        let part_name = named("part-name");
950        let part_abbreviation = named("part-abbreviation");
951        let name_hidden = score_part
952            .find("part-name")
953            .is_some_and(|name| name.get("print-object") == Some("no"));
954        let abbreviation_hidden = score_part
955            .find("part-abbreviation")
956            .is_some_and(|name| name.get("print-object") == Some("no"));
957
958        let adjusted =
959            |text: &str| -> Option<u8> { parse_int(text).map(|value| (value - 1).max(0) as u8) };
960        let mut instrument: Option<Instrument> = None;
961        let mut unpitched = false;
962        if let Some(midi) = score_part.find("midi-instrument") {
963            let unpitched_text = midi.child_text("midi-unpitched");
964            let program_text = midi.child_text("midi-program");
965            if !unpitched_text.is_empty() {
966                unpitched = true;
967                let pitch = adjusted(unpitched_text).unwrap_or(0);
968                instrument = Some(percussion_instrument(pitch));
969            } else if !program_text.is_empty() {
970                let mut read = adjusted(program_text)
971                    .and_then(|program| Instrument::from_midi_program(program).ok())
972                    .unwrap_or_default();
973                if let Some(channel) = midi
974                    .find("midi-channel")
975                    .and_then(Xml::text)
976                    .and_then(adjusted)
977                {
978                    read.set_midi_channel(Some(channel));
979                }
980                instrument = Some(read);
981            }
982        }
983        let mut instrument = instrument.unwrap_or_default();
984        let score_instrument = score_part.find("score-instrument");
985        if let Some(score_instrument) = score_instrument
986            && !unpitched
987        {
988            let name = score_instrument.child_text("instrument-name");
989            if !name.is_empty() {
990                let mut from_name =
991                    Instrument::from_name(name, SearchLanguage::All).unwrap_or_default();
992                from_name.set_midi_channel(instrument.midi_channel());
993                if from_name.midi_program() == instrument.midi_program() || instrument.is_a("Piano")
994                {
995                    instrument = from_name;
996                }
997            }
998        }
999        instrument.set_part_id(Some(self.id.clone()));
1000        instrument.set_part_name(part_name.clone());
1001        instrument.set_part_abbreviation(part_abbreviation.clone());
1002        if let Some(score_instrument) = score_instrument {
1003            let named = |tag: &str| {
1004                score_instrument
1005                    .find(tag)
1006                    .and_then(Xml::text)
1007                    .filter(|text| !text.is_empty())
1008            };
1009            if let Some(name) = named("instrument-name") {
1010                instrument.set_name(Some(clean(name)));
1011            }
1012            if let Some(abbreviation) = named("instrument-abbreviation") {
1013                instrument.set_abbreviation(Some(clean(abbreviation)));
1014            }
1015            if let Some(sound) = named("instrument-sound") {
1016                instrument.set_sound(Some(sound.to_string()));
1017            }
1018        }
1019        self.name = part_name;
1020        self.abbreviation = part_abbreviation;
1021        self.name_hidden = name_hidden;
1022        self.abbreviation_hidden = abbreviation_hidden;
1023        self.instruments.push((0.0, instrument));
1024        Ok(())
1025    }
1026
1027    /// music21's `xmlMeasureToMeasure`. Hands back where a rest a `<forward>`
1028    /// of Finale's made stands, when it is the last thing read.
1029    fn measure(&mut self, element: &'x Xml) -> Result<Option<(usize, bool)>> {
1030        let mut parser = MeasureParser::new(self, element);
1031        parser.parse()?;
1032        let MeasureParser {
1033            mut measure,
1034            staves,
1035            transposition,
1036            full_measure_rest,
1037            marked_full,
1038            finale_forward,
1039            use_voices,
1040            divisions,
1041            ..
1042        } = parser;
1043        self.last_divisions = divisions;
1044        self.max_staves = self.max_staves.max(staves);
1045        if let Some(transposition) = transposition {
1046            self.update_transposition(transposition);
1047        }
1048        self.first_measure_parsed = true;
1049
1050        // music21's `setLastMeasureInfo`.
1051        if measure.number != self.last_measure_number {
1052            self.last_measure_number = measure.number;
1053            self.last_number_suffix = measure.suffix.clone();
1054        }
1055        let own_meter = measure.items.iter().find_map(|item| match &item.element {
1056            StreamElement::TimeSignature(meter) => Some(meter.clone()),
1057            _ => None,
1058        });
1059        if let Some(meter) = own_meter {
1060            self.last_meter = Some(meter);
1061        } else if self.last_meter.is_none() {
1062            self.last_meter = Some(TimeSignature::new(4, 4)?);
1063        }
1064        let bar_length = self
1065            .last_meter
1066            .as_ref()
1067            .map_or(4.0, TimeSignature::bar_quarter_length);
1068
1069        if full_measure_rest {
1070            // The first rest as music21 walks the measure: what stands in
1071            // the measure itself in order, each voice read through where it
1072            // stands, which is at the start and after the signatures.
1073            let mut walk: Vec<(FloatType, i32, bool, usize, Option<usize>)> = Vec::new();
1074            for (index, voice) in measure.voices.iter().enumerate() {
1075                let first = sorted(voice.items.clone())
1076                    .into_iter()
1077                    .find(|item| matches!(item.element, StreamElement::Rest(_)))
1078                    .and_then(|item| item.uid);
1079                walk.push((0.0, 5, false, index, first));
1080            }
1081            for item in &measure.items {
1082                let rest = matches!(item.element, StreamElement::Rest(_));
1083                walk.push((
1084                    item.offset,
1085                    item.order(),
1086                    !item.is_grace(),
1087                    item.seq + measure.voices.len(),
1088                    item.uid.filter(|_| rest),
1089                ));
1090            }
1091            walk.sort_by(|left, right| {
1092                left.0
1093                    .total_cmp(&right.0)
1094                    .then(left.1.cmp(&right.1))
1095                    .then(left.2.cmp(&right.2))
1096                    .then(left.3.cmp(&right.3))
1097            });
1098            let first_uid = walk.into_iter().find_map(|entry| entry.4);
1099            let first = measure
1100                .items
1101                .iter_mut()
1102                .chain(measure.voices.iter_mut().flat_map(|voice| &mut voice.items))
1103                .filter(|item| first_uid.is_some() && item.uid == first_uid)
1104                .find_map(|item| match &mut item.element {
1105                    StreamElement::Rest(rest) => Some((item.uid, rest)),
1106                    _ => None,
1107                });
1108            if let Some((uid, rest)) = first {
1109                let marked_full = uid.is_some_and(|uid| marked_full.contains(&uid));
1110                let duration = rest.duration().clone();
1111                let whole = matches!(
1112                    duration.type_and_dots(),
1113                    Some((DurationType::Whole | DurationType::Breve, 0))
1114                ) && duration.tuplets().is_empty();
1115                if marked_full || (duration.quarter_length() != bar_length && whole) {
1116                    rest.set_duration(Duration::new(bar_length)?);
1117                    rest.set_full_measure(Some(true));
1118                }
1119            }
1120        }
1121
1122        // music21 sums the lengths as floats and snaps where it inserts.
1123        measure.offset = op_frac(self.last_measure_offset);
1124        let index = self.measures.len();
1125
1126        // music21's `adjustTimeAttributesFromMeasure`.
1127        let highest = measure.highest_time();
1128        let has_notes = measure
1129            .items
1130            .iter()
1131            .chain(measure.voices.iter().flat_map(|voice| &voice.items))
1132            .any(Item::is_note_or_rest);
1133        let shift = if highest == bar_length {
1134            highest
1135        } else if highest > bar_length {
1136            let difference = highest - bar_length;
1137            let near = |unit: FloatType| {
1138                let multiple = (difference / unit).round() * unit;
1139                (difference - multiple).abs() < 1e-6
1140            };
1141            if difference > 0.5 || near(0.0625) || near(1.0 / 12.0) {
1142                highest
1143            } else {
1144                bar_length
1145            }
1146        } else if highest == 0.0 && !has_notes {
1147            // Some writers leave the rest out of an empty measure.
1148            let uid = self.importer.uid();
1149            measure.items.push(Item {
1150                offset: 0.0,
1151                seq: usize::MAX,
1152                staff: NO_STAFF,
1153                uid: Some(uid),
1154                element: Rest::new(Duration::new(bar_length)?).into(),
1155            });
1156            self.last_measure_was_short = false;
1157            bar_length
1158        } else {
1159            // A short measure is a pickup where it opens the part or follows
1160            // another short one, and is cut short at its end otherwise.
1161            // music21 pads a pickup as `padAsAnacrusis` does, by the share
1162            // of the bar it lacks, and a short measure by what it lacks,
1163            // unsnapped.
1164            let anacrusis = || op_frac(bar_length * (1.0 - op_frac(highest / bar_length)));
1165            if self.last_measure_offset == 0.0 {
1166                if highest < bar_length {
1167                    measure.padding_left = anacrusis();
1168                }
1169            } else if self.last_measure_was_short {
1170                if highest < bar_length {
1171                    measure.padding_left = anacrusis();
1172                    self.last_measure_was_short = false;
1173                }
1174            } else {
1175                if highest < bar_length {
1176                    measure.padding_right = bar_length - op_frac(highest);
1177                }
1178                self.last_measure_was_short = highest < bar_length;
1179            }
1180            highest
1181        };
1182        self.last_measure_offset += shift;
1183        self.measures.push(measure);
1184        Ok(finale_forward.map(|place| (index, place.is_some() && !use_voices)))
1185    }
1186
1187    /// music21's `removeFinaleIncorrectEndingForwardRest`: a hidden rest
1188    /// Finale's `<forward>` left as the last thing in the last measure.
1189    fn remove_finale_ending_rest(&mut self, last_forward: Option<(usize, bool)>) {
1190        let Some((index, true)) = last_forward else {
1191            return;
1192        };
1193        let Some(measure) = self.measures.get_mut(index) else {
1194            return;
1195        };
1196        let last = sorted(measure.items.clone()).into_iter().rfind(|item| {
1197            item.is_note_or_rest() || matches!(item.element, StreamElement::ChordSymbol(_))
1198        });
1199        if let Some(last) = last
1200            && matches!(&last.element, StreamElement::Rest(rest) if rest.hidden())
1201        {
1202            measure.items.retain(|item| item.seq != last.seq);
1203        }
1204    }
1205
1206    /// music21's `updateTransposition`.
1207    fn update_transposition(&mut self, transposition: Interval) {
1208        let active = self.instruments.last().cloned();
1209        match active {
1210            Some((_, instrument))
1211                if instrument.transposition().is_none() && !self.first_measure_parsed => {}
1212            Some((_, instrument)) => {
1213                let differs = instrument
1214                    .transposition()
1215                    .is_none_or(|held| held.directed_name() != transposition.directed_name());
1216                if differs {
1217                    self.instruments
1218                        .push((self.last_measure_offset, instrument.clone()));
1219                }
1220            }
1221            None => self
1222                .instruments
1223                .push((self.last_measure_offset, Instrument::new())),
1224        }
1225        if let Some((_, instrument)) = self.instruments.last_mut() {
1226            instrument.set_transposition(Some(transposition));
1227        }
1228        self.at_sounding_pitch = false;
1229    }
1230
1231    /// music21's `separateOutPartStaves`: a part written on several staves
1232    /// becomes one part for each, what is written on no staff in particular
1233    /// going into every one.
1234    fn separate_staves(&mut self) -> Result<Vec<BuiltPart>> {
1235        let mut keys: Vec<i32> = Vec::new();
1236        for measure in &self.measures {
1237            for item in measure
1238                .items
1239                .iter()
1240                .chain(measure.voices.iter().flat_map(|voice| &voice.items))
1241            {
1242                if item.staff != NO_STAFF && !keys.contains(&item.staff) {
1243                    keys.push(item.staff);
1244                }
1245            }
1246        }
1247        keys.sort_unstable();
1248
1249        let source = std::mem::take(&mut self.measures);
1250        let mut taken: Vec<usize> = Vec::new();
1251        let mut out = Vec::new();
1252        for key in keys {
1253            let mut measures = Vec::new();
1254            for measure in &source {
1255                let mut copy = MeasureIr {
1256                    offset: measure.offset,
1257                    number: measure.number,
1258                    suffix: measure.suffix.clone(),
1259                    // A measure made for a staff shows its number, whatever
1260                    // the one it was made from did.
1261                    hidden: false,
1262                    left: measure.left.clone(),
1263                    right: measure.right.clone(),
1264                    ending: measure.ending.clone(),
1265                    padding_left: measure.padding_left,
1266                    padding_right: measure.padding_right,
1267                    // music21 makes the staff's measures from a template,
1268                    // which keeps nothing of how the measure is drawn.
1269                    measure_numbering: None,
1270                    items: Vec::new(),
1271                    voices: Vec::new(),
1272                };
1273                let mut seq = 0usize;
1274                let mut take = |items: &[Item], into: &mut Vec<Item>, seq: &mut usize| {
1275                    for item in sorted(items.to_vec()) {
1276                        if item.staff != NO_STAFF && item.staff != key {
1277                            continue;
1278                        }
1279                        let mut item = item;
1280                        // The element itself goes to the first staff that
1281                        // takes it, and a copy to every one after.
1282                        if taken.contains(&item.seq_key()) {
1283                            item.uid = None;
1284                        } else {
1285                            taken.push(item.seq_key());
1286                        }
1287                        item.seq = *seq;
1288                        *seq += 1;
1289                        into.push(item);
1290                    }
1291                };
1292                take(&measure.items, &mut copy.items, &mut seq);
1293                for voice in &measure.voices {
1294                    let mut items = Vec::new();
1295                    take(&voice.items, &mut items, &mut seq);
1296                    copy.voices.push(VoiceIr {
1297                        id: voice.id.clone(),
1298                        items,
1299                    });
1300                }
1301                // music21's `flattenUnnecessaryVoices`.
1302                copy.voices.retain(|voice| !voice.items.is_empty());
1303                if copy.voices.len() == 1 {
1304                    let voice = copy.voices.remove(0);
1305                    for mut item in sorted(voice.items) {
1306                        item.seq = seq;
1307                        seq += 1;
1308                        copy.items.push(item);
1309                    }
1310                }
1311                measures.push(copy);
1312            }
1313            let id = format!("{}-Staff{key}", self.id);
1314            let instruments = self.instruments.clone();
1315            let (stream, uids) = self.build(
1316                StreamKind::PartStaff,
1317                Some(id.clone()),
1318                measures,
1319                &instruments,
1320            );
1321            out.push(BuiltPart {
1322                key: id,
1323                stream,
1324                uids,
1325            });
1326        }
1327        Ok(out)
1328    }
1329}
1330
1331impl Item {
1332    /// A number no two elements read share, whatever measure or voice they
1333    /// stand in: the uid where there is one.
1334    fn seq_key(&self) -> usize {
1335        self.uid.unwrap_or(usize::MAX)
1336    }
1337}
1338
1339/// A measure put together: the stream, and the uid of each leaf in order.
1340fn build_measure(measure: MeasureIr) -> (Stream, Vec<Option<usize>>) {
1341    let mut entries: Vec<(Item, Vec<Option<usize>>)> = Vec::new();
1342    for (index, voice) in measure.voices.into_iter().enumerate() {
1343        let mut uids = Vec::new();
1344        let mut events = Vec::new();
1345        for item in sorted(voice.items) {
1346            uids.push(item.uid);
1347            events.push(StreamEvent::new(item.offset, item.element));
1348        }
1349        let mut stream = Stream::from_events(events);
1350        stream.set_kind(StreamKind::Voice);
1351        stream.set_id(Some(voice.id));
1352        entries.push((
1353            Item {
1354                offset: 0.0,
1355                // Voices are made before anything is read into the measure.
1356                seq: index,
1357                staff: NO_STAFF,
1358                uid: None,
1359                element: stream.into(),
1360            },
1361            uids,
1362        ));
1363    }
1364    let voices = entries.len();
1365    for mut item in measure.items {
1366        item.seq = item.seq.saturating_add(voices);
1367        let uid = item.uid;
1368        entries.push((item, vec![uid]));
1369    }
1370    entries.sort_by(|(left, _), (right, _)| {
1371        left.offset
1372            .total_cmp(&right.offset)
1373            .then(left.order().cmp(&right.order()))
1374            .then(right.is_grace().cmp(&left.is_grace()))
1375            .then(left.seq.cmp(&right.seq))
1376    });
1377    let mut uids = Vec::new();
1378    let mut events = Vec::new();
1379    for (item, leaf_uids) in entries {
1380        uids.extend(leaf_uids);
1381        events.push(StreamEvent::new(item.offset, item.element));
1382    }
1383    let mut stream = Stream::from_events(events);
1384    stream.set_kind(StreamKind::Measure);
1385    stream.set_number(measure.number);
1386    stream.set_number_suffix(measure.suffix);
1387    stream.set_number_hidden(measure.hidden);
1388    stream.set_left_barline(measure.left);
1389    stream.set_right_barline(measure.right);
1390    stream.set_ending(measure.ending);
1391    stream.set_padding_left(measure.padding_left);
1392    stream.set_padding_right(measure.padding_right);
1393    stream.set_measure_numbering(measure.measure_numbering);
1394    (stream, uids)
1395}
1396
1397// ------------------------------------------------------------------ measure
1398
1399/// music21's `MeasureParser`.
1400struct MeasureParser<'p, 'a, 'x> {
1401    part: &'p mut PartParser<'a, 'x>,
1402    element: &'x Xml,
1403    measure: MeasureIr,
1404    seq: usize,
1405    divisions: FloatType,
1406    staves: i32,
1407    transposition: Option<Interval>,
1408    use_voices: bool,
1409    last_voice: Option<String>,
1410    offset: FloatType,
1411    /// Notes of a chord being gathered, with the lyrics written on them.
1412    chord_notes: Vec<&'x Xml>,
1413    chord_lyrics: Vec<&'x Xml>,
1414    /// The uid of the last note, chord or rest read.
1415    last_note: Option<usize>,
1416    rests: usize,
1417    notes: usize,
1418    full_measure_rest: bool,
1419    /// The rests that said they fill their measure.
1420    marked_full: Vec<usize>,
1421    /// Whether the rest being read said so.
1422    marking_full: bool,
1423    /// Whether the last thing read was a rest made of Finale's `<forward>`;
1424    /// the inner value is its seq.
1425    finale_forward: Option<Option<usize>>,
1426    pedal_starts: Vec<(usize, FloatType)>,
1427    /// The staff layouts read here, by staff number and offset, each with
1428    /// what it was put in as, where it was put anywhere: music21's
1429    /// `staffLayoutObjects`.
1430    staff_layouts: Vec<((i64, FloatType), Option<usize>)>,
1431}
1432
1433impl<'p, 'a, 'x> MeasureParser<'p, 'a, 'x> {
1434    fn new(part: &'p mut PartParser<'a, 'x>, element: &'x Xml) -> Self {
1435        let divisions = part.last_divisions;
1436        Self {
1437            part,
1438            element,
1439            measure: MeasureIr::default(),
1440            seq: 0,
1441            divisions,
1442            staves: 1,
1443            transposition: None,
1444            use_voices: false,
1445            last_voice: None,
1446            offset: 0.0,
1447            chord_notes: Vec::new(),
1448            chord_lyrics: Vec::new(),
1449            last_note: None,
1450            rests: 0,
1451            notes: 0,
1452            full_measure_rest: false,
1453            marked_full: Vec::new(),
1454            marking_full: false,
1455            finale_forward: None,
1456            pedal_starts: Vec::new(),
1457            staff_layouts: Vec::new(),
1458        }
1459    }
1460
1461    fn parse(&mut self) -> Result<()> {
1462        self.measure_attributes();
1463        self.voice_information();
1464        let element = self.element;
1465        for (index, child) in element.children.iter().enumerate() {
1466            match child.tag.as_str() {
1467                "note" => self.note(child, element.children.get(index + 1))?,
1468                "backup" => {
1469                    if let Some(ticks) = parse_float(child.child_text("duration")) {
1470                        self.offset = op_frac(self.offset - ticks / self.divisions).max(0.0);
1471                    }
1472                }
1473                "forward" => self.forward(child)?,
1474                "direction" => self.direction(child)?,
1475                "attributes" => self.attributes(child)?,
1476                "harmony" => self.harmony(child)?,
1477                "sound" => {
1478                    if child.get("tempo").is_some() {
1479                        let total = self.offset + self.offset_of(child);
1480                        self.sound_tempo(child, None, staff_number(child), total);
1481                    }
1482                }
1483                "barline" => self.barline(child)?,
1484                "print" => self.print(child),
1485                _ => {}
1486            }
1487        }
1488        if self.rests == 1 && self.notes == 0 {
1489            self.full_measure_rest = true;
1490        }
1491        Ok(())
1492    }
1493
1494    /// Puts an element into the measure itself.
1495    fn insert(
1496        &mut self,
1497        offset: FloatType,
1498        staff: i32,
1499        element: impl Into<StreamElement>,
1500    ) -> usize {
1501        let uid = self.part.importer.uid();
1502        self.measure.items.push(Item {
1503            offset: op_frac(offset),
1504            seq: self.seq,
1505            staff,
1506            uid: Some(uid),
1507            element: element.into(),
1508        });
1509        self.seq += 1;
1510        uid
1511    }
1512
1513    /// music21's `insertInMeasureOrVoice`, for a note, chord or rest.
1514    fn insert_note(&mut self, source: &Xml, staff: i32, element: StreamElement) -> usize {
1515        let uid = self.part.importer.uid();
1516        let item = Item {
1517            offset: self.offset,
1518            seq: self.seq,
1519            staff,
1520            uid: Some(uid),
1521            element,
1522        };
1523        self.seq += 1;
1524        if self.use_voices {
1525            let written = source.child_text("voice");
1526            let voice = if written.is_empty() {
1527                self.last_voice.clone().unwrap_or_else(|| "1".to_string())
1528            } else {
1529                self.last_voice = Some(written.to_string());
1530                written.to_string()
1531            };
1532            if let Some(found) = self.measure.voices.iter_mut().find(|held| held.id == voice) {
1533                found.items.push(item);
1534                return uid;
1535            }
1536        }
1537        self.measure.items.push(item);
1538        uid
1539    }
1540
1541    /// music21's `parseMeasureAttributes` and `parseMeasureNumbers`.
1542    fn measure_attributes(&mut self) {
1543        self.measure.hidden = yes(self.element.get("implicit"));
1544        let raw = self.element.get("number").unwrap_or("");
1545        // music21's `getNumFromStr`: the digits, and whatever is left.
1546        let digits: String = raw.chars().filter(char::is_ascii_digit).collect();
1547        let rest: String = raw.chars().filter(|c| !c.is_ascii_digit()).collect();
1548        if let Ok(number) = digits.parse::<IntegerType>() {
1549            self.measure.number = number;
1550        }
1551        if !rest.is_empty() {
1552            self.measure.suffix = Some(rest);
1553        }
1554        let last = self.part.last_measure_number;
1555        if self.measure.suffix.as_deref() == Some("X") && self.measure.number != last + 1 {
1556            let mut suffix = format!("X{}", self.measure.number);
1557            if let Some(before) = &self.part.last_number_suffix {
1558                suffix = format!("{before}{suffix}");
1559            }
1560            self.measure.number = last;
1561            self.measure.suffix = Some(suffix);
1562        }
1563    }
1564
1565    /// music21's `updateVoiceInformation`: a measure naming more than one
1566    /// voice holds a voice for each.
1567    fn voice_information(&mut self) {
1568        let mut ids: Vec<String> = Vec::new();
1569        for tag in ["note", "forward"] {
1570            for element in self.element.find_all(tag) {
1571                let voice = element.child_text("voice");
1572                if !voice.is_empty() && !ids.iter().any(|held| held == voice) {
1573                    ids.push(voice.to_string());
1574                }
1575            }
1576        }
1577        if ids.len() > 1 {
1578            ids.sort();
1579            self.measure.voices = ids
1580                .into_iter()
1581                .map(|id| VoiceIr {
1582                    id,
1583                    items: Vec::new(),
1584                })
1585                .collect();
1586            self.use_voices = true;
1587        }
1588    }
1589
1590    /// music21's `xmlToOffset`.
1591    fn offset_of(&self, element: &Xml) -> FloatType {
1592        parse_float(element.child_text("offset")).map_or(0.0, |ticks| ticks / self.divisions)
1593    }
1594
1595    /// music21's `xmlForward`.
1596    fn forward(&mut self, element: &'x Xml) -> Result<()> {
1597        let Some(ticks) = parse_float(element.child_text("duration")) else {
1598            return Ok(());
1599        };
1600        let change = op_frac(ticks / self.divisions);
1601        if self.part.importer.finale {
1602            // Finale writes a hidden rest as a `<forward>`. One of no
1603            // length is a quarter rest: music21 takes a length of nought
1604            // for no length given.
1605            let length = if change == 0.0 { 1.0 } else { change };
1606            let mut rest = Rest::new(Duration::new(length)?);
1607            rest.set_hidden(true);
1608            let seq = self.seq;
1609            self.insert_note(element, staff_number(element), rest.into());
1610            self.finale_forward = Some(Some(seq));
1611        }
1612        self.offset = op_frac(self.offset + change);
1613        Ok(())
1614    }
1615
1616    /// music21's `parseAttributesTag`.
1617    fn attributes(&mut self, attributes: &'x Xml) -> Result<()> {
1618        for child in &attributes.children {
1619            match child.tag.as_str() {
1620                "time" => {
1621                    if let Some(meter) = time_signature(child)? {
1622                        self.insert(self.offset, staff_number(child), meter);
1623                    }
1624                }
1625                "clef" => {
1626                    let clef = clef_of(child)?;
1627                    let staff = staff_number(child);
1628                    self.insert(self.offset, staff, clef.clone());
1629                    self.part.last_clefs.retain(|(held, _)| *held != staff);
1630                    self.part.last_clefs.push((staff, clef));
1631                }
1632                "key" => {
1633                    let key = key_signature(child)?;
1634                    self.insert(self.offset, staff_number(child), key);
1635                }
1636                "divisions" => {
1637                    if let Some(divisions) = parse_float(child.stripped()) {
1638                        self.divisions = op_frac(divisions);
1639                    }
1640                }
1641                "staves" => {
1642                    if let Some(staves) = parse_int(child.stripped()) {
1643                        self.staves = staves;
1644                    }
1645                }
1646                "transpose" => self.transposition = Some(transpose_interval(child)?),
1647                "staff-details" => self.staff_details(child),
1648                _ => {}
1649            }
1650        }
1651        Ok(())
1652    }
1653
1654    /// music21's `xmlPrint`: a page layout where the print starts a page or
1655    /// says how one is laid out, a system layout where it starts a system,
1656    /// says how one is laid out, or does not concern a page, and a staff
1657    /// layout for each numbered staff it lays out.
1658    fn print(&mut self, print: &'x Xml) {
1659        let says = |name: &str| print.get(name).is_some_and(|value| value != "no");
1660        let page = says("new-page")
1661            || print.get("page-number").is_some()
1662            || print.find("page-layout").is_some();
1663        let system = says("new-system") || print.find("system-layout").is_some();
1664        if page {
1665            let mut read = PageLayout {
1666                is_new: print.get("new-page").map(|value| value == "yes"),
1667                page_number: print
1668                    .get("page-number")
1669                    .and_then(|value| value.trim().parse().ok()),
1670                ..PageLayout::default()
1671            };
1672            if let Some(written) = print.find("page-layout") {
1673                read_page_layout(written, &mut read);
1674            }
1675            self.insert(0.0, NO_STAFF, StreamElement::Layout(Layout::Page(read)));
1676        }
1677        if system || !page {
1678            let mut read = SystemLayout {
1679                is_new: print.get("new-system").map(|value| value == "yes"),
1680                ..SystemLayout::default()
1681            };
1682            if let Some(written) = print.find("system-layout") {
1683                read_system_layout(written, &mut read);
1684            }
1685            self.insert(0.0, NO_STAFF, StreamElement::Layout(Layout::System(read)));
1686        }
1687        for written in print.find_all("staff-layout") {
1688            let read = staff_layout(written);
1689            let key = (read.staff_number.unwrap_or(1), self.offset);
1690            // music21 keeps it to be added to before knowing whether it is
1691            // put anywhere: one with no number is not, and details for its
1692            // staff added to it are lost with it.
1693            let Some(number) = read.staff_number else {
1694                self.staff_layouts.push((key, None));
1695                continue;
1696            };
1697            let uid = self.insert(
1698                0.0,
1699                i32::try_from(number).unwrap_or(NO_STAFF),
1700                StreamElement::Layout(Layout::Staff(read)),
1701            );
1702            self.staff_layouts.push((key, Some(uid)));
1703        }
1704        if let Some(numbering) = print.find("measure-numbering") {
1705            self.measure.measure_numbering = numbering.text().map(str::to_string);
1706        }
1707    }
1708
1709    /// music21's `handleStaffDetails`: the details of a staff read into the
1710    /// staff layout already standing for it here, or into a new one.
1711    fn staff_details(&mut self, details: &'x Xml) {
1712        let number = details
1713            .get("number")
1714            .and_then(|value| value.trim().parse::<i64>().ok());
1715        let key = (number.unwrap_or(1), self.offset);
1716        let existing = self
1717            .staff_layouts
1718            .iter()
1719            .rev()
1720            .find(|(known, _)| known.0 == key.0 && known.1 == key.1)
1721            .map(|(_, uid)| *uid);
1722        match existing {
1723            Some(Some(uid)) => {
1724                if let Some(item) = self
1725                    .measure
1726                    .items
1727                    .iter_mut()
1728                    .find(|item| item.uid == Some(uid))
1729                    && let StreamElement::Layout(Layout::Staff(layout)) = &mut item.element
1730                {
1731                    read_staff_details(details, layout);
1732                }
1733            }
1734            // Added to one never put anywhere.
1735            Some(None) => {}
1736            None => {
1737                let mut read = StaffLayout::default();
1738                read_staff_details(details, &mut read);
1739                let uid = self.insert(
1740                    self.offset,
1741                    staff_number(details),
1742                    StreamElement::Layout(Layout::Staff(read)),
1743                );
1744                self.staff_layouts.push((key, Some(uid)));
1745            }
1746        }
1747    }
1748
1749    // ----------------------------------------------------------- notes
1750
1751    /// music21's `xmlToNote`.
1752    fn note(&mut self, element: &'x Xml, next: Option<&'x Xml>) -> Result<()> {
1753        let next_is_chord =
1754            next.is_some_and(|next| next.tag == "note" && next.find("chord").is_some());
1755        let is_rest = element.find("rest").is_some();
1756        let mut is_chord = element.find("chord").is_some();
1757        if next_is_chord {
1758            is_chord = true;
1759            let voice = element.child_text("voice");
1760            if element.find("voice").is_some() && !voice.is_empty() {
1761                self.last_voice = Some(voice.to_string());
1762            }
1763        }
1764
1765        let mut increment = 0.0;
1766        if is_chord {
1767            self.chord_notes.push(element);
1768            self.chord_lyrics.extend(element.find_all("lyric"));
1769        } else {
1770            let staff = staff_number(element);
1771            let (mut read, uid) = if is_rest {
1772                self.rests += 1;
1773                let rest = self.rest(element)?;
1774                (ReadNote::Rest(rest), None)
1775            } else {
1776                self.notes += 1;
1777                let (read, uid) = self.simple_note(element, true)?;
1778                (read, Some(uid))
1779            };
1780            let lyrics = lyrics_from(element.find_all("lyric"))?;
1781            let element_value: StreamElement = match &mut read {
1782                ReadNote::Rest(rest) => {
1783                    *rest.lyrics_mut() = lyrics;
1784                    rest.clone().into()
1785                }
1786                ReadNote::Note(note) => {
1787                    *note.lyrics_mut() = lyrics;
1788                    note.clone().into()
1789                }
1790                ReadNote::Unpitched(stroke) => {
1791                    *stroke.written_mut().lyrics_mut() = lyrics;
1792                    stroke.clone().into()
1793                }
1794            };
1795            increment = element_value.quarter_length();
1796            let placed = self.insert_note(element, staff, element_value);
1797            match uid {
1798                // The note was known to its spanners by a number of its own
1799                // before it had a place.
1800                Some(early) => self.rename(early, placed),
1801                None => self.free_pending(placed),
1802            }
1803            if is_rest {
1804                self.rest_spanners(element, placed)?;
1805                if std::mem::take(&mut self.marking_full) {
1806                    self.marked_full.push(placed);
1807                }
1808            }
1809            self.last_note = Some(placed);
1810        }
1811
1812        if !self.chord_notes.is_empty() && !next_is_chord {
1813            let members = std::mem::take(&mut self.chord_notes);
1814            let lyrics = lyrics_from(std::mem::take(&mut self.chord_lyrics).into_iter())?;
1815            let (chord, member_uids) = self.chord(&members, lyrics)?;
1816            increment = chord.quarter_length();
1817            let source = members
1818                .iter()
1819                .find(|member| member.find("voice").is_some())
1820                .copied()
1821                .unwrap_or(element);
1822            let placed = self.insert_note(source, staff_number(members[0]), chord);
1823            for early in member_uids {
1824                self.rename(early, placed);
1825            }
1826            self.free_pending(placed);
1827            self.last_note = Some(placed);
1828        }
1829
1830        self.offset = op_frac(self.offset + increment);
1831        self.finale_forward = None;
1832        Ok(())
1833    }
1834
1835    /// Spanners that knew an element by one number know it by another.
1836    fn rename(&mut self, from: usize, to: usize) {
1837        for building in &mut self.part.importer.spanners {
1838            let mut renamed: Vec<usize> = Vec::new();
1839            for uid in &building.uids {
1840                let uid = if *uid == from { to } else { *uid };
1841                if !renamed.contains(&uid) {
1842                    renamed.push(uid);
1843                }
1844            }
1845            building.uids = renamed;
1846        }
1847    }
1848
1849    /// music21's `freePendingSpannedElementAssignment`.
1850    fn free_pending(&mut self, uid: usize) {
1851        let importer = &mut *self.part.importer;
1852        if let Some(index) = importer.pending.pop_front() {
1853            importer.spanners[index].add_first(uid);
1854        }
1855    }
1856
1857    /// music21's `xmlToSimpleNote`: a note or a stroke, known to the
1858    /// spanners by the number handed back.
1859    fn simple_note(&mut self, element: &'x Xml, free_spanners: bool) -> Result<(ReadNote, usize)> {
1860        let duration = self.duration(element)?;
1861        let mut note = match element.find("unpitched") {
1862            None => Note::from_pitch(pitch_of(element)?),
1863            Some(unpitched) => {
1864                let step = unpitched
1865                    .child_text("display-step")
1866                    .chars()
1867                    .next()
1868                    .unwrap_or('B');
1869                let octave = parse_int(unpitched.child_text("display-octave")).unwrap_or(4);
1870                Unpitched::displayed_at(step, octave)?.written().clone()
1871            }
1872        };
1873        note.set_duration(duration);
1874
1875        let beams: Vec<&Xml> = element.find_all("beam").collect();
1876        if !beams.is_empty() {
1877            note.set_beams(beams_of(&beams)?);
1878        }
1879        if let Some(stem) = element.find("stem") {
1880            note.set_stem_direction(StemDirection::from_name(stem.stripped())?);
1881        }
1882        if let Some(notehead) = element.find("notehead") {
1883            if let Some(text) = notehead.text().filter(|text| !text.is_empty()) {
1884                note.set_notehead(Notehead::from_name(text)?);
1885            }
1886            if let Some(filled) = notehead.get("filled") {
1887                note.set_notehead_fill(Some(filled == "yes"));
1888            }
1889            if let Some(color) = notehead.get("color") {
1890                note.set_color(Some(color.to_string()));
1891            }
1892            if let Some(parentheses) = notehead.get("parentheses") {
1893                note.set_notehead_parenthesis(parentheses == "yes");
1894            }
1895        }
1896
1897        let uid = self.part.importer.uid();
1898        if free_spanners {
1899            // music21 hands a waiting spanner the note as first read, and a
1900            // grace note is a copy made after that: the spanner is left
1901            // holding a note no stream holds.
1902            let joined = if element.find("grace").is_some() {
1903                self.part.importer.uid()
1904            } else {
1905                uid
1906            };
1907            self.free_pending(joined);
1908        }
1909        let mut general = General {
1910            color: note.color().map(str::to_string),
1911            ..General::default()
1912        };
1913        self.general_note(element, &mut general, note.duration().cloned(), uid, false)?;
1914        note.set_color(general.color);
1915        note.set_hidden(general.hidden);
1916        if let Some(volume) = general.volume {
1917            note.set_volume(Some(volume));
1918        }
1919        note.set_size(general.size);
1920        note.set_tie(general.tie);
1921        if let Some(duration) = general.duration {
1922            note.set_duration(duration);
1923        }
1924        *note.articulations_mut() = general.articulations;
1925        *note.expressions_mut() = general.expressions;
1926
1927        Ok(if element.find("unpitched").is_some() {
1928            let mut stroke = Unpitched::new();
1929            *stroke.written_mut() = note;
1930            (ReadNote::Unpitched(stroke), uid)
1931        } else {
1932            (ReadNote::Note(note), uid)
1933        })
1934    }
1935
1936    /// music21's `xmlToChord`.
1937    fn chord(
1938        &mut self,
1939        members: &[&'x Xml],
1940        lyrics: Vec<Lyric>,
1941    ) -> Result<(StreamElement, Vec<usize>)> {
1942        let mut notes: Vec<(ReadNote, usize)> = Vec::new();
1943        for member in members {
1944            notes.push(self.simple_note(member, false)?);
1945        }
1946        let percussion = members
1947            .iter()
1948            .any(|member| member.find("unpitched").is_some());
1949
1950        // The chord takes the first note's beams, and each kind of mark
1951        // once, read off the notes lowest first.
1952        let mut order: Vec<usize> = (0..notes.len()).collect();
1953        order.sort_by(|left, right| {
1954            let ps = |index: &usize| notes[*index].0.written().pitch().ps();
1955            ps(left).total_cmp(&ps(right))
1956        });
1957        let mut articulations: Vec<Articulation> = Vec::new();
1958        let mut expressions: Vec<Expression> = Vec::new();
1959        let mut uids = Vec::new();
1960        for index in order {
1961            let (read, uid) = &notes[index];
1962            uids.push(*uid);
1963            for articulation in read.written().articulations() {
1964                let repeats = ["Fingering", "StringIndication", "FretIndication"]
1965                    .iter()
1966                    .any(|class| articulation.is_a(class));
1967                if !repeats
1968                    && articulations
1969                        .iter()
1970                        .any(|held| held.kind() == articulation.kind())
1971                {
1972                    continue;
1973                }
1974                articulations.push(articulation.clone());
1975            }
1976            for expression in read.written().expressions() {
1977                let same = |held: &Expression| match (held, expression) {
1978                    (Expression::Ornament(left), Expression::Ornament(right)) => {
1979                        left.kind() == right.kind()
1980                    }
1981                    (Expression::Fermata(_), Expression::Fermata(_)) => true,
1982                    (Expression::Arpeggio(_), Expression::Arpeggio(_)) => true,
1983                    _ => false,
1984                };
1985                if !expressions.iter().any(same) {
1986                    expressions.push(expression.clone());
1987                }
1988            }
1989        }
1990        let beams = notes
1991            .first()
1992            .map(|(read, _)| read.written().beams().clone())
1993            .unwrap_or_default();
1994        let duration = notes
1995            .first()
1996            .and_then(|(read, _)| read.written().duration().cloned());
1997        let stem = notes
1998            .first()
1999            .map_or(StemDirection::Unspecified, |(read, _)| {
2000                read.written().stem_direction()
2001            });
2002        for (read, _) in &mut notes {
2003            let written = read.written_mut();
2004            written.articulations_mut().clear();
2005            written.expressions_mut().clear();
2006        }
2007        if let Some((first, _)) = notes.first_mut() {
2008            first.written_mut().set_beams(Beams::new());
2009            // A chord's lyrics are kept on its first note.
2010            *first.written_mut().lyrics_mut() = lyrics;
2011        }
2012
2013        let element: StreamElement = if percussion {
2014            let members: Vec<PercussionNote> = notes
2015                .into_iter()
2016                .map(|(read, _)| match read {
2017                    ReadNote::Unpitched(stroke) => PercussionNote::Unpitched(stroke),
2018                    ReadNote::Note(note) => PercussionNote::Note(note),
2019                    ReadNote::Rest(_) => unreachable!("a chord holds no rests"),
2020                })
2021                .collect();
2022            let mut chord = PercussionChord::new(members)?;
2023            let written = chord.written_mut();
2024            if let Some(duration) = duration {
2025                written.set_duration(duration);
2026            }
2027            written.set_beams(beams);
2028            *written.articulations_mut() = articulations;
2029            *written.expressions_mut() = expressions;
2030            chord.into()
2031        } else {
2032            let members: Vec<Note> = notes
2033                .into_iter()
2034                .map(|(read, _)| match read {
2035                    ReadNote::Note(note) => note,
2036                    _ => unreachable!("a chord of pitches holds notes"),
2037                })
2038                .collect();
2039            let mut chord = Chord::new(members)?;
2040            if let Some(duration) = duration {
2041                chord.set_duration(duration);
2042            }
2043            chord.set_beams(beams);
2044            // A chord reads its stem off its first note where it has none.
2045            let _ = stem;
2046            *chord.articulations_mut() = articulations;
2047            *chord.expressions_mut() = expressions;
2048            chord.into()
2049        };
2050        Ok((element, uids))
2051    }
2052
2053    /// music21's `xmlToRest`.
2054    fn rest(&mut self, element: &'x Xml) -> Result<Rest> {
2055        let duration = self.duration(element)?;
2056        let mut rest = Rest::new(duration);
2057        let tag = element
2058            .find("rest")
2059            .ok_or_else(|| import_error("a rest without a <rest> tag"))?;
2060        if tag.get("measure") == Some("yes") {
2061            let written = element.child_text("type");
2062            if written.is_empty() || matches!(written, "whole" | "breve") {
2063                self.full_measure_rest = true;
2064                self.marking_full = true;
2065                rest.set_full_measure(Some(true));
2066            }
2067        }
2068        let step = tag.child_text("display-step");
2069        if !step.is_empty() {
2070            let name = format!("{step}{}", tag.child_text("display-octave"));
2071            let displayed = Pitch::from_name(&name)?;
2072            let staff = match staff_number(element) {
2073                NO_STAFF => 1,
2074                staff => staff,
2075            };
2076            let middle = self
2077                .part
2078                .last_clefs
2079                .iter()
2080                .find(|(held, _)| *held == staff)
2081                .and_then(|(_, clef)| clef.lowest_line().filter(|_| clef.places_pitches()))
2082                .map_or(35, |lowest| lowest + 4);
2083            rest.set_step_shift(displayed.diatonic_note_number() - middle);
2084        }
2085        let mut general = General::default();
2086        let uid = 0;
2087        self.general_note(
2088            element,
2089            &mut general,
2090            Some(rest.duration().clone()),
2091            uid,
2092            true,
2093        )?;
2094        rest.set_hidden(general.hidden);
2095        rest.set_size(general.size);
2096        if let Some(duration) = general.duration {
2097            rest.set_duration(duration);
2098        }
2099        *rest.articulations_mut() = general.articulations;
2100        *rest.expressions_mut() = general.expressions;
2101        Ok(rest)
2102    }
2103
2104    /// The spanners a rest's notations start or stop, which can only be
2105    /// joined once the rest has a number.
2106    fn rest_spanners(&mut self, element: &'x Xml, uid: usize) -> Result<()> {
2107        for notations in element.find_all("notations") {
2108            self.notation_spanners(notations, uid)?;
2109        }
2110        Ok(())
2111    }
2112
2113    /// music21's `xmlNoteToGeneralNoteHelper`.
2114    fn general_note(
2115        &mut self,
2116        element: &'x Xml,
2117        general: &mut General,
2118        duration: Option<Duration>,
2119        uid: usize,
2120        is_rest: bool,
2121    ) -> Result<()> {
2122        if let Some(color) = element.get("color") {
2123            general.color = Some(color.to_string());
2124        }
2125        general.hidden = element.get("print-object") == Some("no");
2126        if !is_rest && let Some(dynamics) = element.get("dynamics").and_then(parse_float) {
2127            general.volume = Some(Volume::from_velocity_scalar(dynamics * (90.0 / 12700.0))?);
2128        }
2129        if element.get("pizzicato") == Some("yes")
2130            && let Some(kind) = ArticulationKind::from_class_name("Pizzicato")
2131        {
2132            general.articulations.push(Articulation::of_kind(kind));
2133        }
2134        if let Some(written) = element.find("type")
2135            && let Some(size) = written.get("size")
2136        {
2137            general.size = NoteSize::from_name(size);
2138        }
2139        if element.find("tie").is_some() {
2140            general.tie = tie_of(element);
2141        }
2142        if let Some(grace) = element.find("grace") {
2143            // A grace note is written as an eighth where nothing says.
2144            let base = duration.unwrap_or_default();
2145            let mut written = Duration::default();
2146            written.clear();
2147            written.set_tuplets(Vec::new());
2148            for (kind, dots) in base.components() {
2149                written.add_duration_tuple(kind, dots);
2150            }
2151            let unsaid = base.components().is_empty();
2152            if unsaid {
2153                written.add_duration_tuple(DurationType::Eighth, 0);
2154            }
2155            let mut value = written.grace_duration();
2156            let mut marks = Grace::new();
2157            marks.set_value_unsaid(unsaid);
2158            marks.set_slash(matches!(grace.get("slash"), Some("yes") | None));
2159            let share = |name: &str| {
2160                grace
2161                    .get(name)
2162                    .and_then(parse_int)
2163                    .map(|percent| FloatType::from(percent) / 100.0)
2164            };
2165            marks.set_steal_time_previous(share("steal-time-previous"));
2166            marks.set_steal_time_following(share("steal-time-following"));
2167            value.set_grace(Some(marks));
2168            general.duration = Some(value);
2169        }
2170        for notations in element.find_all("notations") {
2171            self.notations(notations, general)?;
2172            if !is_rest {
2173                self.notation_spanners(notations, uid)?;
2174            }
2175        }
2176        Ok(())
2177    }
2178
2179    /// music21's `xmlToDuration`.
2180    fn duration(&mut self, element: &'x Xml) -> Result<Duration> {
2181        let quarter_length = match element.find("duration") {
2182            Some(duration) => {
2183                let ticks = parse_float(duration.stripped())
2184                    .ok_or_else(|| import_error("a <duration> that is not a number"))?;
2185                op_frac(ticks / self.divisions)
2186            }
2187            None => 0.0,
2188        };
2189        let written = element.child_text("type");
2190        if written.is_empty() {
2191            // Some rests say how long they last and nothing else.
2192            return Duration::new(quarter_length);
2193        }
2194        let kind = duration_type(written)?;
2195        let dots = element.find_all("dot").count() as u32;
2196        let tuplets = if element.find("time-modification").is_some() {
2197            self.tuplets(element)?
2198        } else {
2199            Vec::new()
2200        };
2201        if kind.quarter_length_with_dots(dots) == quarter_length && tuplets.is_empty() {
2202            return Duration::new(quarter_length);
2203        }
2204        // Made from the length first, as music21 makes it, so its writing
2205        // counts as worked out even with the written value put in its place.
2206        let mut duration = Duration::new(quarter_length)?;
2207        duration.clear();
2208        duration.set_tuplets(Vec::new());
2209        duration.add_duration_tuple(kind, dots);
2210        duration.set_tuplets(tuplets);
2211        if (duration.quarter_length() - quarter_length).abs() > 1e-7 {
2212            duration.set_linked(false);
2213            duration.set_quarter_length(quarter_length)?;
2214        }
2215        Ok(duration)
2216    }
2217
2218    /// music21's `xmlToTuplets`.
2219    fn tuplets(&mut self, element: &'x Xml) -> Result<Vec<Tuplet>> {
2220        let modification = element
2221            .find("time-modification")
2222            .ok_or_else(|| import_error("Note without time-modification in xmlToTuplets"))?;
2223        let count = |tag: &str, fallback: u32| {
2224            parse_int(modification.child_text(tag)).map_or(fallback, |value| value.max(1) as u32)
2225        };
2226        let normal_type = match modification.child_text("normal-type") {
2227            "" => element.child_text("type"),
2228            written => written,
2229        };
2230        let kind = duration_type(normal_type)?;
2231        let dots = modification.find_all("normal-dot").count() as u32;
2232        let time_mod = Tuplet::new(
2233            count("actual-notes", 3),
2234            count("normal-notes", 2),
2235            kind,
2236            dots,
2237        );
2238
2239        let notations = element.find("notations");
2240        if notations.is_none() {
2241            self.part.active_tuplets[0] = Some(time_mod);
2242        }
2243        let multiplier = |tuplet: &Tuplet| -> (i64, i64) {
2244            // actual notes in the time of normal: each lasts normal/actual,
2245            // scaled by the written values of the two sides.
2246            let ratio = tuplet.multiplier();
2247            (
2248                i64::from(*ratio.numer().unwrap_or(&1)),
2249                i64::from(*ratio.denom().unwrap_or(&1)),
2250            )
2251        };
2252        let divide = |remaining: (i64, i64), by: (i64, i64)| -> (i64, i64) {
2253            reduce(remaining.0 * by.1, remaining.1 * by.0)
2254        };
2255        let mut remaining = multiplier(&time_mod);
2256        let mut returned: [Option<Tuplet>; 8] = Default::default();
2257        // Which of the returned tuplets are the active ones themselves.
2258        let mut from_active = [false; 8];
2259        let mut to_remove: Vec<usize> = Vec::new();
2260        let mut to_stop: Vec<usize> = Vec::new();
2261
2262        if let Some(notations) = notations {
2263            for written in notations.find_all("tuplet") {
2264                let index = written
2265                    .get("number")
2266                    .and_then(parse_int)
2267                    .map_or(1, |number| number.clamp(0, 6) as usize);
2268                let tuplet_type = written.get("type");
2269                if tuplet_type == Some("stop") {
2270                    if let Some(active) = self.part.active_tuplets[index] {
2271                        // One that started on this very note starts and
2272                        // stops on it. music21 asks by value, and so finds
2273                        // an equal tuplet of another number too.
2274                        if returned.iter().flatten().any(|held| *held == active)
2275                            && from_active[index]
2276                            && let Some(held) = &mut returned[index]
2277                        {
2278                            held.set_tuplet_type(Some(TupletType::StartStop));
2279                        }
2280                        to_remove.push(index);
2281                        to_stop.push(index);
2282                    }
2283                    continue;
2284                }
2285                let actual = written.find("tuplet-actual");
2286                let normal = written.find("tuplet-normal");
2287                let mut tuplet = match (actual, normal) {
2288                    (Some(actual), Some(normal)) => {
2289                        let number = |side: &Xml, fallback: u32| {
2290                            parse_int(side.child_text("tuplet-number"))
2291                                .map_or(fallback, |value| value.max(1) as u32)
2292                        };
2293                        let mut tuplet = Tuplet::ratio(number(actual, 3), number(normal, 2));
2294                        let side = |side: &Xml| -> Result<Option<(DurationType, u32)>> {
2295                            match side.find("tuplet-type") {
2296                                Some(written) if written.text().is_some() => Ok(Some((
2297                                    duration_type(written.stripped())?,
2298                                    written.find_all("tuplet-dot").count() as u32,
2299                                ))),
2300                                _ => Ok(None),
2301                            }
2302                        };
2303                        if let Some(value) = side(actual)? {
2304                            tuplet.set_duration_actual(Some(value));
2305                        }
2306                        if let Some(value) = side(normal)? {
2307                            tuplet.set_duration_normal(Some(value));
2308                        }
2309                        tuplet
2310                    }
2311                    _ => time_mod,
2312                };
2313                tuplet
2314                    .set_tuplet_type(tuplet_type.and_then(|name| TupletType::from_name(name).ok()));
2315                let bracket = written.get("bracket");
2316                if let Some(bracket) = bracket {
2317                    tuplet.set_bracket(if bracket == "yes" {
2318                        TupletBracket::Bracket
2319                    } else {
2320                        TupletBracket::None
2321                    });
2322                }
2323                let show_number = written.get("show-number");
2324                if show_number == Some("none") {
2325                    tuplet.set_actual_show(None);
2326                    if bracket.is_none() {
2327                        tuplet.set_bracket(TupletBracket::None);
2328                    }
2329                } else if show_number == Some("both") {
2330                    tuplet.set_normal_show(Some(TupletShow::Number));
2331                }
2332                let both_or_type = |shown: Option<TupletShow>| {
2333                    Some(if shown.is_some() {
2334                        TupletShow::Both
2335                    } else {
2336                        TupletShow::Type
2337                    })
2338                };
2339                if written.get("show-type") == Some("actual") {
2340                    tuplet.set_actual_show(both_or_type(tuplet.actual_show()));
2341                } else if show_number == Some("both") {
2342                    tuplet.set_actual_show(both_or_type(tuplet.actual_show()));
2343                    tuplet.set_normal_show(both_or_type(tuplet.normal_show()));
2344                }
2345                if written.get("line-shape") == Some("curved") {
2346                    tuplet.set_bracket(TupletBracket::Slur);
2347                }
2348                tuplet.set_placement(placement_of(written));
2349                remaining = divide(remaining, multiplier(&tuplet));
2350                returned[index] = Some(tuplet);
2351                from_active[index] = true;
2352                self.part.active_tuplets[index] = Some(tuplet);
2353            }
2354        }
2355
2356        for index in 1..self.part.active_tuplets.len() {
2357            let Some(active) = &self.part.active_tuplets[index] else {
2358                continue;
2359            };
2360            if returned.iter().flatten().any(|held| held == active) {
2361                continue;
2362            }
2363            let mut copy = *active;
2364            copy.set_tuplet_type(if to_stop.contains(&index) {
2365                Some(TupletType::Stop)
2366            } else {
2367                None
2368            });
2369            remaining = divide(remaining, multiplier(&copy));
2370            returned[index] = Some(copy);
2371        }
2372        if remaining != (1, 1) {
2373            let mut remainder = Tuplet::ratio(remaining.1.max(1) as u32, remaining.0.max(1) as u32);
2374            remainder.set_duration_normal(time_mod.duration_normal());
2375            remainder.set_duration_actual(time_mod.duration_actual());
2376            returned[7] = Some(remainder);
2377        }
2378        for index in to_remove {
2379            self.part.active_tuplets[index] = None;
2380        }
2381        Ok(returned.into_iter().flatten().collect())
2382    }
2383
2384    // ------------------------------------------------------- notations
2385
2386    /// music21's `xmlNotations`, apart from the spanners.
2387    fn notations(&mut self, notations: &'x Xml, general: &mut General) -> Result<()> {
2388        for technical in notations.find_all("technical") {
2389            for mark in &technical.children {
2390                if let Some(read) = technical_of(mark) {
2391                    general.articulations.push(read);
2392                }
2393            }
2394        }
2395        for articulations in notations.find_all("articulations") {
2396            for mark in &articulations.children {
2397                if let Some(read) = articulation_of(mark) {
2398                    general.articulations.push(read);
2399                }
2400            }
2401        }
2402        for written in notations.find_all("fermata") {
2403            let mut fermata = Fermata::new();
2404            match written.get("type") {
2405                Some("upright") => fermata.set_fermata_type(FermataType::Upright),
2406                Some("inverted") => fermata.set_fermata_type(FermataType::Inverted),
2407                _ => {}
2408            }
2409            let shape = written.stripped();
2410            if !shape.is_empty() {
2411                fermata.set_shape(Some(shape.to_string()));
2412            }
2413            general.expressions.push(Expression::Fermata(fermata));
2414        }
2415        for tag in ["arpeggiate", "non-arpeggiate"] {
2416            for written in notations.find_all(tag) {
2417                let kind = if tag == "non-arpeggiate" {
2418                    ArpeggioType::NonArpeggio
2419                } else {
2420                    ArpeggioType::from_name(written.get("direction").unwrap_or("normal"))?
2421                };
2422                match written.get("number") {
2423                    None => general.expressions.push(Expression::Arpeggio(kind)),
2424                    Some(number) => general.arpeggio_spanners.push((number.to_string(), kind)),
2425                }
2426            }
2427        }
2428        let mut latest: Option<usize> = None;
2429        for ornaments in notations.find_all("ornaments") {
2430            for mark in &ornaments.children {
2431                if mark.tag == "accidental-mark" {
2432                    let Some(index) = latest else {
2433                        continue;
2434                    };
2435                    let mut accidental = accidental_of(mark)?;
2436                    accidental.set_display_status(Some(true));
2437                    if let Expression::Ornament(ornament) = &mut general.expressions[index] {
2438                        if ornament.is_a("Turn") {
2439                            if mark.get("placement") == Some("below") {
2440                                ornament.set_lower_accidental(Some(accidental));
2441                            } else {
2442                                ornament.set_upper_accidental(Some(accidental));
2443                            }
2444                        } else if ornament.is_a("GeneralMordent") || ornament.is_a("Trill") {
2445                            ornament.set_accidental(Some(accidental))?;
2446                        }
2447                    }
2448                    continue;
2449                }
2450                let class = match mark.tag.as_str() {
2451                    "trill-mark" => "Trill",
2452                    "turn" | "delayed-turn" => "Turn",
2453                    "inverted-turn" | "delayed-inverted-turn" => "InvertedTurn",
2454                    "shake" => "Shake",
2455                    "mordent" => "Mordent",
2456                    "inverted-mordent" => "InvertedMordent",
2457                    "schleifer" => "Schleifer",
2458                    "other-ornament" => "Ornament",
2459                    "tremolo" => {
2460                        if matches!(mark.get("type"), Some("start" | "stop")) {
2461                            continue;
2462                        }
2463                        "Tremolo"
2464                    }
2465                    _ => continue,
2466                };
2467                let Some(kind) = OrnamentKind::from_class_name(class) else {
2468                    continue;
2469                };
2470                let mut ornament = Ornament::of_kind(kind);
2471                match mark.tag.as_str() {
2472                    "delayed-turn" | "delayed-inverted-turn" => {
2473                        ornament.set_delay(OrnamentDelay::Default);
2474                    }
2475                    "turn" | "inverted-turn" => ornament.set_delay(OrnamentDelay::NoDelay),
2476                    "tremolo" => {
2477                        ornament.set_number_of_marks(tremolo_marks(mark)?)?;
2478                    }
2479                    _ => {}
2480                }
2481                if mark.tag != "tremolo" {
2482                    if let Some(placement) = mark.get("placement") {
2483                        ornament.set_placement(Some(placement.to_string()));
2484                    }
2485                    latest = Some(general.expressions.len());
2486                }
2487                general
2488                    .expressions
2489                    .push(Expression::Ornament(Box::new(ornament)));
2490            }
2491        }
2492        Ok(())
2493    }
2494
2495    /// music21's `xmlNotationsToSpanners` and the arpeggios across chords.
2496    fn notation_spanners(&mut self, notations: &'x Xml, uid: usize) -> Result<()> {
2497        for tag in ["arpeggiate", "non-arpeggiate"] {
2498            for written in notations.find_all(tag) {
2499                let Some(number) = written.get("number") else {
2500                    continue;
2501                };
2502                let kind = if tag == "non-arpeggiate" {
2503                    ArpeggioType::NonArpeggio
2504                } else {
2505                    ArpeggioType::from_name(written.get("direction").unwrap_or("normal"))
2506                        .unwrap_or(ArpeggioType::Normal)
2507                };
2508                // A number tells apart arpeggios struck together, which may
2509                // cross staves but not parts.
2510                let key = (
2511                    number.to_string(),
2512                    op_frac(self.part.last_measure_offset + self.offset),
2513                );
2514                let found = self
2515                    .part
2516                    .arpeggios
2517                    .iter()
2518                    .find(|(held, _)| *held == key)
2519                    .map(|(_, index)| *index);
2520                let spanners = &mut self.part.importer.spanners;
2521                let index = found.unwrap_or_else(|| {
2522                    let mut building = Building::new(SpannerKind::ArpeggioMark, Some(number));
2523                    building.arpeggio = kind;
2524                    spanners.push(building);
2525                    spanners.len() - 1
2526                });
2527                if found.is_none() {
2528                    self.part.arpeggios.push((key, index));
2529                }
2530                self.part.importer.spanners[index].add(uid);
2531            }
2532        }
2533        for ornaments in notations.find_all("ornaments") {
2534            for mark in &ornaments.children {
2535                match mark.tag.as_str() {
2536                    "wavy-line" => {
2537                        self.one_spanner(mark, Some(uid), SpannerKind::TrillExtension, false);
2538                    }
2539                    // music21's `xmlToTremolo`: a tremolo that starts or
2540                    // stops is one between notes.
2541                    "tremolo" if matches!(mark.get("type"), Some("start" | "stop")) => {
2542                        let index =
2543                            self.one_spanner(mark, Some(uid), SpannerKind::TremoloSpanner, false);
2544                        self.part.importer.spanners[index].marks = tremolo_marks(mark)?;
2545                    }
2546                    _ => {}
2547                }
2548            }
2549        }
2550        for slur in notations.find_all("slur") {
2551            let index = self.one_spanner(slur, Some(uid), SpannerKind::Slur, false);
2552            let building = &mut self.part.importer.spanners[index];
2553            if let Some(line_type) = slur.get("line-type") {
2554                building.line_type = Some(line_type.to_string());
2555            }
2556            if let Some(placement) = placement_of(slur) {
2557                building.placement = Some(placement);
2558            }
2559        }
2560        for tag in ["glissando", "slide"] {
2561            for written in notations.find_all(tag) {
2562                let index = self.one_spanner(written, Some(uid), SpannerKind::Glissando, false);
2563                let building = &mut self.part.importer.spanners[index];
2564                if tag == "slide" {
2565                    building.slide = SlideType::Continuous;
2566                }
2567                match written.get("line-type") {
2568                    Some(line_type) => {
2569                        let lower = line_type.to_lowercase();
2570                        if !["solid", "dashed", "dotted", "wavy"].contains(&lower.as_str()) {
2571                            return Err(import_error(format!("not a valid value: {line_type}")));
2572                        }
2573                        building.line_type = Some(lower);
2574                    }
2575                    None if tag == "slide" => building.line_type = Some("solid".to_string()),
2576                    None => {}
2577                }
2578            }
2579        }
2580        Ok(())
2581    }
2582
2583    /// music21's `xmlOneSpanner`: the open spanner of a kind and number, or
2584    /// a new one.
2585    fn one_spanner(
2586        &mut self,
2587        element: &Xml,
2588        target: Option<usize>,
2589        kind: SpannerKind,
2590        allow_duplicates: bool,
2591    ) -> usize {
2592        let id = element.get("number");
2593        let spanners = &mut self.part.importer.spanners;
2594        let found = spanners
2595            .iter()
2596            .position(|held| held.kind == kind && !held.complete && held.id_local.as_deref() == id);
2597        let index = match found {
2598            Some(index) if !allow_duplicates => index,
2599            _ => {
2600                let mut building = Building::new(kind, id);
2601                if let Some(placement) = placement_of(element) {
2602                    building.placement = Some(placement);
2603                }
2604                spanners.push(building);
2605                spanners.len() - 1
2606            }
2607        };
2608        if let Some(target) = target {
2609            spanners[index].add(target);
2610        }
2611        if element.get("type") == Some("stop") {
2612            spanners[index].complete = true;
2613        }
2614        index
2615    }
2616
2617    // ------------------------------------------------------ directions
2618
2619    /// music21's `xmlDirection`.
2620    fn direction(&mut self, direction: &'x Xml) -> Result<()> {
2621        let total = self.offset_of(direction) + self.offset;
2622        let staff = staff_number(direction);
2623        let mut metronome_added = false;
2624        for direction_type in direction.find_all("direction-type") {
2625            for specific in &direction_type.children {
2626                self.direction_type(specific, direction, staff, total)?;
2627                if specific.tag == "metronome" {
2628                    metronome_added = true;
2629                }
2630            }
2631        }
2632        if !metronome_added
2633            && let Some(sound) = direction
2634                .find_all("sound")
2635                .find(|sound| sound.get("tempo").is_some())
2636        {
2637            self.sound_tempo(sound, Some(direction), staff, total);
2638        }
2639        Ok(())
2640    }
2641
2642    /// music21's `setSoundTempo`: how fast the music is played, where no
2643    /// mark says.
2644    fn sound_tempo(&mut self, sound: &Xml, direction: Option<&Xml>, staff: i32, total: FloatType) {
2645        let Some(tempo) = sound
2646            .get("tempo")
2647            .and_then(parse_float)
2648            .filter(|qpm| *qpm != 0.0)
2649        else {
2650            return;
2651        };
2652        let mut mark = MetronomeMark::default().with_number_sounding(tempo);
2653        if let Some(direction) = direction {
2654            mark.set_placement(placement_of(direction));
2655        }
2656        self.insert(total, staff, mark);
2657    }
2658
2659    /// music21's `setDirectionInDirectionType`.
2660    fn direction_type(
2661        &mut self,
2662        specific: &'x Xml,
2663        direction: &'x Xml,
2664        staff: i32,
2665        total: FloatType,
2666    ) -> Result<()> {
2667        match specific.tag.as_str() {
2668            "dynamics" => {
2669                for written in &specific.children {
2670                    let text = match (written.tag.as_str(), written.stripped()) {
2671                        ("other-dynamic", text) if !text.is_empty() => text,
2672                        (tag, _) => tag,
2673                    };
2674                    let mut dynamic = Dynamic::new(text);
2675                    dynamic.set_placement(placement_of(direction));
2676                    self.insert(total, staff, dynamic);
2677                }
2678            }
2679            "wedge" | "bracket" | "dashes" | "octave-shift" | "pedal" => {
2680                // music21 warns and carries on when one cannot be read.
2681                if let Ok(made) = self.direction_spanners(specific, staff, total) {
2682                    for index in made {
2683                        let building = &mut self.part.importer.spanners[index];
2684                        if let Some(placement) = placement_of(specific) {
2685                            building.placement = Some(placement);
2686                        }
2687                        if building.kind != SpannerKind::Crescendo
2688                            && building.kind != SpannerKind::Diminuendo
2689                            && let Some(line_type) = specific.get("line-type")
2690                        {
2691                            building.line_type = Some(line_type.to_string());
2692                        }
2693                    }
2694                }
2695            }
2696            "coda" | "segno" => {
2697                let kind = if specific.tag == "segno" {
2698                    RepeatExpressionKind::Segno
2699                } else {
2700                    RepeatExpressionKind::Coda
2701                };
2702                self.insert(total, staff, RepeatExpression::new(kind));
2703            }
2704            "rehearsal" => {
2705                // music21's `xmlToRehearsalMark`.
2706                let mut mark = RehearsalMark::new(specific.stripped());
2707                if let Some(enclosure) = specific.get("enclosure") {
2708                    let lower = enclosure.to_lowercase();
2709                    if !ENCLOSURES.contains(&lower.as_str()) {
2710                        return Err(import_error(format!(
2711                            "Not a supported enclosure: '{enclosure}'"
2712                        )));
2713                    }
2714                    mark.set_enclosure(Some(lower));
2715                }
2716                mark.set_placement(placement_of(direction));
2717                self.insert(total, staff, mark);
2718            }
2719            "metronome" => {
2720                let tempo = metronome(specific, placement_of(direction))?;
2721                self.insert(total, staff, tempo);
2722            }
2723            "words" => {
2724                let text = specific.stripped();
2725                let placement = placement_of(direction);
2726                // Words that say where to go next are a repeat mark.
2727                let repeat = RepeatExpressionKind::ALL
2728                    .into_iter()
2729                    .find(|kind| RepeatExpression::new(*kind).is_valid_text(text));
2730                match repeat {
2731                    Some(kind) => {
2732                        let mut mark = RepeatExpression::new(kind);
2733                        mark.set_text(text);
2734                        // The words are placed, not the mark: one drawn as
2735                        // its sign, a coda or a segno, stands where music21
2736                        // puts a sign whatever side its words were on.
2737                        if !mark.use_symbol() {
2738                            mark.set_placement(placement);
2739                        }
2740                        self.insert(total, staff, mark);
2741                    }
2742                    None => {
2743                        let mut words = TextExpression::new(text);
2744                        words.set_placement(placement);
2745                        self.insert(total, staff, words);
2746                    }
2747                }
2748            }
2749            _ => {}
2750        }
2751        Ok(())
2752    }
2753
2754    /// music21's `xmlDirectionTypeToSpanners`. Hands back the spanners it
2755    /// started.
2756    fn direction_spanners(
2757        &mut self,
2758        element: &'x Xml,
2759        staff: i32,
2760        total: FloatType,
2761    ) -> Result<Vec<usize>> {
2762        let last = self.last_note;
2763        let id = element.get("number");
2764        let mut made = Vec::new();
2765        let open = |spanners: &[Building], wanted: &dyn Fn(SpannerKind) -> bool| {
2766            spanners.iter().position(|held| {
2767                wanted(held.kind) && !held.complete && held.id_local.as_deref() == id
2768            })
2769        };
2770        match element.tag.as_str() {
2771            "wedge" => {
2772                let kind = match element.get("type") {
2773                    Some("crescendo") => Some(SpannerKind::Crescendo),
2774                    Some("diminuendo") => Some(SpannerKind::Diminuendo),
2775                    Some("stop") => None,
2776                    other => {
2777                        return Err(import_error(format!("Unknown type, {other:?}.")));
2778                    }
2779                };
2780                match kind {
2781                    Some(kind) => {
2782                        let index = self.one_spanner(element, None, kind, true);
2783                        self.part.importer.pending.push_back(index);
2784                        made.push(index);
2785                    }
2786                    None => {
2787                        let spanners = &mut self.part.importer.spanners;
2788                        let index = open(spanners, &SpannerKind::is_wedge)
2789                            .ok_or_else(|| import_error("Error in getting DynamicWedges"))?;
2790                        spanners[index].complete = true;
2791                        if let Some(last) = last {
2792                            spanners[index].add(last);
2793                        }
2794                    }
2795                }
2796            }
2797            "bracket" | "dashes" => {
2798                let dashes = element.tag == "dashes";
2799                let tick = || -> Result<LineEnd> {
2800                    match element.get("line-end") {
2801                        Some(name) => LineEnd::from_name(name),
2802                        None => Ok(LineEnd::Down),
2803                    }
2804                };
2805                let height = element.get("end-length").and_then(parse_float);
2806                let Importer {
2807                    spanners, pending, ..
2808                } = &mut *self.part.importer;
2809                match element.get("type") {
2810                    Some("start") => {
2811                        let mut building = Building::new(SpannerKind::Line, id);
2812                        if dashes {
2813                            building.ends.start = LineEnd::None;
2814                            building.line_type = Some("dashed".to_string());
2815                        } else {
2816                            building.ends.start_height = height;
2817                            building.ends.start = tick()?;
2818                            if let Some(line_type) = element.get("line-type") {
2819                                building.line_type = Some(line_type.to_string());
2820                            }
2821                        }
2822                        spanners.push(building);
2823                        pending.push_back(spanners.len() - 1);
2824                        made.push(spanners.len() - 1);
2825                    }
2826                    Some("stop") => {
2827                        let Some(index) = open(spanners, &|kind| kind == SpannerKind::Line) else {
2828                            return Ok(made);
2829                        };
2830                        let building = &mut spanners[index];
2831                        building.complete = true;
2832                        if dashes {
2833                            building.ends.end = LineEnd::None;
2834                            building.line_type = Some("dashed".to_string());
2835                        } else {
2836                            building.ends.end = tick()?;
2837                            if height.is_some() {
2838                                building.ends.end_height = height;
2839                            }
2840                            if let Some(line_type) = element.get("line-type") {
2841                                building.line_type = Some(line_type.to_string());
2842                            }
2843                        }
2844                        if let Some(last) = last {
2845                            building.add(last);
2846                        }
2847                    }
2848                    other => {
2849                        return Err(import_error(format!(
2850                            "unidentified mxType of mxBracket: {other:?}"
2851                        )));
2852                    }
2853                }
2854            }
2855            "octave-shift" => {
2856                let Importer {
2857                    spanners, pending, ..
2858                } = &mut *self.part.importer;
2859                match element.get("type") {
2860                    Some(direction @ ("up" | "down")) => {
2861                        let size = element.get("size").and_then(parse_int).unwrap_or(8);
2862                        // An `up` shift is a line under the notes.
2863                        let up = direction == "down";
2864                        let name = match (size, up) {
2865                            (15, true) => "15ma",
2866                            (15, false) => "15mb",
2867                            (22, true) => "22da",
2868                            (22, false) => "22db",
2869                            (_, true) => "8va",
2870                            (_, false) => "8vb",
2871                        };
2872                        let mut building = Building::new(SpannerKind::Ottava, id);
2873                        building.shift = Some(OctaveShift::from_name(name, false)?);
2874                        building.placement = Some(if up {
2875                            Placement::Above
2876                        } else {
2877                            Placement::Below
2878                        });
2879                        spanners.push(building);
2880                        pending.push_back(spanners.len() - 1);
2881                        made.push(spanners.len() - 1);
2882                    }
2883                    Some(kind @ ("continue" | "stop")) => {
2884                        let index = open(spanners, &|kind| kind == SpannerKind::Ottava)
2885                            .ok_or_else(|| import_error("Error in getting Ottava"))?;
2886                        if kind == "continue" {
2887                            pending.push_back(index);
2888                        } else {
2889                            spanners[index].complete = true;
2890                            if let Some(last) = last {
2891                                spanners[index].add(last);
2892                            }
2893                        }
2894                    }
2895                    other => {
2896                        return Err(import_error(format!(
2897                            "unidentified mxType of octave-shift: {other:?}"
2898                        )));
2899                    }
2900                }
2901            }
2902            "pedal" => {
2903                let Importer {
2904                    spanners, pending, ..
2905                } = &mut *self.part.importer;
2906                match element.get("type") {
2907                    Some(kind @ ("start" | "sostenuto")) => {
2908                        let mut building = Building::new(SpannerKind::PedalMark, id);
2909                        building.pedal.pedal_type = Some(if kind == "start" {
2910                            PedalType::Sustain
2911                        } else {
2912                            PedalType::Sostenuto
2913                        });
2914                        if element.get("line") == Some("yes") {
2915                            building.pedal.form = Some(PedalForm::Line);
2916                        } else if element.get("line") == Some("no")
2917                            || element.get("sign") == Some("yes")
2918                        {
2919                            building.pedal.form = Some(PedalForm::Symbol);
2920                        }
2921                        building.pedal.abbreviated = element.get("abbreviated") == Some("yes");
2922                        spanners.push(building);
2923                        pending.push_back(spanners.len() - 1);
2924                        self.pedal_starts.push((spanners.len() - 1, total));
2925                        made.push(spanners.len() - 1);
2926                    }
2927                    Some(kind @ ("continue" | "stop" | "discontinue" | "resume" | "change")) => {
2928                        let index = open(spanners, &|kind| kind == SpannerKind::PedalMark)
2929                            .ok_or_else(|| import_error("Error in getting PedalMark"))?;
2930                        match kind {
2931                            "stop" => {
2932                                spanners[index].complete = true;
2933                                if let Some(last) = last {
2934                                    spanners[index].add(last);
2935                                }
2936                            }
2937                            "resume" => {
2938                                let started = self
2939                                    .pedal_starts
2940                                    .iter()
2941                                    .find(|(held, _)| *held == index)
2942                                    .map(|(_, at)| *at);
2943                                if spanners[index].pedal.form == Some(PedalForm::Symbol)
2944                                    && started == Some(total)
2945                                {
2946                                    spanners[index].pedal.form = Some(PedalForm::SymbolLine);
2947                                } else {
2948                                    self.pedal_object(PedalObjectKind::GapEnd, index, staff, total);
2949                                }
2950                            }
2951                            "discontinue" => {
2952                                self.pedal_object(PedalObjectKind::GapStart, index, staff, total);
2953                            }
2954                            "change" => {
2955                                self.pedal_object(PedalObjectKind::Bounce, index, staff, total);
2956                            }
2957                            // music21 remembers nothing of a pedal that
2958                            // carries on.
2959                            _ => {}
2960                        }
2961                    }
2962                    other => {
2963                        return Err(import_error(format!(
2964                            "unidentified mxType of pedal: {other:?}"
2965                        )));
2966                    }
2967                }
2968            }
2969            _ => {}
2970        }
2971        Ok(made)
2972    }
2973
2974    /// A bounce or a gap in a held pedal, put where the direction stands and
2975    /// joined by the pedal mark it belongs to.
2976    fn pedal_object(&mut self, kind: PedalObjectKind, pedal: usize, staff: i32, total: FloatType) {
2977        let uid = self.insert(total, staff, PedalObject::new(kind));
2978        self.part.importer.spanners[pedal].add(uid);
2979    }
2980
2981    /// music21's `xmlHarmony` and `xmlToChordSymbol`.
2982    fn harmony(&mut self, harmony: &'x Xml) -> Result<()> {
2983        let mut kind = harmony.child_text("kind").to_string();
2984        let kind_element = harmony.find("kind");
2985        let mut kind_text = String::new();
2986        if !kind.is_empty() {
2987            for (alias, target) in crate::chordsymbol::CHORD_KIND_ALIASES {
2988                if alias == kind {
2989                    kind = target.to_string();
2990                }
2991            }
2992            let written = kind_element.and_then(|kind| kind.get("text")).unwrap_or("");
2993            if !(written.is_empty() && kind != "none") {
2994                kind_text = written.to_string();
2995            }
2996        }
2997        let pitch_of = |step: &Xml, alter: Option<&Xml>| -> Result<Option<Pitch>> {
2998            let name = match step.text().filter(|text| !text.is_empty()) {
2999                Some(text) => text,
3000                None => match step.get("text") {
3001                    Some(text) => text,
3002                    None => return Ok(None),
3003                },
3004            };
3005            let mut pitch = Pitch::from_name(name.trim())?;
3006            if let Some(alter) = alter.and_then(|alter| parse_float(alter.stripped())) {
3007                pitch.set_written_accidental(Some(Accidental::new(alter)?));
3008            }
3009            Ok(Some(pitch))
3010        };
3011        let bass = match harmony.find("bass") {
3012            Some(bass) => {
3013                let step = bass
3014                    .find("bass-step")
3015                    .ok_or_else(|| import_error("bass-step missing"))?;
3016                pitch_of(step, bass.find("bass-alter"))?
3017            }
3018            None => None,
3019        };
3020        let root = match harmony.find("root") {
3021            Some(root) => match root.find("root-step") {
3022                Some(step) => pitch_of(step, root.find("root-alter"))?,
3023                None => None,
3024            },
3025            None => None,
3026        };
3027
3028        let mut symbol = if kind == "none" {
3029            ChordSymbol::no_chord(Some(kind_text.clone()).filter(|text| !text.is_empty()))
3030        } else {
3031            let root = root.ok_or_else(|| {
3032                import_error("reading a chord symbol given by function is not supported yet")
3033            })?;
3034            let mut symbol = match ChordSymbol::from_kind(root.clone(), &kind, bass.clone()) {
3035                Ok(symbol) => symbol,
3036                // A kind music21 has no notes for is still a symbol of that
3037                // kind on its root.
3038                Err(_) => {
3039                    let mut symbol = ChordSymbol::parse_music21(root.name())?;
3040                    symbol.set_root(root);
3041                    symbol.set_bass(bass);
3042                    symbol.with_kind(&kind)
3043                }
3044            };
3045            symbol.set_kind_text(Some(kind_text));
3046            symbol
3047        };
3048        if !symbol.is_no_chord() {
3049            for degree in harmony.find_all("degree") {
3050                let value = parse_int(degree.child_text("degree-value"))
3051                    .ok_or_else(|| import_error("degree-value missing"))?;
3052                let alter = parse_int(degree.child_text("degree-alter")).unwrap_or(0);
3053                let modification = ChordStepModification::new(
3054                    crate::chordsymbol::ChordStepModificationType::from_music21_name(
3055                        degree.child_text("degree-type"),
3056                    )?,
3057                    value.clamp(0, 255) as u8,
3058                    alter,
3059                )?;
3060                symbol.add_chord_step_modification(modification);
3061            }
3062        }
3063        symbol.set_placement(placement_of(harmony));
3064        let offset = self.offset + self.offset_of(harmony);
3065        self.insert(offset, staff_number(harmony), symbol);
3066        Ok(())
3067    }
3068
3069    /// music21's `xmlBarline`.
3070    fn barline(&mut self, element: &'x Xml) -> Result<()> {
3071        let style = element.child_text("bar-style");
3072        let right = matches!(element.get("location"), Some("right") | None);
3073        let barline = match element.find("repeat") {
3074            Some(repeat) => {
3075                let written = match repeat.get("direction").map(str::to_lowercase).as_deref() {
3076                    Some("forward") => RepeatDirection::Start,
3077                    Some("backward") => RepeatDirection::End,
3078                    Some(other) => {
3079                        return Err(import_error(format!(
3080                            "cannot handle mx direction format: {other}"
3081                        )));
3082                    }
3083                    None => return Err(import_error("Repeat sign direction is required")),
3084                };
3085                // A repeat closing a measure ends the passage, whatever the
3086                // file says; only an end says how many times.
3087                let direction = if right { RepeatDirection::End } else { written };
3088                let times = repeat
3089                    .get("times")
3090                    .and_then(parse_int)
3091                    .map(|times| times.max(0) as u32)
3092                    .filter(|_| written == RepeatDirection::End);
3093                // The sign is drawn as its direction draws it, not as the
3094                // file's bar style says.
3095                Barline::repeat(direction, times)
3096            }
3097            None => {
3098                let mut barline = Barline::default();
3099                if !style.is_empty() {
3100                    barline.set_bar_type(BarlineType::from_name(style)?);
3101                }
3102                barline
3103            }
3104        };
3105
3106        if let Some(ending) = element.find("ending") {
3107            let index = self.part.measures.len();
3108            let open = self
3109                .part
3110                .brackets
3111                .iter()
3112                .position(|bracket| !bracket.complete);
3113            let at = match open {
3114                Some(at) => {
3115                    if !self.part.brackets[at].measures.contains(&index) {
3116                        self.part.brackets[at].measures.push(index);
3117                    }
3118                    at
3119                }
3120                None => {
3121                    self.part.brackets.push(Bracket {
3122                        numbers: vec![0],
3123                        measures: vec![index],
3124                        complete: false,
3125                    });
3126                    self.part.brackets.len() - 1
3127                }
3128            };
3129            let bracket = &mut self.part.brackets[at];
3130            if ending.get("type") == Some("start") {
3131                bracket.numbers = bracket_numbers(ending.get("number").unwrap_or("1"));
3132                if let Some(text) = ending.text() {
3133                    let digits = text.strip_suffix('.').unwrap_or(text);
3134                    if !digits.is_empty()
3135                        && digits.chars().all(|c| c.is_ascii_digit())
3136                        && let Ok(number) = digits.parse::<u32>()
3137                    {
3138                        bracket.numbers = vec![number];
3139                    }
3140                }
3141            }
3142            if matches!(ending.get("type"), Some("stop" | "discontinue")) {
3143                bracket.complete = true;
3144            }
3145        }
3146
3147        match element.get("location") {
3148            Some("left") => self.measure.left = Some(barline),
3149            Some("right") | None => self.measure.right = Some(barline),
3150            // A barline in the middle of a measure is an element of its own,
3151            // which music21 appends: after the last thing read so far ends,
3152            // whatever the file's place for it, and at the very start of a
3153            // measure whose notes are all in voices.
3154            Some(_) => {
3155                let end = self.measure.highest_time_outside_voices();
3156                self.insert(end, NO_STAFF, barline);
3157            }
3158        }
3159        Ok(())
3160    }
3161}
3162
3163/// General MIDI's drum for a pitch as the instrument music21 has for it:
3164/// its `PercussionMapper.midiPitchToInstrument`, and a bare unpitched
3165/// instrument where it has none.
3166fn percussion_instrument(pitch: u8) -> Instrument {
3167    let class = match pitch {
3168        35 | 36 => "BassDrum",
3169        37 | 38 | 40 => "SnareDrum",
3170        41 | 43 | 45 | 47 | 48 | 50 => "TomTom",
3171        42 | 44 | 46 => "HiHatCymbal",
3172        49 | 57 => "CrashCymbals",
3173        54 => "Tambourine",
3174        56 => "Cowbell",
3175        58 => "Vibraslap",
3176        60 | 61 => "BongoDrums",
3177        62..=64 => "CongaDrum",
3178        65 | 66 => "Timbales",
3179        67 | 68 => "Agogo",
3180        70 => "Maracas",
3181        71 | 72 => "Whistle",
3182        76 | 77 => "Woodblock",
3183        80 | 81 => "Triangle",
3184        _ => {
3185            let mut bare = Instrument::of_kind("UnpitchedPercussion").unwrap_or_default();
3186            bare.set_percussion_pitch(Some(pitch));
3187            return bare;
3188        }
3189    };
3190    let mut instrument = Instrument::of_kind(class).unwrap_or_default();
3191    // An instrument that names its drums apart takes the one asked for.
3192    let usual = instrument.percussion_pitch();
3193    instrument.set_percussion_pitch(Some(pitch));
3194    if instrument.modifier().is_none() {
3195        instrument.set_percussion_pitch(usual);
3196    }
3197    instrument
3198}
3199
3200/// What is read of a note before it is known whether it has a pitch.
3201enum ReadNote {
3202    Note(Note),
3203    Unpitched(Unpitched),
3204    Rest(Rest),
3205}
3206
3207impl ReadNote {
3208    fn written(&self) -> &Note {
3209        match self {
3210            Self::Note(note) => note,
3211            Self::Unpitched(stroke) => stroke.written(),
3212            Self::Rest(_) => unreachable!("a rest is not written as a note"),
3213        }
3214    }
3215
3216    fn written_mut(&mut self) -> &mut Note {
3217        match self {
3218            Self::Note(note) => note,
3219            Self::Unpitched(stroke) => stroke.written_mut(),
3220            Self::Rest(_) => unreachable!("a rest is not written as a note"),
3221        }
3222    }
3223}
3224
3225/// What a `<note>` says that a note, a stroke and a rest all take.
3226#[derive(Default)]
3227struct General {
3228    color: Option<String>,
3229    hidden: bool,
3230    volume: Option<Volume>,
3231    size: Option<NoteSize>,
3232    tie: Option<Tie>,
3233    duration: Option<Duration>,
3234    articulations: Vec<Articulation>,
3235    expressions: Vec<Expression>,
3236    arpeggio_spanners: Vec<(String, ArpeggioType)>,
3237}
3238
3239fn reduce(numerator: i64, denominator: i64) -> (i64, i64) {
3240    fn gcd(a: i64, b: i64) -> i64 {
3241        if b == 0 { a.abs() } else { gcd(b, a % b) }
3242    }
3243    let divisor = gcd(numerator, denominator).max(1);
3244    (numerator / divisor, denominator / divisor)
3245}
3246
3247/// music21's `_setAttributeFromTagText` with `_floatOrIntStr`: the length
3248/// a child gives, where it gives one.
3249fn tenths(element: &Xml, tag: &str) -> Option<Tenths> {
3250    element
3251        .find(tag)
3252        .and_then(Xml::text)
3253        .filter(|text| !text.is_empty())
3254        .and_then(Tenths::parse)
3255}
3256
3257/// music21's `xmlPageLayoutToPageLayout`.
3258fn read_page_layout(written: &Xml, read: &mut PageLayout) {
3259    if let Some(height) = tenths(written, "page-height") {
3260        read.page_height = Some(height);
3261    }
3262    if let Some(width) = tenths(written, "page-width") {
3263        read.page_width = Some(width);
3264    }
3265    if let Some(margins) = written.find("page-margins") {
3266        for (tag, place) in [
3267            ("top-margin", &mut read.top_margin),
3268            ("bottom-margin", &mut read.bottom_margin),
3269            ("left-margin", &mut read.left_margin),
3270            ("right-margin", &mut read.right_margin),
3271        ] {
3272            if let Some(margin) = tenths(margins, tag) {
3273                *place = Some(margin);
3274            }
3275        }
3276    }
3277}
3278
3279/// music21's `xmlSystemLayoutToSystemLayout`.
3280fn read_system_layout(written: &Xml, read: &mut SystemLayout) {
3281    if let Some(margins) = written.find("system-margins") {
3282        for (tag, place) in [
3283            ("top-margin", &mut read.top_margin),
3284            ("bottom-margin", &mut read.bottom_margin),
3285            ("left-margin", &mut read.left_margin),
3286            ("right-margin", &mut read.right_margin),
3287        ] {
3288            if let Some(margin) = tenths(margins, tag) {
3289                *place = Some(margin);
3290            }
3291        }
3292    }
3293    if let Some(distance) = tenths(written, "system-distance") {
3294        read.distance = Some(distance);
3295    }
3296    if let Some(distance) = tenths(written, "top-system-distance") {
3297        read.top_distance = Some(distance);
3298    }
3299}
3300
3301/// music21's `xmlStaffLayoutToStaffLayout`.
3302fn staff_layout(written: &Xml) -> StaffLayout {
3303    StaffLayout {
3304        distance: tenths(written, "staff-distance"),
3305        staff_number: written
3306            .get("number")
3307            .and_then(|value| value.trim().parse().ok()),
3308        ..StaffLayout::default()
3309    }
3310}
3311
3312/// music21's `xmlStaffLayoutFromStaffDetails`.
3313fn read_staff_details(details: &Xml, read: &mut StaffLayout) {
3314    if let Some(number) = details
3315        .get("number")
3316        .and_then(|value| value.trim().parse().ok())
3317    {
3318        read.staff_number = Some(number);
3319    }
3320    match details.get("print-object") {
3321        Some("no") => read.hidden = Some(true),
3322        Some("yes") => read.hidden = Some(false),
3323        _ => {}
3324    }
3325    if let Some(lines) = details
3326        .find("staff-lines")
3327        .and_then(Xml::text)
3328        .and_then(|text| text.trim().parse().ok())
3329    {
3330        read.staff_lines = Some(lines);
3331    }
3332    if let Some(kind) = details
3333        .find("staff-type")
3334        .and_then(Xml::text)
3335        .and_then(|text| StaffType::from_name(text.trim()))
3336    {
3337        read.staff_type = kind;
3338    }
3339    if let Some(size) = tenths(details, "staff-size") {
3340        read.staff_size = Some(size.value());
3341    }
3342}
3343
3344/// music21's `setPrintStyleAlign`: where text stands, its font and colour,
3345/// and how it is aligned, read into a style.
3346pub(crate) fn read_print_style_align(written: &Xml, style: &mut TextStyle) -> Result<()> {
3347    let value = |name: &str| written.get(name).map(StyleValue::read);
3348    if let Some(read) = value("default-x") {
3349        style.absolute_x = Some(read);
3350    }
3351    if let Some(read) = value("default-y") {
3352        style.absolute_y = Some(read);
3353    }
3354    if let Some(read) = value("relative-x") {
3355        style.relative_x = Some(read);
3356    }
3357    if let Some(read) = value("relative-y") {
3358        style.relative_y = Some(read);
3359    }
3360    if let Some(family) = written.get("font-family") {
3361        style.font_family = Some(
3362            family
3363                .split(',')
3364                .map(|name| name.trim().to_string())
3365                .collect(),
3366        );
3367    }
3368    if let Some(font_style) = written.get("font-style") {
3369        style.set_font_style(Some(font_style))?;
3370    }
3371    if let Some(size) = value("font-size") {
3372        style.font_size = Some(size);
3373    }
3374    if let Some(weight) = written.get("font-weight") {
3375        style.set_font_weight(Some(weight))?;
3376    }
3377    if let Some(color) = written.get("color") {
3378        style.color = Some(color.to_string());
3379    }
3380    if let Some(align) = written.get("valign") {
3381        style.set_align_vertical(Some(align))?;
3382    }
3383    if let Some(align) = written.get("halign") {
3384        style.set_align_horizontal(Some(align))?;
3385    }
3386    Ok(())
3387}
3388
3389/// music21's `xmlCreditToTextBox`: a text box of the credit's words, one
3390/// line each, drawn as its first words say, on the page it names or the
3391/// first. A fresh text box's place, 500 tenths each way, stands where the
3392/// words say none.
3393fn text_box(credit: &Xml) -> Result<TextBox> {
3394    let mut read = TextBox::new("");
3395    read.style.set_align_horizontal(None)?;
3396    read.style.set_align_vertical(None)?;
3397    if let Some(page) = credit.get("page") {
3398        read.page = page.trim().parse().map_err(|_| {
3399            import_error(format!("invalid literal for int() with base 10: {page:?}"))
3400        })?;
3401    }
3402    let words: Vec<String> = credit
3403        .find_all("credit-words")
3404        .filter_map(|words| {
3405            words
3406                .text()
3407                .filter(|text| !text.is_empty())
3408                .map(str::to_string)
3409        })
3410        .collect();
3411    if words.is_empty() {
3412        return Ok(read);
3413    }
3414    read.content = words.join("\n");
3415    if let Some(first) = credit.find("credit-words") {
3416        read_print_style_align(first, &mut read.style)?;
3417        read.style.set_justify(first.get("justify"))?;
3418    }
3419    Ok(read)
3420}
3421
3422/// music21's `xmlDefaultsToScoreLayout`.
3423fn score_layout(defaults: &Xml) -> ScoreLayout {
3424    let mut read = ScoreLayout::default();
3425    if let Some(scaling) = defaults.find("scaling") {
3426        read.scaling_millimeters = tenths(scaling, "millimeters");
3427        read.scaling_tenths = tenths(scaling, "tenths");
3428    }
3429    if let Some(written) = defaults.find("page-layout") {
3430        let mut page = PageLayout::default();
3431        read_page_layout(written, &mut page);
3432        read.page_layout = Some(page);
3433    }
3434    if let Some(written) = defaults.find("system-layout") {
3435        let mut system = SystemLayout::default();
3436        read_system_layout(written, &mut system);
3437        read.system_layout = Some(system);
3438    }
3439    read.staff_layouts = defaults
3440        .find_all("staff-layout")
3441        .map(staff_layout)
3442        .collect();
3443    read
3444}
3445
3446/// music21's `getStaffNumber`.
3447fn staff_number(element: &Xml) -> i32 {
3448    match element.tag.as_str() {
3449        "harmony" | "forward" | "note" | "direction" => {
3450            parse_int(element.child_text("staff")).unwrap_or(NO_STAFF)
3451        }
3452        "clef" | "staff-details" | "staff-layout" => {
3453            element.get("number").and_then(parse_int).unwrap_or(1)
3454        }
3455        "measure-style" | "key" | "time" | "transpose" => element
3456            .get("number")
3457            .and_then(parse_int)
3458            .unwrap_or(NO_STAFF),
3459        _ => NO_STAFF,
3460    }
3461}
3462
3463/// music21's `xmlToPitch`.
3464fn pitch_of(note: &Xml) -> Result<Pitch> {
3465    let Some(written) = note.find("pitch") else {
3466        return Pitch::from_name("C4");
3467    };
3468    let step = written.child_text("step");
3469    let octave = written.child_text("octave");
3470    let mut pitch = Pitch::from_name(format!("{step}{octave}"))?;
3471    let alter = parse_float(written.child_text("alter"));
3472    let accidental = note
3473        .find("accidental")
3474        .filter(|written| !written.stripped().is_empty());
3475    match accidental {
3476        Some(written) => {
3477            if let Ok(mut read) = accidental_of(written) {
3478                read.set_display_status(Some(true));
3479                if let Some(alter) = alter
3480                    && alter != read.alter()
3481                {
3482                    read.set_alter_independently(alter);
3483                }
3484                pitch.set_written_accidental(Some(read));
3485            }
3486        }
3487        None => {
3488            if let Some(alter) = alter {
3489                let mut read = Accidental::new(alter)
3490                    .map_err(|_| import_error(format!("incorrect accidental {alter} for pitch")))?;
3491                read.set_display_status(Some(false));
3492                pitch.set_written_accidental(Some(read));
3493            }
3494        }
3495    }
3496    Ok(pitch)
3497}
3498
3499/// music21's `xmlToAccidental`.
3500fn accidental_of(written: &Xml) -> Result<Accidental> {
3501    let name = written.stripped().to_lowercase();
3502    let name = match name.as_str() {
3503        "quarter-sharp" => "half-sharp",
3504        "three-quarters-sharp" => "one-and-a-half-sharp",
3505        "quarter-flat" => "half-flat",
3506        "three-quarters-flat" => "one-and-a-half-flat",
3507        "flat-flat" => "double-flat",
3508        "sharp-sharp" => "double-sharp",
3509        other => other,
3510    };
3511    let mut accidental = Accidental::natural();
3512    accidental.set_allowing_non_standard_value(name)?;
3513    if let Some(color) = written.get("color") {
3514        accidental.set_color(Some(color.to_string()));
3515    }
3516    match (yes(written.get("parentheses")), yes(written.get("bracket"))) {
3517        (true, true) => accidental.set_display_style("both")?,
3518        (true, false) => accidental.set_display_style("parentheses")?,
3519        (false, true) => accidental.set_display_style("bracket")?,
3520        (false, false) => {}
3521    }
3522    Ok(accidental)
3523}
3524
3525/// music21's `xmlToBeams`.
3526fn beams_of(written: &[&Xml]) -> Result<Beams> {
3527    let mut beams = Beams::new();
3528    for (index, beam) in written.iter().enumerate() {
3529        let text = beam.text().map_or("begin", str::trim);
3530        let (beam_type, direction) = match text {
3531            "begin" => (BeamType::Start, None),
3532            "continue" => (BeamType::Continue, None),
3533            "end" => (BeamType::Stop, None),
3534            "forward hook" => (BeamType::PartialBeam, Some(BeamDirection::Right)),
3535            "backward hook" => (BeamType::PartialBeam, Some(BeamDirection::Left)),
3536            other => {
3537                return Err(import_error(format!(
3538                    "unexpected beam type encountered ({other})"
3539                )));
3540            }
3541        };
3542        let mut read = Beam::new(beam_type, direction);
3543        read.set_number(Some(index as u32 + 1));
3544        beams.beams_mut().push(read);
3545    }
3546    Ok(beams)
3547}
3548
3549/// music21's `xmlToTie`.
3550fn tie_of(note: &Xml) -> Option<Tie> {
3551    let types: Vec<&str> = note
3552        .find_all("tie")
3553        .filter_map(|tie| tie.get("type"))
3554        .collect();
3555    let tie_type = if types.len() == 1 {
3556        TieType::from_name(types[0]).ok()?
3557    } else if types.contains(&"stop") && types.contains(&"start") {
3558        TieType::Continue
3559    } else {
3560        TieType::Start
3561    };
3562    let mut tie = Tie::new(tie_type);
3563    if let Some(first) = note
3564        .find("notations")
3565        .and_then(|notations| notations.find("tied"))
3566    {
3567        if let Some(style) = first.get("line-type").filter(|style| *style != "wavy")
3568            && let Ok(style) = TieStyle::from_name(style)
3569        {
3570            tie.set_style(style);
3571        }
3572        match first.get("placement") {
3573            Some(placement) => tie.set_placement(Placement::from_name(placement).ok()),
3574            None => match first.get("orientation") {
3575                Some("over") => tie.set_placement(Some(Placement::Above)),
3576                Some("under") => tie.set_placement(Some(Placement::Below)),
3577                _ => {}
3578            },
3579        }
3580    }
3581    Some(tie)
3582}
3583
3584/// music21's `updateLyricsFromList` and `xmlToLyric`.
3585fn lyrics_from<'x>(written: impl Iterator<Item = &'x Xml>) -> Result<Vec<Lyric>> {
3586    let mut out = Vec::new();
3587    let mut current = 1;
3588    for lyric in written {
3589        let texts: Vec<&Xml> = lyric.find_all("text").collect();
3590        let syllabics: Vec<&Xml> = lyric.find_all("syllabic").collect();
3591        let elisions: Vec<&Xml> = lyric.find_all("elision").collect();
3592        let mut read = Lyric::unsung();
3593        if texts.is_empty() {
3594            // A lyric that only extends one: music21 hands back a bare one.
3595            out.push(read);
3596            current += 1;
3597            continue;
3598        }
3599        if texts.len() == 1 {
3600            if let Some(text) = texts[0].text() {
3601                read.set_text(text.trim());
3602            }
3603            if let Some(syllabic) = syllabics.first()
3604                && let Ok(syllabic) = Syllabic::from_name(syllabic.stripped())
3605            {
3606                read.set_syllabic(syllabic);
3607            }
3608        } else if texts.len() > 1 {
3609            let mut components = Vec::new();
3610            for (index, text) in texts.iter().enumerate() {
3611                let mut component = Lyric::unsung();
3612                if let Some(text) = text.text() {
3613                    component.set_text(text.trim());
3614                }
3615                if let Some(syllabic) = syllabics.get(index) {
3616                    if let Ok(syllabic) = Syllabic::from_name(syllabic.stripped()) {
3617                        component.set_syllabic(syllabic);
3618                    }
3619                    if index >= 1
3620                        && let Some(elision) = elisions.get(index - 1)
3621                    {
3622                        component.set_elision_before(elision.text().unwrap_or(""));
3623                    }
3624                }
3625                components.push(component);
3626            }
3627            read.set_components(components);
3628        }
3629        match lyric.get("number") {
3630            Some(number) => match number.trim().parse::<IntegerType>() {
3631                Ok(number) => read.set_number(number),
3632                Err(_) => {
3633                    read.set_number(0);
3634                    read.set_identifier(Some(number.to_string()));
3635                }
3636            },
3637            None => read.set_number(0),
3638        }
3639        if let Some(name) = lyric.get("name") {
3640            read.set_identifier(Some(name.to_string()));
3641        }
3642        if let Some(justify) = lyric.get("justify") {
3643            read.set_justify(Some(Justification::from_name(justify)?));
3644        }
3645        if let Some(placement) = lyric.get("placement") {
3646            read.set_placement(Placement::from_name(placement).ok());
3647        }
3648        if lyric.get("print-object") == Some("no") {
3649            read.set_hidden(true);
3650        }
3651        if let Some(color) = lyric.get("color") {
3652            read.set_color(Some(color.to_string()));
3653        }
3654        if read.number() == 0 {
3655            read.set_number(current);
3656        }
3657        out.push(read);
3658        current += 1;
3659    }
3660    Ok(out)
3661}
3662
3663/// music21's `xmlToArticulation`.
3664fn articulation_of(mark: &Xml) -> Option<Articulation> {
3665    let class = match mark.tag.as_str() {
3666        "accent" => "Accent",
3667        "strong-accent" => "StrongAccent",
3668        "staccato" => "Staccato",
3669        "staccatissimo" => "Staccatissimo",
3670        "spiccato" => "Spiccato",
3671        "tenuto" => "Tenuto",
3672        "detached-legato" => "DetachedLegato",
3673        "scoop" => "Scoop",
3674        "plop" => "Plop",
3675        "doit" => "Doit",
3676        "falloff" => "Falloff",
3677        "breath-mark" => "BreathMark",
3678        "caesura" => "Caesura",
3679        "stress" => "Stress",
3680        "unstress" => "Unstress",
3681        "other-articulation" => "Articulation",
3682        _ => return None,
3683    };
3684    let mut articulation = Articulation::of_kind(ArticulationKind::from_class_name(class)?);
3685    if let Some(placement) = mark.get("placement") {
3686        articulation.set_placement(Some(placement.to_string()));
3687    }
3688    let text = mark.stripped();
3689    match mark.tag.as_str() {
3690        "strong-accent" => {
3691            if let Some(direction) = mark.get("type") {
3692                articulation.set_point_direction(Some(direction.to_string()));
3693            }
3694        }
3695        "breath-mark" if !text.is_empty() => articulation.set_symbol(Some(text.to_string())),
3696        "other-articulation" if !text.is_empty() => {
3697            articulation.set_display_text(Some(text.to_string()));
3698        }
3699        _ => {}
3700    }
3701    Some(articulation)
3702}
3703
3704/// music21's `xmlTechnicalToArticulation`.
3705fn technical_of(mark: &Xml) -> Option<Articulation> {
3706    let class = match mark.tag.as_str() {
3707        "up-bow" => "UpBow",
3708        "down-bow" => "DownBow",
3709        "harmonic" => "StringHarmonic",
3710        "open-string" => "OpenString",
3711        "thumb-position" => "StringThumbPosition",
3712        "fingering" => "Fingering",
3713        "pluck" => "FrettedPluck",
3714        "double-tongue" => "DoubleTongue",
3715        "triple-tongue" => "TripleTongue",
3716        "stopped" => "Stopped",
3717        "snap-pizzicato" => "SnapPizzicato",
3718        "string" => "StringIndication",
3719        "bend" => "FretBend",
3720        "tap" => "FretTap",
3721        "fret" => "FretIndication",
3722        "heel" => "OrganHeel",
3723        "toe" => "OrganToe",
3724        "fingernails" => "HarpFingerNails",
3725        "handbell" => "HandbellIndication",
3726        "other-technical" => "TechnicalIndication",
3727        _ => return None,
3728    };
3729    let mut technical = Articulation::of_kind(ArticulationKind::from_class_name(class)?);
3730    let text = mark.stripped();
3731    match mark.tag.as_str() {
3732        "fingering" => {
3733            technical.set_finger(Some(Finger::from_written(mark.text().unwrap_or(""))));
3734            if let Some(substitution) = mark.get("substitution") {
3735                technical.set_substitution(substitution == "yes");
3736            }
3737            if let Some(alternate) = mark.get("alternate") {
3738                technical.set_alternate(alternate == "yes");
3739            }
3740        }
3741        "handbell" | "other-technical" if !text.is_empty() => {
3742            technical.set_display_text(Some(text.to_string()));
3743        }
3744        "fret" | "string" => {
3745            if let Some(number) = parse_int(text) {
3746                technical.set_number(number);
3747            }
3748        }
3749        "harmonic" => {
3750            if mark.find("artificial").is_some() {
3751                technical.set_harmonic_type(Some("artificial".to_string()));
3752            } else if mark.find("natural").is_some() {
3753                technical.set_harmonic_type(Some("natural".to_string()));
3754            }
3755        }
3756        "heel" | "toe" => {
3757            if let Some(substitution) = mark.get("substitution") {
3758                technical.set_substitution(substitution == "yes");
3759            }
3760        }
3761        _ => {}
3762    }
3763    if let Some(placement) = mark.get("placement") {
3764        technical.set_placement(Some(placement.to_string()));
3765    }
3766    Some(technical)
3767}
3768
3769/// music21's `xmlToTempoIndication`, for a mark with one note value.
3770/// music21's `xmlToTempoIndication`: a `<metronome>` is a tempo mark, or
3771/// with two note values a metric modulation, placed as its direction says.
3772fn metronome(written: &Xml, placement: Option<Placement>) -> Result<StreamElement> {
3773    let mut referents: Vec<Duration> = Vec::new();
3774    let mut numbers: Vec<FloatType> = Vec::new();
3775    for child in &written.children {
3776        match child.tag.as_str() {
3777            "beat-unit" => {
3778                referents.push(Duration::from_type(duration_type(child.stripped())?));
3779            }
3780            "beat-unit-dot" => {
3781                let last = referents
3782                    .last_mut()
3783                    .ok_or_else(|| import_error("encountered metronome components out of order"))?;
3784                let (kind, dots) = last
3785                    .type_and_dots()
3786                    .ok_or_else(|| import_error("a beat unit with no written value"))?;
3787                *last = Duration::from_type_with_dots(kind, dots + 1);
3788            }
3789            "per-minute" => {
3790                if let Some(number) = parse_float(child.stripped()) {
3791                    numbers.push(number);
3792                }
3793            }
3794            _ => {}
3795        }
3796    }
3797    let parentheses = written.get("parentheses") == Some("yes");
3798    if referents.len() > 1 {
3799        // All a file gives are the two note values: neither side has a
3800        // number until the mark in force before it is known.
3801        let mut sides = referents.into_iter();
3802        let mut modulation = MetricModulation::new();
3803        if let Some(old) = sides.next() {
3804            modulation.set_old_referent(old, None);
3805        }
3806        if let Some(new) = sides.next() {
3807            modulation.set_new_referent(new);
3808        }
3809        modulation.set_parentheses(parentheses);
3810        modulation.set_placement(placement);
3811        return Ok(modulation.into());
3812    }
3813    let mut mark = MetronomeMark::default();
3814    if let Some(number) = numbers.first() {
3815        mark.set_number(Some(*number));
3816    }
3817    if let Some(referent) = referents.into_iter().next() {
3818        mark.set_referent(referent);
3819    }
3820    if parentheses {
3821        mark.set_parentheses(true);
3822    }
3823    mark.set_placement(placement);
3824    Ok(mark.into())
3825}
3826
3827/// music21's `xmlToTimeSignature`.
3828fn time_signature(written: &Xml) -> Result<Option<TimeSignature>> {
3829    if written.find("senza-misura").is_some() {
3830        return Err(import_error(
3831            "reading a senza misura time signature is not supported yet",
3832        ));
3833    }
3834    let mut numerators = Vec::new();
3835    let mut denominators = Vec::new();
3836    for child in &written.children {
3837        match child.tag.as_str() {
3838            "beats" => numerators.push(child.stripped()),
3839            "beat-type" => denominators.push(child.stripped()),
3840            "interchangeable" => break,
3841            _ => {}
3842        }
3843    }
3844    let ratio = numerators
3845        .iter()
3846        .zip(&denominators)
3847        .map(|(numerator, denominator)| format!("{numerator}/{denominator}"))
3848        .collect::<Vec<_>>()
3849        .join("+");
3850    let mut meter = TimeSignature::from_ratio_string(&ratio)
3851        .map_err(|_| import_error(format!("Cannot process time signature {ratio}")))?;
3852    if let Some(color) = written.get("color") {
3853        meter.set_color(Some(color.to_string()));
3854    }
3855    meter.set_hidden(written.get("print-object") == Some("no"));
3856    match written.get("symbol") {
3857        Some(symbol @ ("common" | "cut" | "single-number" | "normal")) => {
3858            meter.set_symbol(Some(symbol.to_string()));
3859        }
3860        Some("note") => meter.set_symbolize_denominator(true),
3861        _ => {}
3862    }
3863    Ok(Some(meter))
3864}
3865
3866/// music21's `xmlToClef`.
3867fn clef_of(written: &Xml) -> Result<Clef> {
3868    let sign = written.child_text("sign");
3869    let mut clef = if matches!(
3870        sign.to_lowercase().as_str(),
3871        "tab" | "percussion" | "none" | "jianpu"
3872    ) {
3873        Clef::from_string(sign, 0)?
3874    } else {
3875        let line = match written.find("line") {
3876            Some(line) => line.stripped(),
3877            None if sign == "G" => "2",
3878            None => "4",
3879        };
3880        let octave_change = parse_int(written.child_text("clef-octave-change")).unwrap_or(0);
3881        Clef::from_string(&format!("{sign}{line}"), octave_change)?
3882    };
3883    if let Some(color) = written.get("color") {
3884        clef.set_color(Some(color.to_string()));
3885    }
3886    Ok(clef)
3887}
3888
3889/// music21's `xmlToKeySignature`: a key where the mode is said and is one
3890/// music21 knows, a signature otherwise.
3891fn key_signature(written: &Xml) -> Result<StreamElement> {
3892    let color = written.get("color").map(str::to_string);
3893    if written.find("fifths").is_none() {
3894        let mut steps = Vec::new();
3895        let mut alters = Vec::new();
3896        for child in &written.children {
3897            match child.tag.as_str() {
3898                "key-step" if !child.stripped().is_empty() => steps.push(child.stripped()),
3899                "key-alter" => {
3900                    if let Some(alter) = parse_float(child.stripped()) {
3901                        alters.push(alter);
3902                    }
3903                }
3904                _ => {}
3905            }
3906        }
3907        if steps.len() != alters.len() {
3908            return Err(import_error(
3909                "For non traditional signatures each step must have an alter",
3910            ));
3911        }
3912        let mut pitches = Vec::new();
3913        for (step, alter) in steps.into_iter().zip(alters) {
3914            let mut pitch = Pitch::from_name(step)?;
3915            pitch.set_written_accidental(Some(Accidental::new(alter)?));
3916            pitches.push(pitch);
3917        }
3918        let mut signature = KeySignature::from_altered_pitches(pitches);
3919        signature.set_color(color);
3920        return Ok(signature.into());
3921    }
3922    let sharps = parse_int(written.child_text("fifths")).unwrap_or(0);
3923    let mut signature = KeySignature::new(sharps);
3924    let mode = written.find("mode").and_then(Xml::text).unwrap_or("");
3925    if !mode.is_empty()
3926        && let Ok(mut key) = signature.try_as_key(Some(mode), None)
3927    {
3928        key.set_color(color);
3929        return Ok(key.into());
3930    }
3931    signature.set_color(color);
3932    Ok(signature.into())
3933}
3934
3935/// music21's `xmlTransposeToInterval`.
3936fn transpose_interval(written: &Xml) -> Result<Interval> {
3937    let mut diatonic = written
3938        .find("diatonic")
3939        .and_then(|d| parse_int(d.stripped()));
3940    let chromatic = written
3941        .find("chromatic")
3942        .and_then(|c| parse_int(c.stripped()));
3943    let mut octave_change = 0;
3944    if let Some(octaves) = written
3945        .find("octave-change")
3946        .and_then(|o| parse_int(o.stripped()))
3947    {
3948        octave_change = octaves * 12;
3949        if let Some(diatonic) = &mut diatonic {
3950            *diatonic += 7 * octaves;
3951        }
3952    }
3953    match (diatonic, chromatic) {
3954        (Some(diatonic), Some(chromatic)) => {
3955            let actual = |steps: IntegerType| if steps < 0 { steps - 1 } else { steps + 1 };
3956            Interval::from_generic_and_chromatic(actual(diatonic), chromatic + octave_change)
3957                .or_else(|_| {
3958                    let steps = diatonic - octave_change * 7 / 12;
3959                    Interval::from_generic_and_chromatic(actual(steps), chromatic + octave_change)
3960                })
3961        }
3962        (None, Some(chromatic)) => Interval::from_semitones(chromatic + octave_change),
3963        _ => Interval::from_name("P1"),
3964    }
3965}
3966
3967#[cfg(test)]
3968mod tests {
3969    use super::*;
3970    use crate::musicxml::{ExportOptions, to_musicxml};
3971
3972    /// A score of one part holding these measures.
3973    fn score(measures: &str) -> String {
3974        format!(
3975            "<?xml version=\"1.0\"?><score-partwise version=\"4.0\"><part-list>             <score-part id=\"P1\"><part-name>Flute</part-name></score-part></part-list>             <part id=\"P1\">{measures}</part></score-partwise>"
3976        )
3977    }
3978
3979    const NOTE: &str = "<note><pitch><step>C</step><octave>4</octave></pitch>                        <duration>1</duration><type>quarter</type></note>";
3980
3981    #[test]
3982    fn offsets_are_snapped_as_music21_snaps_them() {
3983        // music21's `opFrac`: a third is a third, not the float nearest it.
3984        assert_eq!(op_frac(0.333_333_333_333_3), 1.0 / 3.0);
3985        assert_eq!(op_frac(0.25), 0.25);
3986        assert_eq!(op_frac(1.0 / 3.0 + 1.0 / 3.0 + 1.0 / 3.0), 1.0);
3987        assert_eq!(op_frac(-2.0 / 3.0), -2.0 / 3.0);
3988    }
3989
3990    #[test]
3991    fn an_ending_is_numbered_as_music21_numbers_it() {
3992        assert_eq!(bracket_numbers(""), [0]);
3993        assert_eq!(bracket_numbers("2"), [2]);
3994        assert_eq!(bracket_numbers("1, 2"), [1, 2]);
3995        assert_eq!(bracket_numbers("1-3"), [1, 2, 3]);
3996        assert_eq!(bracket_numbers("first"), [1]);
3997    }
3998
3999    #[test]
4000    fn a_part_and_its_notes_are_read() {
4001        let read = from_musicxml(&score(&format!(
4002            "<measure number=\"1\"><attributes><divisions>1</divisions>             <key><fifths>-1</fifths><mode>major</mode></key>             <time><beats>2</beats><beat-type>4</beat-type></time>             <clef><sign>G</sign><line>2</line></clef></attributes>{NOTE}             <note><rest/><duration>1</duration><type>quarter</type></note></measure>"
4003        )))
4004        .unwrap();
4005        let part = read.parts()[0];
4006        assert_eq!(part.name(), Some("Flute"));
4007        let measure = part.measures()[0];
4008        assert_eq!(measure.number(), 1);
4009        let kinds: Vec<&str> = measure
4010            .events()
4011            .iter()
4012            .map(|event| match event.element() {
4013                StreamElement::Clef(_) => "clef",
4014                StreamElement::Key(_) => "key",
4015                StreamElement::TimeSignature(_) => "time",
4016                StreamElement::Note(_) => "note",
4017                StreamElement::Rest(_) => "rest",
4018                _ => "other",
4019            })
4020            .collect();
4021        // Sorted as music21 sorts a measure, whatever order the file had.
4022        assert_eq!(kinds, ["clef", "key", "time", "note", "rest"]);
4023        assert_eq!(measure.events()[4].offset(), 1.0);
4024    }
4025
4026    #[test]
4027    fn notes_sharing_a_stem_are_a_chord() {
4028        let read = from_musicxml(&score(&format!(
4029            "<measure number=\"1\"><attributes><divisions>1</divisions></attributes>{NOTE}             <note><chord/><pitch><step>E</step><octave>4</octave></pitch>             <duration>1</duration><type>quarter</type></note></measure>"
4030        )))
4031        .unwrap();
4032        let measure = read.parts()[0].measures()[0];
4033        let chords: Vec<_> = measure
4034            .events()
4035            .iter()
4036            .filter_map(|event| match event.element() {
4037                StreamElement::Chord(chord) => Some(chord),
4038                _ => None,
4039            })
4040            .collect();
4041        assert_eq!(chords.len(), 1);
4042        assert_eq!(chords[0].notes().len(), 2);
4043    }
4044
4045    #[test]
4046    fn two_voices_are_two_streams() {
4047        let voiced = |voice: u8| {
4048            format!(
4049                "<note><pitch><step>C</step><octave>4</octave></pitch><duration>1</duration>                 <voice>{voice}</voice><type>quarter</type></note>"
4050            )
4051        };
4052        let read = from_musicxml(&score(&format!(
4053            "<measure number=\"1\"><attributes><divisions>1</divisions></attributes>{}             <backup><duration>1</duration></backup>{}</measure>",
4054            voiced(1),
4055            voiced(2)
4056        )))
4057        .unwrap();
4058        let measure = read.parts()[0].measures()[0];
4059        let voices = measure.voices();
4060        assert_eq!(voices.len(), 2);
4061        assert_eq!(voices[1].id(), Some("2"));
4062        // The second voice was backed up to the start of the measure.
4063        assert_eq!(voices[1].events()[0].offset(), 0.0);
4064    }
4065
4066    #[test]
4067    fn a_slur_joins_the_notes_it_is_written_on() {
4068        let slurred = |kind: &str| {
4069            format!(
4070                "<note><pitch><step>C</step><octave>4</octave></pitch><duration>1</duration>                 <type>quarter</type><notations><slur number=\"1\" type=\"{kind}\"/>                 </notations></note>"
4071            )
4072        };
4073        let read = from_musicxml(&score(&format!(
4074            "<measure number=\"1\"><attributes><divisions>1</divisions></attributes>{}{NOTE}{}             </measure>",
4075            slurred("start"),
4076            slurred("stop")
4077        )))
4078        .unwrap();
4079        let spanners = read.spanners();
4080        assert_eq!(spanners.len(), 1);
4081        assert_eq!(spanners[0].kind(), SpannerKind::Slur);
4082        let leaves = read.leaves();
4083        let ends: Vec<usize> = spanners[0].spanned().iter().flatten().copied().collect();
4084        assert_eq!(ends.len(), 2);
4085        assert!(matches!(leaves[ends[0]].1, StreamElement::Note(_)));
4086        assert_eq!(leaves[ends[1]].0, 2.0);
4087    }
4088
4089    #[test]
4090    fn a_hairpin_started_after_a_part_ends_takes_the_next_part_first_note() {
4091        let wedge = |kind: &str| {
4092            format!(
4093                "<direction><direction-type><wedge type=\"{kind}\"/></direction-type></direction>"
4094            )
4095        };
4096        let measure = |step: &str, after: &str| {
4097            format!(
4098                "<measure number=\"1\"><attributes><divisions>1</divisions></attributes>\
4099                 <note><pitch><step>{step}</step><octave>4</octave></pitch><duration>1</duration>\
4100                 <type>quarter</type></note>{after}</measure>"
4101            )
4102        };
4103        let document = format!(
4104            "<?xml version=\"1.0\"?><score-partwise version=\"4.0\"><part-list>\
4105             <score-part id=\"P1\"><part-name>One</part-name></score-part>\
4106             <score-part id=\"P2\"><part-name>Two</part-name></score-part></part-list>\
4107             <part id=\"P1\">{}</part><part id=\"P2\">{}</part></score-partwise>",
4108            measure("C", &(wedge("diminuendo") + &wedge("stop"))),
4109            measure("D", ""),
4110        );
4111        let read = from_musicxml(&document).unwrap();
4112        let spanners = read.spanners();
4113        assert_eq!(spanners.len(), 1);
4114        assert_eq!(spanners[0].kind(), SpannerKind::Diminuendo);
4115        let leaves = read.leaves();
4116        let named: Vec<String> = spanners[0]
4117            .spanned()
4118            .iter()
4119            .flatten()
4120            .map(|position| match leaves[*position].1 {
4121                StreamElement::Note(note) => note.pitch().name_with_octave(),
4122                _ => String::new(),
4123            })
4124            .collect();
4125        // The stop names the note it follows, and the queue then puts the
4126        // next note read, in the next part, first.
4127        assert_eq!(named, ["D4", "C4"]);
4128    }
4129
4130    #[test]
4131    fn a_part_on_two_staves_is_two_parts_in_a_brace() {
4132        let on_staff = |staff: u8| {
4133            format!(
4134                "<note><pitch><step>C</step><octave>4</octave></pitch><duration>1</duration>                 <type>quarter</type><staff>{staff}</staff></note>"
4135            )
4136        };
4137        let read = from_musicxml(&score(&format!(
4138            "<measure number=\"1\"><attributes><divisions>1</divisions><staves>2</staves>             </attributes>{}<backup><duration>1</duration></backup>{}</measure>",
4139            on_staff(1),
4140            on_staff(2)
4141        )))
4142        .unwrap();
4143        let parts = read.parts();
4144        assert_eq!(parts.len(), 2);
4145        assert_eq!(parts[0].kind(), StreamKind::PartStaff);
4146        assert_eq!(parts[1].id(), Some("P1-Staff2"));
4147        let groups = read.staff_groups();
4148        assert_eq!(groups.len(), 1);
4149        assert_eq!(groups[0].parts(), [0, 1]);
4150        assert_eq!(groups[0].symbol(), Some("brace"));
4151    }
4152
4153    #[test]
4154    fn what_is_read_is_written_back() {
4155        let document = score(&format!(
4156            "<measure number=\"1\"><attributes><divisions>1</divisions>             <time><beats>1</beats><beat-type>4</beat-type></time>             <clef><sign>G</sign><line>2</line></clef></attributes>{NOTE}</measure>"
4157        ));
4158        let written = to_musicxml(
4159            &from_musicxml(&document).unwrap(),
4160            &ExportOptions::default(),
4161        )
4162        .unwrap();
4163        assert!(written.contains("<part-name>Flute</part-name>"));
4164        assert!(written.contains("<step>C</step>"));
4165        assert!(written.contains("<type>quarter</type>"));
4166        assert!(written.contains("<beat-type>4</beat-type>"));
4167    }
4168
4169    /// A quarter note carrying these notations.
4170    fn noted(notations: &str) -> String {
4171        format!(
4172            "<note><pitch><step>C</step><octave>4</octave></pitch><duration>1</duration>\
4173             <type>quarter</type><notations>{notations}</notations></note>"
4174        )
4175    }
4176
4177    /// One measure of a quarter to the division holding these.
4178    fn measure_of(contents: &str) -> String {
4179        score(&format!(
4180            "<measure number=\"1\"><attributes><divisions>1</divisions></attributes>\
4181             {contents}</measure>"
4182        ))
4183    }
4184
4185    fn written_back(read: &Stream) -> String {
4186        to_musicxml(read, &ExportOptions::default()).unwrap()
4187    }
4188
4189    #[test]
4190    fn a_glissando_and_a_slide_are_read() {
4191        let read = from_musicxml(&measure_of(&format!(
4192            "{}{}{}",
4193            noted("<glissando number=\"1\" type=\"start\" line-type=\"dashed\">gl.</glissando>"),
4194            noted("<glissando number=\"1\" type=\"stop\"/><slide number=\"2\" type=\"start\"/>"),
4195            noted("<slide number=\"2\" type=\"stop\"/>"),
4196        )))
4197        .unwrap();
4198        let spanners = read.spanners();
4199        assert_eq!(spanners.len(), 2);
4200        assert!(
4201            spanners
4202                .iter()
4203                .all(|spanner| spanner.kind() == SpannerKind::Glissando && spanner.len() == 2)
4204        );
4205        // music21 reads the line and how it is played, and not the words.
4206        assert_eq!(spanners[0].line_type(), Some("dashed"));
4207        assert_eq!(
4208            spanners[0].glissando_details(),
4209            Some(&Glissando {
4210                slide_type: SlideType::Chromatic,
4211                label: None
4212            })
4213        );
4214        // A slide is drawn solid where nothing says.
4215        assert_eq!(spanners[1].line_type(), Some("solid"));
4216        assert_eq!(
4217            spanners[1]
4218                .glissando_details()
4219                .map(|slide| slide.slide_type),
4220            Some(SlideType::Continuous)
4221        );
4222        let written = written_back(&read);
4223        assert!(written.contains("<glissando line-type=\"dashed\" number=\"1\" type=\"start\" />"));
4224        assert!(written.contains("<slide line-type=\"solid\" number=\"2\" type=\"stop\" />"));
4225    }
4226
4227    #[test]
4228    fn a_glissando_drawn_no_way_music21_knows_is_refused() {
4229        let read = from_musicxml(&measure_of(&noted(
4230            "<glissando number=\"1\" type=\"start\" line-type=\"zigzag\"/>",
4231        )));
4232        assert!(read.is_err());
4233    }
4234
4235    #[test]
4236    fn a_tremolo_that_starts_and_stops_is_a_spanner() {
4237        let read = from_musicxml(&measure_of(&format!(
4238            "{}{}{}",
4239            noted("<ornaments><tremolo type=\"start\" placement=\"above\">2</tremolo></ornaments>"),
4240            noted("<ornaments><tremolo type=\"stop\">2</tremolo></ornaments>"),
4241            noted("<ornaments><tremolo type=\"single\">1</tremolo></ornaments>"),
4242        )))
4243        .unwrap();
4244        let spanners = read.spanners();
4245        assert_eq!(spanners.len(), 1);
4246        assert_eq!(spanners[0].kind(), SpannerKind::TremoloSpanner);
4247        assert_eq!(spanners[0].number_of_marks(), Some(2));
4248        assert_eq!(spanners[0].placement(), Some(Placement::Above));
4249        assert_eq!(spanners[0].len(), 2);
4250        // The tremolo on one note is an ornament of that note still.
4251        let ornamented = read
4252            .leaves()
4253            .iter()
4254            .filter(|(_, element)| match element {
4255                StreamElement::Note(note) => !note.expressions().is_empty(),
4256                _ => false,
4257            })
4258            .count();
4259        assert_eq!(ornamented, 1);
4260        let written = written_back(&read);
4261        assert!(written.contains("<tremolo placement=\"above\" type=\"start\">2</tremolo>"));
4262        assert!(written.contains("<tremolo type=\"stop\">2</tremolo>"));
4263    }
4264
4265    fn pedal(attributes: &str) -> String {
4266        format!("<direction><direction-type><pedal {attributes}/></direction-type></direction>")
4267    }
4268
4269    #[test]
4270    fn a_pedal_keeps_its_bounces_and_gaps() {
4271        let read = from_musicxml(&measure_of(&format!(
4272            "{}{NOTE}{}{NOTE}{}{NOTE}{}{NOTE}{}",
4273            pedal("type=\"start\" line=\"yes\" number=\"1\""),
4274            pedal("type=\"change\" line=\"yes\" number=\"1\""),
4275            pedal("type=\"discontinue\" line=\"yes\" number=\"1\""),
4276            pedal("type=\"resume\" line=\"yes\" number=\"1\""),
4277            pedal("type=\"stop\" line=\"yes\" number=\"1\""),
4278        )))
4279        .unwrap();
4280        let spanners = read.spanners();
4281        assert_eq!(spanners.len(), 1);
4282        assert_eq!(spanners[0].kind(), SpannerKind::PedalMark);
4283        let leaves = read.leaves();
4284        let joined: Vec<String> = spanners[0]
4285            .spanned()
4286            .iter()
4287            .flatten()
4288            .map(|position| match leaves[*position] {
4289                (offset, StreamElement::PedalObject(object)) => {
4290                    format!("{} at {offset}", object.kind().class_name())
4291                }
4292                (offset, _) => format!("note at {offset}"),
4293            })
4294            .collect();
4295        assert_eq!(
4296            joined,
4297            [
4298                "note at 0",
4299                "PedalBounce at 1",
4300                "PedalGapStart at 2",
4301                "PedalGapEnd at 3",
4302                "note at 3"
4303            ]
4304        );
4305        let written = written_back(&read);
4306        for kind in ["change", "discontinue", "resume"] {
4307            assert!(written.contains(&format!("<pedal line=\"yes\" type=\"{kind}\" />")));
4308        }
4309    }
4310
4311    #[test]
4312    fn a_sign_with_a_line_resumed_at_once_is_one_pedal() {
4313        let read = from_musicxml(&measure_of(&format!(
4314            "{NOTE}{}{}{NOTE}{}",
4315            pedal("type=\"start\" sign=\"yes\" number=\"1\""),
4316            pedal("type=\"resume\" line=\"yes\" number=\"1\""),
4317            pedal("type=\"stop\" line=\"yes\" number=\"1\""),
4318        )))
4319        .unwrap();
4320        let spanners = read.spanners();
4321        assert_eq!(
4322            spanners[0].pedal().and_then(|pedal| pedal.form),
4323            Some(PedalForm::SymbolLine)
4324        );
4325        assert!(
4326            read.leaves()
4327                .iter()
4328                .all(|(_, element)| !matches!(element, StreamElement::PedalObject(_)))
4329        );
4330        // The line resumes where the pedal mark stands, the start of the
4331        // part, a quarter before the note it goes down on.
4332        let written = written_back(&read);
4333        assert!(written.contains(
4334            "<pedal line=\"yes\" type=\"resume\" />\n        </direction-type>\n        \
4335             <offset sound=\"yes\">-10080</offset>"
4336        ));
4337    }
4338
4339    #[test]
4340    fn a_barline_inside_a_measure_stands_where_music21_appends_it() {
4341        let barline = "<barline location=\"middle\"><bar-style>dashed</bar-style></barline>";
4342        let inner = |document: &str| -> Vec<FloatType> {
4343            let read = from_musicxml(document).unwrap();
4344            let measure = read.parts()[0].measures()[0];
4345            measure
4346                .events()
4347                .iter()
4348                .filter(|event| matches!(event.element(), StreamElement::Barline(_)))
4349                .map(StreamEvent::offset)
4350                .collect()
4351        };
4352        // After what has been read, wherever the file would have it.
4353        assert_eq!(
4354            inner(&measure_of(&format!(
4355                "{NOTE}{NOTE}{barline}{NOTE}{NOTE}{barline}"
4356            ))),
4357            [2.0, 4.0]
4358        );
4359        // And at the start of a measure whose notes are all in voices.
4360        let voiced = |voice: u8| {
4361            format!(
4362                "<note><pitch><step>C</step><octave>4</octave></pitch><duration>1</duration>\
4363                 <voice>{voice}</voice><type>quarter</type></note>"
4364            )
4365        };
4366        assert_eq!(
4367            inner(&measure_of(&format!(
4368                "{}{barline}<backup><duration>1</duration></backup>{}",
4369                voiced(1),
4370                voiced(2)
4371            ))),
4372            [0.0]
4373        );
4374        // Nothing is written for one.
4375        let read = from_musicxml(&measure_of(&format!("{NOTE}{barline}{NOTE}"))).unwrap();
4376        assert!(!written_back(&read).contains("<barline"));
4377    }
4378
4379    #[test]
4380    fn a_lyric_says_how_it_is_drawn() {
4381        let read = from_musicxml(&measure_of(
4382            "<note><pitch><step>C</step><octave>4</octave></pitch><duration>1</duration>\
4383             <type>quarter</type><lyric number=\"1\" justify=\"left\" placement=\"below\" \
4384             print-object=\"yes\"><syllabic>single</syllabic><text>la</text></lyric></note>",
4385        ))
4386        .unwrap();
4387        let leaves = read.leaves();
4388        let note = leaves
4389            .iter()
4390            .find_map(|(_, element)| match element {
4391                StreamElement::Note(note) => Some(note),
4392                _ => None,
4393            })
4394            .expect("the measure holds a note");
4395        let lyric = &note.lyrics()[0];
4396        assert_eq!(lyric.justify(), Some(Justification::Left));
4397        assert_eq!(lyric.placement(), Some(Placement::Below));
4398        assert!(!lyric.is_hidden());
4399        assert!(
4400            written_back(&read)
4401                .contains("<lyric justify=\"left\" name=\"1\" number=\"1\" placement=\"below\">")
4402        );
4403    }
4404
4405    #[test]
4406    fn a_rehearsal_mark_is_read_and_written_centred() {
4407        let read = from_musicxml(&measure_of(&format!(
4408            "<direction placement=\"above\"><direction-type>             <rehearsal enclosure=\"square\">A</rehearsal></direction-type></direction>{NOTE}"
4409        )))
4410        .unwrap();
4411        let leaves = read.leaves();
4412        let mark = leaves
4413            .iter()
4414            .find_map(|(_, element)| match element {
4415                StreamElement::RehearsalMark(mark) => Some(mark),
4416                _ => None,
4417            })
4418            .expect("the measure holds a rehearsal mark");
4419        assert_eq!(mark.content(), "A");
4420        assert_eq!(mark.enclosure(), Some("square"));
4421        assert_eq!(mark.placement(), Some(Placement::Above));
4422        assert!(written_back(&read).contains(
4423            "<rehearsal enclosure=\"square\" halign=\"center\" valign=\"middle\">A</rehearsal>"
4424        ));
4425        // music21 raises on an enclosure it has no name for.
4426        assert!(
4427            from_musicxml(&measure_of(
4428                "<direction><direction-type><rehearsal enclosure=\"star\">A</rehearsal>                 </direction-type></direction>"
4429            ))
4430            .is_err()
4431        );
4432    }
4433
4434    #[test]
4435    fn a_time_signature_may_be_left_unprinted() {
4436        let read = from_musicxml(&score(
4437            "<measure number=\"1\"><attributes><divisions>1</divisions>             <time print-object=\"no\"><beats>3</beats><beat-type>4</beat-type></time>             </attributes></measure>",
4438        ))
4439        .unwrap();
4440        let leaves = read.leaves();
4441        let meter = leaves
4442            .iter()
4443            .find_map(|(_, element)| match element {
4444                StreamElement::TimeSignature(meter) => Some(meter),
4445                _ => None,
4446            })
4447            .expect("the measure holds a time signature");
4448        assert!(meter.is_hidden());
4449        assert!(written_back(&read).contains("<time print-object=\"no\">"));
4450    }
4451
4452    #[test]
4453    fn a_metric_modulation_takes_its_numbers_from_the_tempo_before_it() {
4454        // Read off music21: a dotted quarter at 110, then a quarter becoming
4455        // a dotted quarter, sounds at 247.5 quarters a minute.
4456        let read = from_musicxml(&score(&format!(
4457            "<measure number=\"1\"><attributes><divisions>1</divisions></attributes>\
4458             <direction><direction-type><metronome><beat-unit>quarter</beat-unit>\
4459             <beat-unit-dot/><per-minute>110</per-minute></metronome></direction-type>\
4460             </direction>{NOTE}</measure><measure number=\"2\">\
4461             <direction placement=\"above\"><direction-type><metronome>\
4462             <beat-unit>quarter</beat-unit><beat-unit>quarter</beat-unit><beat-unit-dot/>\
4463             </metronome></direction-type></direction>{NOTE}</measure>"
4464        )))
4465        .unwrap();
4466        let leaves = read.leaves();
4467        let modulation = leaves
4468            .iter()
4469            .find_map(|(_, element)| match element {
4470                StreamElement::MetricModulation(modulation) => Some(modulation),
4471                _ => None,
4472            })
4473            .expect("the second measure holds a metric modulation");
4474        // As read, neither side has a number: a file gives the note values.
4475        assert_eq!(
4476            modulation.old_metronome().and_then(|old| old.number()),
4477            None
4478        );
4479        assert_eq!(modulation.placement(), Some(Placement::Above));
4480        let written = written_back(&read);
4481        assert!(written.contains(
4482            "<metronome parentheses=\"no\">\n            <beat-unit>quarter</beat-unit>\n            \
4483             <beat-unit>quarter</beat-unit>\n            <beat-unit-dot />\n          \
4484             </metronome>\n        </direction-type>\n        <sound tempo=\"247.5\" />"
4485        ));
4486    }
4487
4488    #[test]
4489    fn words_naming_a_coda_place_no_sign() {
4490        let words = |text: &str| {
4491            format!(
4492                "<direction placement=\"above\"><direction-type><words>{text}</words>                 </direction-type></direction>"
4493            )
4494        };
4495        let read = from_musicxml(&measure_of(&format!(
4496            "{}{NOTE}{}",
4497            words("Coda"),
4498            words("Fine")
4499        )))
4500        .unwrap();
4501        let placed: Vec<Option<Placement>> = read
4502            .leaves()
4503            .iter()
4504            .filter_map(|(_, element)| match element {
4505                StreamElement::RepeatExpression(mark) => Some(mark.placement()),
4506                _ => None,
4507            })
4508            .collect();
4509        // The coda is drawn as its sign, which music21 places nowhere; the
4510        // words of a fine stay where they were written.
4511        assert_eq!(placed, [None, Some(Placement::Above)]);
4512    }
4513
4514    #[test]
4515    fn a_document_that_is_not_a_score_is_refused() {
4516        assert!(from_musicxml("<opus/>").is_err());
4517        assert!(from_musicxml("not xml at all").is_err());
4518    }
4519
4520    #[test]
4521    fn credits_are_read_and_written_as_music21_keeps_them() {
4522        let document = format!(
4523            "<?xml version=\"1.0\"?><score-partwise version=\"4.0\">\
4524             <credit page=\"2\"><credit-words default-x=\"600.5\" default-y=\"1500\" \
4525             font-size=\"22.0\" font-style=\"bolditalic\" justify=\"Center\" valign=\"top\">\
4526             Sonata</credit-words><credit-words>in G</credit-words></credit>\
4527             <credit><credit-words halign=\"right\">Anon</credit-words></credit>\
4528             <part-list><score-part id=\"P1\"><part-name>Flute</part-name></score-part>\
4529             </part-list><part id=\"P1\"><measure number=\"1\"><attributes><divisions>1\
4530             </divisions></attributes>{NOTE}</measure></part></score-partwise>"
4531        );
4532        let read = from_musicxml(&document).unwrap();
4533        let boxes: Vec<&TextBox> = read
4534            .events()
4535            .iter()
4536            .filter_map(|event| match event.element() {
4537                StreamElement::TextBox(text) => Some(&**text),
4538                _ => None,
4539            })
4540            .collect();
4541        assert_eq!(boxes.len(), 2);
4542        assert_eq!(boxes[0].content, "Sonata\nin G");
4543        assert_eq!(boxes[0].page, 2);
4544        assert_eq!(boxes[0].style.font_size, Some(StyleValue::Whole(22)));
4545        assert_eq!(boxes[0].style.justify(), Some("center"));
4546        // A credit that says no place stands where a fresh text box does.
4547        assert_eq!(boxes[1].style.absolute_x, Some(StyleValue::Whole(500)));
4548        assert_eq!(boxes[1].page, 1);
4549
4550        let written = written_back(&read);
4551        for expected in [
4552            "<credit page=\"2\">",
4553            "default-x=\"600.5\"",
4554            "font-size=\"22\"",
4555            "font-style=\"italic\"",
4556            "font-weight=\"bold\"",
4557            "justify=\"center\"",
4558            "xml:space=\"preserve\"",
4559            "<credit-words default-x=\"500\" default-y=\"500\" halign=\"right\">Anon</credit-words>",
4560        ] {
4561            assert!(written.contains(expected), "{expected} in\n{written}");
4562        }
4563        assert!(written.find("<credit").unwrap() < written.find("<part-list>").unwrap());
4564        // A page that is no number is refused, as music21's `int` refuses it.
4565        assert!(from_musicxml("<score-partwise><credit page=\"x\"/></score-partwise>").is_err());
4566    }
4567
4568    #[test]
4569    fn layouts_are_read_and_written_as_music21_keeps_them() {
4570        let document = format!(
4571            "<?xml version=\"1.0\"?><score-partwise version=\"4.0\">             <defaults><scaling><millimeters>6.5</millimeters><tenths>40</tenths></scaling>             <page-layout><page-height>1800.0</page-height><page-width>1400</page-width>             <page-margins><left-margin>80</left-margin><right-margin>80</right-margin>             </page-margins></page-layout><staff-layout><staff-distance>90</staff-distance>             </staff-layout></defaults><part-list><score-part id=\"P1\"><part-name>Flute             </part-name></score-part></part-list><part id=\"P1\"><measure number=\"1\">             <print new-system=\"yes\"><system-layout><system-distance>120</system-distance>             </system-layout><staff-layout number=\"1\"><staff-distance>70.5</staff-distance>             </staff-layout><measure-numbering>system</measure-numbering></print>             <attributes><divisions>1</divisions><staff-details number=\"1\" print-object=\"no\">             <staff-lines>4</staff-lines></staff-details></attributes>{NOTE}</measure></part>             </score-partwise>"
4572        );
4573        let read = from_musicxml(&document).unwrap();
4574        let score_layout = read
4575            .events()
4576            .iter()
4577            .find_map(|event| match event.element() {
4578                StreamElement::Layout(Layout::Score(layout)) => Some(layout),
4579                _ => None,
4580            })
4581            .expect("the defaults are a score layout");
4582        // `1800.0` is kept whole, as music21's `_floatOrIntStr` keeps it.
4583        assert_eq!(
4584            score_layout.page_layout.as_ref().unwrap().page_height,
4585            Some(Tenths::Whole(1800))
4586        );
4587        assert_eq!(
4588            score_layout.scaling_millimeters,
4589            Some(Tenths::Fraction(6.5))
4590        );
4591        let measure = &read.parts()[0].measures()[0];
4592        assert_eq!(measure.measure_numbering(), Some("system"));
4593        let staff = measure
4594            .events()
4595            .iter()
4596            .find_map(|event| match event.element() {
4597                StreamElement::Layout(Layout::Staff(layout)) => Some(layout),
4598                _ => None,
4599            })
4600            .expect("a staff layout");
4601        // The details are read into the staff layout the print made.
4602        assert_eq!(staff.distance, Some(Tenths::Fraction(70.5)));
4603        assert_eq!(staff.staff_lines, Some(4));
4604        assert_eq!(staff.hidden, Some(true));
4605
4606        let written = written_back(&read);
4607        for expected in [
4608            "<millimeters>6.5</millimeters>",
4609            "<page-height>1800</page-height>",
4610            "<staff-distance>90</staff-distance>",
4611            "<print new-system=\"yes\">",
4612            "<system-distance>120</system-distance>",
4613            "<staff-layout number=\"1\">",
4614            "<measure-numbering>system</measure-numbering>",
4615            "<staff-details print-object=\"no\">",
4616            "<staff-lines>4</staff-lines>",
4617        ] {
4618            assert!(
4619                written.contains(expected),
4620                "{expected} in
4621{written}"
4622            );
4623        }
4624    }
4625}