Skip to main content

music21_rs/musicxml/
mod.rs

1//! MusicXML export: music21's `musicxml.m21ToXml`.
2//!
3//! [`to_musicxml`] writes a score as a `score-partwise` document, element for
4//! element as music21's `ScoreExporter` writes it with `makeNotation=False`:
5//! the score is taken as already notated, so its measures, beams, ties and
6//! the accidentals it shows are written as they stand rather than worked out
7//! again. A score is parts holding measures, a measure may hold voices, and a
8//! stream holding no parts is written as one part.
9//!
10//! [`ExportOptions::make_notation`] asks for the notation to be worked out
11//! first, as music21's exporter does by default: a part of loose notes, or a
12//! score whose lengths no single note value writes, is then written as
13//! music21 writes it.
14//!
15//! The writer does no file IO; it hands back the document as a string.
16
17mod notate;
18mod partstaff;
19mod read;
20mod tree;
21
22pub use read::from_musicxml;
23
24use crate::articulations::{Articulation, ArticulationKind};
25use crate::bar::{Barline, Ending, RepeatDirection};
26use crate::chord::Chord;
27use crate::chordsymbol::ChordSymbol;
28use crate::clef::Clef;
29use crate::defaults::FloatType;
30use crate::duration::{Duration, DurationType, Tuplet};
31use crate::dynamics::Dynamic;
32use crate::error::{Error, Result};
33use crate::expressions::{ArpeggioType, Expression, Ornament};
34use crate::instrument::Instrument;
35use crate::interval::Interval;
36use crate::key::KeySignature;
37use crate::layout::{Layout, PageLayout, StaffLayout, StaffType, SystemLayout, Tenths};
38use crate::metadata::{Metadata, namespace_name};
39use crate::meter::TimeSignature;
40use crate::notation::{
41    Beam, BeamDirection, BeamType, Beams, Lyric, Notehead, Placement, StemDirection, Tie,
42};
43use crate::note::Note;
44use crate::pitch::{Accidental, Pitch};
45use crate::repeat::{RepeatExpression, RepeatExpressionKind};
46use crate::rest::Rest;
47use crate::spanner::{
48    PedalForm, PedalObject, PedalObjectKind, PedalType, SlideType, Spanner, SpannerKind,
49};
50use crate::stream::{StaffGroup, Stream, StreamElement, StreamKind};
51use crate::style::TextStyle;
52use crate::tempo::{MetricModulation, MetronomeMark};
53use crate::text::TextBox;
54
55use tree::Element;
56
57/// How many divisions of a quarter note durations are counted in: music21's
58/// `defaults.divisionsPerQuarter`, `32 * 3 * 3 * 5 * 7`, which counts every
59/// tuplet music21 writes in whole numbers.
60pub const DIVISIONS_PER_QUARTER: FloatType = 10080.0;
61
62/// The MusicXML version written.
63pub const MUSICXML_VERSION: &str = "4.0";
64
65/// What a document says about how it was made, where the score does not say.
66///
67/// The defaults are music21's own, so that a score written here reads as the
68/// one music21 writes; a caller making a document of its own will want its
69/// own title and author, or none.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct ExportOptions {
72    /// The `<encoding-date>`, as `YYYY-MM-DD`. music21 writes the day it ran;
73    /// the crate reads no clock, so the date is written only when given.
74    pub encoding_date: Option<String>,
75    /// The `<software>` line written when the score carries no metadata.
76    /// A score with metadata names its software there instead.
77    pub software: String,
78    /// The movement title of a score with no title of its own: music21's
79    /// `defaults.title`, `Music21 Fragment`.
80    pub default_title: Option<String>,
81    /// The composer named when the score names no contributor: music21's
82    /// `defaults.author`, `Music21`.
83    pub default_author: Option<String>,
84    /// Whether to work out the notation the score leaves unsaid before
85    /// writing it, as music21's exporter does with `makeNotation=True`:
86    /// gaps filled with unprinted rests, a part with no measures cut into
87    /// them, notes running past a barline cut and tied, accidentals decided,
88    /// notes beamed and tuplets bracketed where the part has none yet, and
89    /// lengths no single note value writes cut into tied values. A measure
90    /// or a voice handed in alone is written as a part of one measure, given
91    /// the meter and clef its notes fit where it states none. The score
92    /// handed in is not changed.
93    ///
94    /// Off, the score is written as it stands, which is music21's
95    /// `makeNotation=False`: a caller that has made its notation already
96    /// gets exactly what it made.
97    pub make_notation: bool,
98}
99
100impl Default for ExportOptions {
101    fn default() -> Self {
102        Self {
103            encoding_date: None,
104            software: format!("music21-rs v.{}", env!("CARGO_PKG_VERSION")),
105            default_title: Some("Music21 Fragment".to_string()),
106            default_author: Some("Music21".to_string()),
107            make_notation: false,
108        }
109    }
110}
111
112/// Writes a score as a MusicXML document.
113///
114/// ```
115/// use music21_rs::{Duration, Note, Stream, StreamKind, TimeSignature};
116/// use music21_rs::musicxml::{ExportOptions, to_musicxml};
117///
118/// let mut measure = Stream::with_kind(StreamKind::Measure);
119/// measure.set_number(1);
120/// measure.insert(0.0, TimeSignature::new(1, 4)?);
121/// measure.push(Note::from_name("D#4")?.with_duration(Duration::quarter()));
122/// let mut part = Stream::with_kind(StreamKind::Part);
123/// part.push(measure);
124/// let mut score = Stream::with_kind(StreamKind::Score);
125/// score.push(part);
126///
127/// let xml = to_musicxml(&score, &ExportOptions::default())?;
128/// assert!(xml.contains("<step>D</step>"));
129/// assert!(xml.contains("<alter>1</alter>"));
130/// assert!(xml.contains("<duration>10080</duration>"));
131/// # Ok::<(), music21_rs::Error>(())
132/// ```
133///
134/// With [`ExportOptions::make_notation`] a part or a stream of loose notes
135/// may be handed in as readily as a score:
136///
137/// ```
138/// use music21_rs::{Duration, Note, Stream, StreamKind, TimeSignature};
139/// use music21_rs::musicxml::{ExportOptions, to_musicxml};
140///
141/// let mut part = Stream::with_kind(StreamKind::Part);
142/// part.insert(0.0, TimeSignature::new(3, 4)?);
143/// part.insert(0.0, Note::from_name("C4")?.with_duration(Duration::new(5.0)?));
144/// let options = ExportOptions {
145///     make_notation: true,
146///     ..ExportOptions::default()
147/// };
148/// let xml = to_musicxml(&part, &options)?;
149/// // A dotted half tied to a half, in two measures.
150/// assert_eq!(xml.matches("<measure ").count(), 2);
151/// assert!(xml.contains("<tie type=\"start\" />"));
152/// # Ok::<(), music21_rs::Error>(())
153/// ```
154///
155/// # Errors
156///
157/// A part with no measures, a score nested in a score, a duration no single
158/// note value writes, and anything the crate cannot yet write: each as
159/// music21's `MusicXMLExportException` says it where music21 raises. Making
160/// the notation first leaves only what no notation can write: a length no
161/// tie of note values reaches that is not an unprinted rest.
162pub fn to_musicxml(score: &Stream, options: &ExportOptions) -> Result<String> {
163    // music21's exporter turns a part at sounding pitch to the pitch its
164    // instruments read, and the notes under an octave line to where they
165    // sound (`toWrittenPitch(ottavasToSounding=True)`), before it writes;
166    // making the notation does that itself, at the point music21 does it.
167    let prepared;
168    let score = if options.make_notation {
169        prepared = notate::notated(score)?;
170        &prepared
171    } else if holds_sounding_pitch(score) || score.holds_transposing_ottava() {
172        prepared = score.to_written_pitch_by(true)?;
173        &prepared
174    } else {
175        score
176    };
177    let root = score_element(score, options)?;
178    Ok(format!(
179        "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE score-partwise  PUBLIC \
180         \"-//Recordare//DTD MusicXML {MUSICXML_VERSION} Partwise//EN\" \
181         \"http://www.musicxml.org/dtds/partwise.dtd\">\n{}",
182        root.dump()
183    ))
184}
185
186/// Whether anything in the stream, itself included, says it is at sounding
187/// pitch: nothing else is moved by writing it at written pitch.
188fn holds_sounding_pitch(stream: &Stream) -> bool {
189    stream.at_sounding_pitch() == Some(true)
190        || stream
191            .events()
192            .iter()
193            .filter_map(|event| event.element().as_stream())
194            .any(holds_sounding_pitch)
195}
196
197fn export_error(message: impl Into<String>) -> Error {
198    Error::MusicXml(message.into())
199}
200
201// ------------------------------------------------------------------ score
202
203/// music21's `ScoreExporter.parse`.
204fn score_element(score: &Stream, options: &ExportOptions) -> Result<Element> {
205    if score.is_empty() {
206        return score_element(&empty_score(), options);
207    }
208    let parts: Vec<&Stream> = if has_part_like_streams(score) {
209        for event in score.events() {
210            if let StreamElement::Stream(inner) = event.element()
211                && matches!(inner.kind(), StreamKind::Score | StreamKind::Opus)
212            {
213                return Err(export_error(
214                    "Exporting scores nested inside scores is not supported",
215                ));
216            }
217        }
218        score
219            .events()
220            .iter()
221            .filter_map(|event| event.element().as_stream())
222            .filter(|stream| matches!(stream.kind(), StreamKind::Part | StreamKind::PartStaff))
223            .collect()
224    } else {
225        vec![score]
226    };
227
228    let groups = score.staff_groups();
229    let joined = joinable_groups(&parts, groups);
230    let voice_bases = voice_bases(&parts, groups, &joined);
231    let numbered = id_locals(score, &parts);
232    let mut shared = Shared::default();
233    let mut exported = Vec::with_capacity(parts.len());
234    for (index, part) in parts.iter().copied().enumerate() {
235        let spanners = numbered.clone();
236        let mut role = StaffRole::default();
237        for group in &joined {
238            let parts_of = groups[*group].parts();
239            if let Some(at) = parts_of.iter().position(|position| *position == index) {
240                role.previous = at.checked_sub(1).map(|before| parts[parts_of[before]]);
241                role.later = at > 0;
242                if at == 0 {
243                    role.followers = parts_of[1..]
244                        .iter()
245                        .map(|position| parts[*position])
246                        .collect();
247                }
248            }
249        }
250        let previous = role.previous;
251        exported.push(Some(PartExporter::new(part, &mut shared, &role)?.parse(
252            spanners,
253            previous,
254            &voice_bases,
255        )?));
256    }
257
258    // music21's `joinPartStaffs`: each group's staves written into its first.
259    for group in &joined {
260        let positions = groups[*group].parts();
261        let mut roots: Vec<Element> = positions
262            .iter()
263            .map(|position| {
264                exported[*position]
265                    .as_mut()
266                    .map(|part| std::mem::replace(&mut part.part, Element::new("part")))
267                    .expect("a staff is joined into one group only")
268            })
269            .collect();
270        let staves: Vec<&Stream> = positions.iter().map(|position| parts[*position]).collect();
271        partstaff::join(
272            &mut roots,
273            differs_across(&staves, first_key, same_key),
274            differs_across(&staves, first_meter, same_meter),
275        )?;
276        let first = roots.swap_remove(0);
277        if let Some(part) = exported[positions[0]].as_mut() {
278            part.part = first;
279        }
280        for position in &positions[1..] {
281            exported[*position] = None;
282        }
283    }
284
285    let mut root = Element::new("score-partwise");
286    root.set("version", MUSICXML_VERSION);
287    let metadata = score.metadata();
288    set_titles(&mut root, metadata, options);
289    set_identification(&mut root, metadata, options);
290    set_defaults(&mut root, score);
291    for event in score.events() {
292        if let StreamElement::TextBox(text) = event.element() {
293            root.push(credit_element(text)?);
294        }
295    }
296    set_part_list(root.sub("part-list"), &exported, groups, &joined);
297    for (index, part) in exported.into_iter().flatten().enumerate() {
298        add_divider_comment(&mut root, &format!("Part {}", index + 1));
299        root.push(part.part);
300    }
301    Ok(root)
302}
303
304/// Where a part stands among the staves of a group written as one part.
305#[derive(Default)]
306struct StaffRole<'a> {
307    /// The staff before it in its group.
308    previous: Option<&'a Stream>,
309    /// The staves after it, where it is its group's first.
310    followers: Vec<&'a Stream>,
311    /// Whether it is a staff after its group's first.
312    later: bool,
313}
314
315/// music21's `joinableGroups`: the staff groups whose staves are written as
316/// one part, by index into the score's groups. A group of more than one
317/// `PartStaff`, each with measures; a second group over the same staves is
318/// passed over; and if any two overlap, none is joined.
319fn joinable_groups(parts: &[&Stream], groups: &[StaffGroup]) -> Vec<usize> {
320    let mut joinable: Vec<usize> = Vec::new();
321    for (index, group) in groups.iter().enumerate() {
322        let staves = group.parts();
323        if staves.len() <= 1 {
324            continue;
325        }
326        let fits = staves.iter().all(|position| {
327            parts.get(*position).is_some_and(|part| {
328                part.kind() == StreamKind::PartStaff && !part.measures().is_empty()
329            })
330        });
331        if !fits
332            || joinable
333                .iter()
334                .any(|known| groups[*known].parts() == staves)
335        {
336            continue;
337        }
338        joinable.push(index);
339    }
340    let mut seen: Vec<usize> = Vec::new();
341    for index in &joinable {
342        for position in groups[*index].parts() {
343            if seen.contains(position) {
344                return Vec::new();
345            }
346            seen.push(*position);
347        }
348    }
349    joinable
350}
351
352/// music21's `renumberVoicesWithinStaffGroups`: in a group of staves, a voice
353/// with no id of its own is numbered on from the voices of the staves above
354/// it in the measures starting where its measure does. Each measure is
355/// given the number its first such voice takes after one.
356fn voice_bases(
357    parts: &[&Stream],
358    groups: &[StaffGroup],
359    joined: &[usize],
360) -> std::collections::HashMap<usize, u32> {
361    let mut bases = std::collections::HashMap::new();
362    for group in joined {
363        let mut measures: Vec<(FloatType, usize, &Stream)> = Vec::new();
364        for (staff, position) in groups[*group].parts().iter().enumerate() {
365            for event in parts[*position].events() {
366                if let Some(measure) = event
367                    .element()
368                    .as_stream()
369                    .filter(|stream| stream.kind() == StreamKind::Measure)
370                {
371                    measures.push((event.offset(), staff, measure));
372                }
373            }
374        }
375        measures.sort_by(|left, right| left.0.total_cmp(&right.0).then(left.1.cmp(&right.1)));
376        let mut next = 0;
377        let mut stack_offset = None;
378        for (offset, _, measure) in measures {
379            if stack_offset != Some(offset) {
380                stack_offset = Some(offset);
381                next = 0;
382            }
383            bases.insert(measure as *const Stream as usize, next);
384            next += measure
385                .voices()
386                .iter()
387                .filter(|voice| voice.id().is_none())
388                .count() as u32;
389        }
390    }
391    bases
392}
393
394/// The first of some kind of mark anywhere in a staff.
395fn first_of<'a>(
396    staff: &'a Stream,
397    pick: impl Fn(&'a StreamElement) -> bool,
398) -> Option<&'a StreamElement> {
399    staff
400        .recurse()
401        .into_iter()
402        .map(|(_, element)| element)
403        .find(|element| pick(element))
404}
405
406fn first_key(staff: &Stream) -> Option<&StreamElement> {
407    first_of(staff, |element| {
408        matches!(
409            element,
410            StreamElement::Key(_) | StreamElement::KeySignature(_)
411        )
412    })
413}
414
415fn first_meter(staff: &Stream) -> Option<&StreamElement> {
416    first_of(staff, |element| {
417        matches!(element, StreamElement::TimeSignature(_))
418    })
419}
420
421/// Whether a later staff's first mark of a kind differs from the first
422/// staff's: music21's `isMultiAttribute`.
423fn differs_across(
424    staves: &[&Stream],
425    first: impl Fn(&Stream) -> Option<&StreamElement>,
426    same: impl Fn(&StreamElement, &StreamElement) -> bool,
427) -> bool {
428    let mut initial: Option<&StreamElement> = None;
429    for staff in staves {
430        match initial {
431            None => initial = first(staff),
432            Some(initial) => {
433                if let Some(later) = first(staff)
434                    && !same(later, initial)
435                {
436                    return true;
437                }
438            }
439        }
440    }
441    false
442}
443
444/// music21's `==` between two key signatures: a key equals a key of the same
445/// tonic and mode, a signature a signature of the same sharps, and a key
446/// never a bare signature.
447fn same_key(left: &StreamElement, right: &StreamElement) -> bool {
448    match (left, right) {
449        (StreamElement::Key(left), StreamElement::Key(right)) => {
450            left.tonic().name() == right.tonic().name() && left.mode() == right.mode()
451        }
452        (StreamElement::KeySignature(left), StreamElement::KeySignature(right)) => {
453            left.sharps() == right.sharps()
454                && left.altered_pitches().ok().map(|pitches| {
455                    pitches
456                        .iter()
457                        .map(Pitch::name_with_octave)
458                        .collect::<Vec<_>>()
459                }) == right.altered_pitches().ok().map(|pitches| {
460                    pitches
461                        .iter()
462                        .map(Pitch::name_with_octave)
463                        .collect::<Vec<_>>()
464                })
465        }
466        _ => false,
467    }
468}
469
470/// music21's `ratioEqual` between two meters.
471fn same_meter(left: &StreamElement, right: &StreamElement) -> bool {
472    match (left, right) {
473        (StreamElement::TimeSignature(left), StreamElement::TimeSignature(right)) => {
474            left.numerator() == right.numerator() && left.denominator() == right.denominator()
475        }
476        _ => false,
477    }
478}
479
480/// music21's `setPartList`: each part's `<score-part>`, with a staff group
481/// opened before the first part it spans and closed after the last, groups
482/// numbered from one in the order they open.
483fn set_part_list(
484    part_list: &mut Element,
485    parts: &[Option<ExportedPart>],
486    groups: &[StaffGroup],
487    joined: &[usize],
488) {
489    let mut numbers: Vec<Option<usize>> = vec![None; groups.len()];
490    let mut next = 1;
491    for (index, part) in parts.iter().enumerate() {
492        let Some(part) = part else {
493            continue;
494        };
495        for (group_index, group) in groups.iter().enumerate() {
496            if joined.contains(&group_index) {
497                continue;
498            }
499            if group.parts().first() == Some(&index) {
500                let mut start = staff_group_element(group);
501                start.set("number", next.to_string());
502                numbers[group_index] = Some(next);
503                next += 1;
504                part_list.push(start);
505            }
506        }
507        part_list.push(part.score_part.clone());
508        for (group_index, group) in groups.iter().enumerate() {
509            if joined.contains(&group_index) {
510                continue;
511            }
512            if group.parts().last() == Some(&index) {
513                let mut stop = Element::new("part-group");
514                stop.set("type", "stop");
515                if let Some(number) = numbers[group_index] {
516                    stop.set("number", number.to_string());
517                }
518                part_list.push(stop);
519            }
520        }
521    }
522}
523
524/// music21's `staffGroupToXmlPartGroup`.
525fn staff_group_element(group: &StaffGroup) -> Element {
526    let mut element = Element::new("part-group");
527    element.set("type", "start");
528    if let Some(name) = group.name() {
529        element.sub_text("group-name", name);
530    }
531    if group.name_hidden() {
532        element.sub("group-name-display").set("print-object", "no");
533    }
534    if let Some(abbreviation) = group.abbreviation() {
535        element.sub_text("group-abbreviation", abbreviation);
536    }
537    if let Some(symbol) = group.symbol() {
538        element.sub_text("group-symbol", symbol);
539    }
540    let barline = element.sub("group-barline");
541    if let Some(bar_together) = group.bar_together() {
542        barline.set_text(bar_together.as_str());
543    }
544    element
545}
546
547/// A note at one end of a spanner's range: the element holding it and its
548/// pitch space.
549type Extreme = (*const StreamElement, FloatType);
550
551/// The number each arpeggio across several chords is written with in one
552/// measure, one to sixteen and round again, in the order they are met:
553/// music21's `getArpeggioNumber`.
554#[derive(Default)]
555struct ArpeggioNumbers {
556    known: std::cell::RefCell<Vec<(*const Spanner, u32)>>,
557}
558
559impl ArpeggioNumbers {
560    fn number(&self, spanner: &Spanner) -> u32 {
561        let mut known = self.known.borrow_mut();
562        if let Some((_, number)) = known.iter().find(|(held, _)| std::ptr::eq(*held, spanner)) {
563            return *number;
564        }
565        let number = known.len() as u32 % 16 + 1;
566        known.push((spanner, number));
567        number
568    }
569}
570
571/// A spanner with the number it is written with and the elements it joins.
572#[derive(Clone)]
573struct Numbered<'a> {
574    spanner: &'a Spanner,
575    id_local: u32,
576    /// Each element joined, or nothing for one standing in no stream.
577    elements: Vec<Option<&'a StreamElement>>,
578}
579
580impl Numbered<'_> {
581    fn is(joined: Option<&Option<&StreamElement>>, element: *const StreamElement) -> bool {
582        joined.is_some_and(|joined| joined.is_some_and(|joined| std::ptr::eq(joined, element)))
583    }
584
585    fn holds(&self, element: *const StreamElement) -> bool {
586        self.elements
587            .iter()
588            .any(|joined| Self::is(Some(joined), element))
589    }
590
591    fn is_first(&self, element: *const StreamElement) -> bool {
592        Self::is(self.elements.first(), element)
593    }
594
595    fn is_last(&self, element: *const StreamElement) -> bool {
596        Self::is(self.elements.last(), element)
597    }
598
599    /// The lowest and the highest note the spanner joins, each as the
600    /// element holding it and its place in that element's chord: music21's
601    /// `noteExtremes`, which takes the first of equal pitches either way.
602    fn note_extremes(&self) -> Option<(Extreme, Extreme)> {
603        let mut notes: Vec<Extreme> = Vec::new();
604        for element in self.elements.iter().flatten() {
605            match element {
606                StreamElement::Note(note) => notes.push((*element, note.pitch().ps())),
607                StreamElement::Chord(chord) => {
608                    for note in chord.notes() {
609                        notes.push((*element, note.pitch().ps()));
610                    }
611                }
612                _ => {}
613            }
614        }
615        let first = *notes.first()?;
616        let (mut lowest, mut highest) = (first, first);
617        for note in notes {
618            if note.1 < lowest.1 {
619                lowest = note;
620            }
621            if note.1 > highest.1 {
622                highest = note;
623            }
624        }
625        Some((lowest, highest))
626    }
627}
628
629/// music21's `MetricModulation.updateByContext`, as its exporter sets it off
630/// by asking each modulation of a part for its two marks: one with a side
631/// that has no number yet takes it from the tempo in force before it -- the
632/// last mark, tempo word or modulation standing earlier in the part, and
633/// none standing where it stands itself.
634fn resolved_modulations(part: &Stream) -> Vec<(*const StreamElement, MetricModulation)> {
635    let mut elements = part.recurse();
636    elements.sort_by(|left, right| left.0.total_cmp(&right.0));
637    // Every tempo so far with where it stands, as the mark it sounds at.
638    let mut tempos: Vec<(FloatType, Option<MetronomeMark>)> = Vec::new();
639    let mut resolved = Vec::new();
640    for (offset, element) in elements {
641        match element {
642            StreamElement::MetronomeMark(mark) => tempos.push((offset, Some(mark.clone()))),
643            StreamElement::TempoText(text) => tempos.push((offset, Some(text.metronome_mark()))),
644            StreamElement::MetricModulation(modulation) => {
645                let previous = tempos
646                    .iter()
647                    .rev()
648                    .find(|(at, _)| *at < offset)
649                    .and_then(|(_, mark)| mark.as_ref());
650                let mut modulation = (**modulation).clone();
651                let unnumbered =
652                    |side: Option<&MetronomeMark>| side.is_some_and(|mark| mark.number().is_none());
653                if unnumbered(modulation.old_metronome()) {
654                    modulation.update_from(previous);
655                }
656                if unnumbered(modulation.new_metronome()) {
657                    modulation.update_from(previous);
658                }
659                tempos.push((offset, modulation.new_metronome().cloned()));
660                resolved.push((element as *const StreamElement, modulation));
661            }
662            _ => {}
663        }
664    }
665    resolved
666}
667
668/// music21's `setIdLocals` over the score's spanners: each kind numbered
669/// one to six and round again -- the number MusicXML pairs a spanner's start
670/// with its stop by -- in the order the score holds them, its own first and
671/// then each part's. Each is resolved to the elements it joins, wherever in
672/// the score they stand.
673fn id_locals<'a>(score: &'a Stream, parts: &[&'a Stream]) -> Vec<Numbered<'a>> {
674    let resolve = |holder: &'a Stream, spanner: &'a Spanner| {
675        let leaves = holder.leaves();
676        spanner
677            .spanned()
678            .iter()
679            .map(|position| {
680                position
681                    .and_then(|position| leaves.get(position))
682                    .map(|(_, element)| *element)
683            })
684            .collect::<Vec<_>>()
685    };
686    let mut held: Vec<(&'a Spanner, Vec<Option<&'a StreamElement>>)> = Vec::new();
687    for spanner in score.spanners() {
688        held.push((spanner, resolve(score, spanner)));
689    }
690    for part in parts {
691        if std::ptr::eq(*part, score) {
692            continue;
693        }
694        for spanner in part.spanners() {
695            held.push((spanner, resolve(part, spanner)));
696        }
697    }
698    let mut counts: Vec<(&'static str, u32)> = Vec::new();
699    held.into_iter()
700        .map(|(spanner, elements)| {
701            let class = spanner.kind().numbering_class();
702            let index = match counts.iter_mut().find(|(known, _)| *known == class) {
703                Some((_, count)) => {
704                    *count += 1;
705                    *count - 1
706                }
707                None => {
708                    counts.push((class, 1));
709                    0
710                }
711            };
712            Numbered {
713                spanner,
714                id_local: index % 6 + 1,
715                elements,
716            }
717        })
718        .collect()
719}
720
721/// Whether a stream holds parts: music21's `hasPartLikeStreams`.
722fn has_part_like_streams(stream: &Stream) -> bool {
723    stream.events().iter().any(|event| {
724        event
725            .element()
726            .as_stream()
727            .is_some_and(|inner| matches!(inner.kind(), StreamKind::Part | StreamKind::PartStaff))
728    })
729}
730
731/// music21's `emptyObject`: "This Page Intentionally Left Blank", a whole
732/// rest in one measure.
733fn empty_score() -> Stream {
734    let mut measure = Stream::with_kind(StreamKind::Measure);
735    measure.push(Rest::new(Duration::whole()));
736    let mut part = Stream::with_kind(StreamKind::Part);
737    part.push(measure);
738    let mut score = Stream::with_kind(StreamKind::Score);
739    score.push(part);
740    let mut metadata = Metadata::new();
741    metadata.add_text("title", "This Page Intentionally Left Blank");
742    score.set_metadata(Some(metadata));
743    score
744}
745
746/// music21's `addDividerComment`.
747fn add_divider_comment(root: &mut Element, comment: &str) {
748    let length = comment.chars().count().min(60);
749    let low = (60 - length) / 2;
750    let high = (60 - length).div_ceil(2);
751    root.push_comment(format!(
752        "{} {comment} {}",
753        "=".repeat(low),
754        "=".repeat(high)
755    ));
756}
757
758/// music21's `setTitles`.
759fn set_titles(root: &mut Element, metadata: Option<&Metadata>, options: &ExportOptions) {
760    let title = metadata.and_then(|md| md.first_text("title"));
761    if let Some(title) = title {
762        root.sub("work").sub_text("work-title", title);
763    }
764    if let Some(number) = metadata.and_then(|md| md.first_text("movementNumber")) {
765        root.sub_text("movement-number", number);
766    }
767    let movement_title = metadata
768        .and_then(|md| md.first_text("movementName"))
769        .or(title)
770        .or(options.default_title.as_deref());
771    if let Some(movement_title) = movement_title {
772        root.sub_text("movement-title", movement_title);
773    }
774}
775
776/// music21's `setIdentification`, with `setEncoding` and
777/// `metadataToMiscellaneous`.
778fn set_identification(root: &mut Element, metadata: Option<&Metadata>, options: &ExportOptions) {
779    let identification = root.sub("identification");
780    let mut found_one = false;
781    if let Some(md) = metadata {
782        for (_, contributor) in md.contributors() {
783            let creator = identification.sub("creator");
784            if let Some(role) = contributor.role() {
785                creator.set("type", role);
786            }
787            creator.set_text(contributor.text());
788            found_one = true;
789        }
790    }
791    if !found_one && let Some(author) = &options.default_author {
792        let creator = identification.sub("creator");
793        creator.set("type", "composer");
794        creator.set_text(author.as_str());
795    }
796    if let Some(md) = metadata {
797        for copyright in md.get("copyright") {
798            let rights = identification.sub("rights");
799            if let Some(role) = copyright.role() {
800                rights.set("type", role);
801            }
802            rights.set_text(copyright.text());
803        }
804    }
805
806    let encoding = identification.sub("encoding");
807    if let Some(date) = &options.encoding_date {
808        encoding.sub_text("encoding-date", date.as_str());
809    }
810    match metadata {
811        Some(md) => {
812            let mut found_music21 = false;
813            for software in md.get("software") {
814                if software.text().contains("music21 v.") {
815                    if found_music21 {
816                        continue;
817                    }
818                    found_music21 = true;
819                }
820                encoding.sub_text("software", software.text());
821            }
822        }
823        None => encoding.sub_text("software", options.software.as_str()),
824    }
825    for element in ["beam", "stem", "accidental"] {
826        let supports = encoding.sub("supports");
827        supports.set("element", element);
828        supports.set("type", "yes");
829    }
830
831    if let Some(md) = metadata {
832        let mut miscellaneous = Element::new("miscellaneous");
833        let (mut skipped_name, mut skipped_number, mut skipped_title) = (false, false, false);
834        for (unique_name, value) in md.all() {
835            if crate::metadata::is_contributor_unique_name(unique_name) {
836                continue;
837            }
838            match unique_name {
839                "software" | "copyright" => continue,
840                "movementName" if !skipped_name => {
841                    skipped_name = true;
842                    continue;
843                }
844                "movementNumber" if !skipped_number => {
845                    skipped_number = true;
846                    continue;
847                }
848                "title" if !skipped_title => {
849                    skipped_title = true;
850                    continue;
851                }
852                _ => {}
853            }
854            let name = namespace_name(unique_name).unwrap_or(unique_name);
855            if name.starts_with("m21FileInfo:") {
856                continue;
857            }
858            let field = miscellaneous.sub("miscellaneous-field");
859            field.set("name", name);
860            field.set_text(value.text());
861        }
862        if miscellaneous.len() > 0 {
863            identification.push(miscellaneous);
864        }
865    }
866}
867
868/// music21's `setDefaults`: the scaling and the page, system and staff
869/// layouts of the score's first score layout, or music21's own scaling
870/// where it has none.
871fn set_defaults(root: &mut Element, score: &Stream) {
872    let first = score
873        .events()
874        .iter()
875        .find_map(|event| match event.element() {
876            StreamElement::Layout(Layout::Score(layout)) => Some(layout),
877            _ => None,
878        });
879    let defaults = root.sub("defaults");
880    let Some(layout) = first else {
881        let scaling = defaults.sub("scaling");
882        scaling.sub_text("millimeters", "7");
883        scaling.sub_text("tenths", "40");
884        return;
885    };
886    if layout.scaling_millimeters.is_some() || layout.scaling_tenths.is_some() {
887        let said = |value: Option<Tenths>| {
888            value.map_or_else(|| "None".to_string(), |value| value.to_string())
889        };
890        let scaling = defaults.sub("scaling");
891        scaling.sub_text("millimeters", said(layout.scaling_millimeters));
892        scaling.sub_text("tenths", said(layout.scaling_tenths));
893    }
894    if let Some(page) = &layout.page_layout {
895        defaults.push(page_layout_element(page));
896    }
897    if let Some(system) = &layout.system_layout {
898        defaults.push(system_layout_element(system));
899    }
900    for staff in &layout.staff_layouts {
901        defaults.push(staff_layout_element(staff));
902    }
903}
904
905/// music21's `setPrintStyleAlign`: where text stands, its font and colour,
906/// and how it is aligned, each as an attribute where the style says it.
907pub(crate) fn set_print_style_align(element: &mut Element, style: &TextStyle) -> Result<()> {
908    for (name, value) in [
909        ("default-x", &style.absolute_x),
910        ("default-y", &style.absolute_y),
911        ("relative-x", &style.relative_x),
912        ("relative-y", &style.relative_y),
913    ] {
914        if let Some(value) = value {
915            element.set(name, value.to_string());
916        }
917    }
918    if let Some(font_style) = style.font_style() {
919        element.set("font-style", font_style);
920    }
921    if let Some(size) = &style.font_size {
922        element.set("font-size", size.to_string());
923    }
924    if let Some(weight) = style.font_weight() {
925        element.set("font-weight", weight);
926    }
927    // MusicXML's font-style has no bold, which music21 writes as a weight.
928    match style.font_style() {
929        Some("bold") => {
930            element.set("font-weight", "bold");
931            element.remove_attribute("font-style");
932        }
933        Some("bolditalic") => {
934            element.set("font-weight", "bold");
935            element.set("font-style", "italic");
936        }
937        _ => {}
938    }
939    if let Some(family) = style
940        .font_family
941        .as_ref()
942        .filter(|family| !family.is_empty())
943    {
944        element.set("font-family", family.join(","));
945    }
946    if let Some(color) = &style.color {
947        element.set("color", normalize_color(color)?);
948    }
949    if let Some(align) = style.align_vertical() {
950        element.set("valign", align);
951    }
952    if let Some(align) = style.align_horizontal() {
953        element.set("halign", align);
954    }
955    Ok(())
956}
957
958/// music21's `textBoxToXmlCredit`: the box's text as the words of a credit
959/// on its page, drawn as its style says.
960fn credit_element(text: &TextBox) -> Result<Element> {
961    let mut credit = Element::new("credit");
962    credit.set("page", text.page.to_string());
963    let mut words = Element::new("credit-words");
964    if text.content.contains('\n') {
965        words.set("xml:space", "preserve");
966    }
967    words.set_text(text.content.clone());
968    set_print_style_align(&mut words, &text.style)?;
969    if let Some(justify) = text.style.justify() {
970        words.set("justify", justify);
971    }
972    credit.push(words);
973    Ok(credit)
974}
975
976/// music21's `_setTagTextFromAttribute`: a child with the value, where
977/// there is one.
978fn sub_tenths(element: &mut Element, tag: &'static str, value: Option<Tenths>) {
979    if let Some(value) = value {
980        element.sub_text(tag, value.to_string());
981    }
982}
983
984/// music21's `pageLayoutToXmlPageLayout`.
985fn page_layout_element(page: &PageLayout) -> Element {
986    let mut element = Element::new("page-layout");
987    sub_tenths(&mut element, "page-height", page.page_height);
988    sub_tenths(&mut element, "page-width", page.page_width);
989    let mut margins = Element::new("page-margins");
990    sub_tenths(&mut margins, "left-margin", page.left_margin);
991    sub_tenths(&mut margins, "right-margin", page.right_margin);
992    sub_tenths(&mut margins, "top-margin", page.top_margin);
993    sub_tenths(&mut margins, "bottom-margin", page.bottom_margin);
994    if margins.len() > 0 {
995        element.push(margins);
996    }
997    element
998}
999
1000/// music21's `systemLayoutToXmlSystemLayout`.
1001fn system_layout_element(system: &SystemLayout) -> Element {
1002    let mut element = Element::new("system-layout");
1003    let mut margins = Element::new("system-margins");
1004    sub_tenths(&mut margins, "top-margin", system.top_margin);
1005    sub_tenths(&mut margins, "bottom-margin", system.bottom_margin);
1006    sub_tenths(&mut margins, "left-margin", system.left_margin);
1007    sub_tenths(&mut margins, "right-margin", system.right_margin);
1008    if margins.len() > 0 {
1009        element.push(margins);
1010    }
1011    sub_tenths(&mut element, "system-distance", system.distance);
1012    sub_tenths(&mut element, "top-system-distance", system.top_distance);
1013    element
1014}
1015
1016/// music21's `staffLayoutToXmlStaffLayout`.
1017fn staff_layout_element(staff: &StaffLayout) -> Element {
1018    let mut element = Element::new("staff-layout");
1019    sub_tenths(&mut element, "staff-distance", staff.distance);
1020    if let Some(number) = staff.staff_number {
1021        element.set("number", number.to_string());
1022    }
1023    element
1024}
1025
1026/// music21's `staffLayoutToXmlStaffDetails`: whether the staff is printed,
1027/// what it is for where that is not ordinary, and its lines.
1028fn staff_details_element(staff: &StaffLayout) -> Element {
1029    let mut element = Element::new("staff-details");
1030    element.set(
1031        "print-object",
1032        if staff.hidden == Some(true) {
1033            "no"
1034        } else {
1035            "yes"
1036        },
1037    );
1038    if !matches!(staff.staff_type, StaffType::Regular | StaffType::Other) {
1039        element.sub_text("staff-type", staff.staff_type.as_str());
1040    }
1041    if let Some(lines) = staff.staff_lines {
1042        element.sub_text("staff-lines", lines.to_string());
1043    }
1044    element
1045}
1046
1047/// music21's `yesNo` of an attribute it sets only where it has a value.
1048fn set_yes_no(element: &mut Element, name: &'static str, value: Option<bool>) {
1049    if let Some(value) = value {
1050        element.set(name, if value { "yes" } else { "no" });
1051    }
1052}
1053
1054// ------------------------------------------------------------------- part
1055
1056/// What the parts of one score share while they are written: the MIDI
1057/// channels, instrument ids and part ids already given out.
1058#[derive(Default)]
1059struct Shared {
1060    midi_channels: Vec<Option<u8>>,
1061    instrument_ids: Vec<String>,
1062    part_ids: Vec<String>,
1063}
1064
1065struct ExportedPart {
1066    part: Element,
1067    score_part: Element,
1068}
1069
1070/// An instrument with the offset it starts at within its part.
1071struct PlacedInstrument {
1072    offset: FloatType,
1073    instrument: Instrument,
1074}
1075
1076/// music21's `PartExporter`.
1077struct PartExporter<'a> {
1078    stream: &'a Stream,
1079    instruments: Vec<PlacedInstrument>,
1080}
1081
1082impl<'a> PartExporter<'a> {
1083    /// music21's `instrumentSetup`.
1084    fn new(stream: &'a Stream, shared: &mut Shared, role: &StaffRole<'_>) -> Result<Self> {
1085        let mut instruments = instruments_of(stream);
1086        if instruments.is_empty() {
1087            let mut default = Instrument::new();
1088            default.set_part_name(Some(String::new()));
1089            instruments.push(PlacedInstrument {
1090                offset: 0.0,
1091                instrument: default,
1092            });
1093        }
1094
1095        // music21 gives a part whose id is taken, or who has none, a random
1096        // one; the part's own id, then the first free `P<n>`, is used here.
1097        let first = &mut instruments[0].instrument;
1098        let taken = |id: &str| shared.part_ids.iter().any(|known| known == id);
1099        if first.part_id().is_none_or(taken) {
1100            let fresh = stream
1101                .id()
1102                .filter(|id| !taken(id))
1103                .map(str::to_string)
1104                .unwrap_or_else(|| {
1105                    (1..)
1106                        .map(|number| format!("P{number}"))
1107                        .find(|candidate| !taken(candidate))
1108                        .expect("there is always an unused number")
1109                });
1110            first.set_part_id(Some(fresh));
1111        }
1112        let part_id = first.part_id().unwrap_or_default().to_string();
1113
1114        // music21's `mergeInstrumentStreamPartStaffAware`: the first staff of
1115        // a group plays the instruments of every staff in it, and the staves
1116        // after it leave theirs to the first.
1117        if role.later {
1118            return Ok(Self {
1119                stream,
1120                instruments,
1121            });
1122        }
1123        for follower in &role.followers {
1124            let mut theirs = instruments_of(follower);
1125            if theirs.is_empty() {
1126                let mut default = Instrument::new();
1127                default.set_part_name(Some(String::new()));
1128                theirs.push(PlacedInstrument {
1129                    offset: 0.0,
1130                    instrument: default,
1131                });
1132            }
1133            instruments.extend(theirs);
1134            instruments = deduplicate(instruments);
1135        }
1136
1137        let mut seen: Vec<String> = Vec::new();
1138        for (index, placed) in instruments.iter_mut().enumerate() {
1139            let instrument = &mut placed.instrument;
1140            if seen.iter().any(|kind| kind == instrument.kind()) {
1141                continue;
1142            }
1143            seen.push(instrument.kind().to_string());
1144            if instrument
1145                .midi_channel()
1146                .is_none_or(|channel| shared.midi_channels.contains(&Some(channel)))
1147            {
1148                let used: Vec<u8> = shared.midi_channels.iter().flatten().copied().collect();
1149                // music21 warns and carries on when it runs out of channels.
1150                let _ = instrument.auto_assign_midi_channel(&used, 16);
1151            }
1152            shared.midi_channels.push(instrument.midi_channel());
1153            let duplicate = instrument
1154                .instrument_id()
1155                .is_none_or(|id| shared.instrument_ids.iter().any(|known| known == id));
1156            if duplicate {
1157                let fresh = (1..)
1158                    .map(|number| format!("{part_id}-I{number}"))
1159                    .find(|candidate| !shared.instrument_ids.contains(candidate))
1160                    .expect("there is always an unused number");
1161                instrument.set_instrument_id(Some(fresh));
1162            }
1163            shared
1164                .instrument_ids
1165                .push(instrument.instrument_id().unwrap_or_default().to_string());
1166            if index == 0 {
1167                shared.part_ids.push(part_id.clone());
1168            }
1169        }
1170        Ok(Self {
1171            stream,
1172            instruments,
1173        })
1174    }
1175
1176    fn part_id(&self) -> &str {
1177        self.instruments[0].instrument.part_id().unwrap_or_default()
1178    }
1179
1180    /// music21's `PartExporter.parse`, with `makeNotation=False`.
1181    fn parse(
1182        self,
1183        spanners: Vec<Numbered<'a>>,
1184        previous: Option<&'a Stream>,
1185        voice_bases: &'a std::collections::HashMap<usize, u32>,
1186    ) -> Result<ExportedPart> {
1187        let measures: Vec<(FloatType, &Stream)> = self
1188            .stream
1189            .events()
1190            .iter()
1191            .filter_map(|event| {
1192                event
1193                    .element()
1194                    .as_stream()
1195                    .filter(|stream| stream.kind() == StreamKind::Measure)
1196                    .map(|stream| (event.offset(), stream))
1197            })
1198            .collect();
1199        if measures.is_empty() {
1200            return Err(export_error(
1201                "Cannot export with makeNotation=False if there are no measures",
1202            ));
1203        }
1204
1205        let mut part = Element::new("part");
1206        part.set("id", self.part_id());
1207        let mut context = PartContext {
1208            instruments: &self.instruments,
1209            own_instruments: self
1210                .stream
1211                .recurse()
1212                .into_iter()
1213                .filter_map(|(offset, element)| match element {
1214                    StreamElement::Instrument(instrument) => Some((offset, &**instrument)),
1215                    _ => None,
1216                })
1217                .collect(),
1218            own_clefs: self
1219                .stream
1220                .recurse()
1221                .into_iter()
1222                .filter_map(|(offset, element)| match element {
1223                    StreamElement::Clef(clef)
1224                        if clef.places_pitches() && clef.lowest_line().is_some() =>
1225                    {
1226                        Some((offset, clef))
1227                    }
1228                    _ => None,
1229                })
1230                .collect(),
1231            meter: None,
1232            last_divisions: None,
1233            spanners,
1234            clef: None,
1235            previous,
1236            voice_bases,
1237            modulations: resolved_modulations(self.stream),
1238        };
1239        for (offset, measure) in measures {
1240            add_divider_comment(&mut part, &format!("Measure {}", measure.number()));
1241            part.push(MeasureExporter::new(measure, offset, &mut context).parse()?);
1242        }
1243        Ok(ExportedPart {
1244            score_part: self.score_part(),
1245            part,
1246        })
1247    }
1248
1249    /// music21's `getXmlScorePart`.
1250    fn score_part(&self) -> Element {
1251        let mut score_part = Element::new("score-part");
1252        score_part.set("id", self.part_id());
1253        let name = score_part.sub("part-name");
1254        name.set_text(part_name(self.stream).unwrap_or_default());
1255        if self.stream.name_hidden() {
1256            name.set("print-object", "no");
1257        }
1258        if let Some(abbreviation) = part_abbreviation(self.stream) {
1259            let element = score_part.sub("part-abbreviation");
1260            element.set_text(abbreviation);
1261            if self.stream.abbreviation_hidden() {
1262                element.set("print-object", "no");
1263            }
1264        }
1265        let mut seen: Vec<&str> = Vec::new();
1266        for placed in &self.instruments {
1267            let instrument = &placed.instrument;
1268            if seen.contains(&instrument.kind()) {
1269                continue;
1270            }
1271            if instrument.name().is_some()
1272                || instrument.abbreviation().is_some()
1273                || instrument.midi_program().is_some()
1274            {
1275                score_part.push(score_instrument(instrument));
1276                seen.push(instrument.kind());
1277            }
1278        }
1279        let mut seen: Vec<&str> = Vec::new();
1280        for placed in &self.instruments {
1281            let instrument = &placed.instrument;
1282            if seen.contains(&instrument.kind()) {
1283                continue;
1284            }
1285            if instrument.midi_program().is_some() || instrument.is_a("UnpitchedPercussion") {
1286                score_part.push(midi_instrument(instrument));
1287                seen.push(instrument.kind());
1288            }
1289        }
1290        score_part
1291    }
1292}
1293
1294/// Every instrument anywhere in a part, with where it starts.
1295fn instruments_of(stream: &Stream) -> Vec<PlacedInstrument> {
1296    stream
1297        .recurse()
1298        .into_iter()
1299        .filter_map(|(offset, element)| match element {
1300            StreamElement::Instrument(instrument) => Some(PlacedInstrument {
1301                offset,
1302                instrument: (**instrument).clone(),
1303            }),
1304            _ => None,
1305        })
1306        .collect()
1307}
1308
1309/// music21's `instrument.deduplicate`: at each offset where the instruments
1310/// agree on their names, where each says the same or nothing, the names are
1311/// shared out, one instrument of each kind is kept, and a bare `Instrument`
1312/// gives way to one of a kind.
1313fn deduplicate(mut instruments: Vec<PlacedInstrument>) -> Vec<PlacedInstrument> {
1314    instruments.sort_by(|left, right| left.offset.total_cmp(&right.offset));
1315    let mut out: Vec<PlacedInstrument> = Vec::new();
1316    let mut start = 0;
1317    while start < instruments.len() {
1318        let offset = instruments[start].offset;
1319        let end = instruments[start..]
1320            .iter()
1321            .position(|placed| placed.offset != offset)
1322            .map_or(instruments.len(), |length| start + length);
1323        let group = &instruments[start..end];
1324        let agreed = |read: fn(&Instrument) -> Option<&str>| -> Option<Option<String>> {
1325            let mut said: Option<&str> = None;
1326            for placed in group {
1327                match (said, read(&placed.instrument)) {
1328                    (_, None) => {}
1329                    (None, Some(name)) => said = Some(name),
1330                    (Some(known), Some(name)) if known == name => {}
1331                    _ => return None,
1332                }
1333            }
1334            Some(said.map(str::to_string))
1335        };
1336        match (agreed(Instrument::part_name), agreed(Instrument::name)) {
1337            (Some(part_name), Some(name)) if group.len() > 1 => {
1338                let has_specific = group
1339                    .iter()
1340                    .any(|placed| placed.instrument.kind() != "Instrument");
1341                let mut kept: Vec<PlacedInstrument> = Vec::new();
1342                for placed in group {
1343                    let kind = placed.instrument.kind();
1344                    if (has_specific && kind == "Instrument")
1345                        || kept.iter().any(|known| known.instrument.kind() == kind)
1346                    {
1347                        continue;
1348                    }
1349                    let mut instrument = placed.instrument.clone();
1350                    instrument.set_part_name(part_name.clone());
1351                    instrument.set_name(name.clone());
1352                    kept.push(PlacedInstrument { offset, instrument });
1353                }
1354                out.extend(kept);
1355            }
1356            _ => out.extend(group.iter().map(|placed| PlacedInstrument {
1357                offset,
1358                instrument: placed.instrument.clone(),
1359            })),
1360        }
1361        start = end;
1362    }
1363    out
1364}
1365
1366/// music21's `Part.partName`: the part's own, else the first instrument
1367/// held directly that names one.
1368fn part_name(part: &Stream) -> Option<&str> {
1369    part.name().or_else(|| {
1370        held_instruments(part)
1371            .into_iter()
1372            .find_map(|instrument| instrument.part_name().or(instrument.name()))
1373    })
1374}
1375
1376/// Every instrument a part holds, those inside its measures too.
1377fn held_instruments(part: &Stream) -> Vec<&Instrument> {
1378    part.recurse()
1379        .into_iter()
1380        .filter_map(|(_, element)| match element {
1381            StreamElement::Instrument(instrument) => Some(&**instrument),
1382            _ => None,
1383        })
1384        .collect()
1385}
1386
1387/// music21's `Part.partAbbreviation`, read the same way.
1388fn part_abbreviation(part: &Stream) -> Option<&str> {
1389    part.abbreviation().or_else(|| {
1390        held_instruments(part)
1391            .into_iter()
1392            .find_map(|instrument| instrument.part_abbreviation().or(instrument.abbreviation()))
1393    })
1394}
1395
1396/// music21's `instrumentToXmlScoreInstrument`.
1397fn score_instrument(instrument: &Instrument) -> Element {
1398    let mut element = Element::new("score-instrument");
1399    element.set("id", instrument.instrument_id().unwrap_or_default());
1400    element.sub_text(
1401        "instrument-name",
1402        instrument.name().unwrap_or("None").to_string(),
1403    );
1404    if let Some(abbreviation) = instrument.abbreviation() {
1405        element.sub_text("instrument-abbreviation", abbreviation);
1406    }
1407    element
1408}
1409
1410/// music21's `instrumentToXmlMidiInstrument`.
1411fn midi_instrument(instrument: &Instrument) -> Element {
1412    let mut element = Element::new("midi-instrument");
1413    element.set("id", instrument.instrument_id().unwrap_or_default());
1414    let channel = instrument.midi_channel().unwrap_or(0);
1415    element.sub_text("midi-channel", (u32::from(channel) + 1).to_string());
1416    if let Some(program) = instrument.midi_program() {
1417        element.sub_text("midi-program", (u32::from(program) + 1).to_string());
1418    }
1419    if instrument.is_a("UnpitchedPercussion")
1420        && let Some(pitch) = instrument.percussion_pitch()
1421    {
1422        element.sub_text("midi-unpitched", (u32::from(pitch) + 1).to_string());
1423    }
1424    element
1425}
1426
1427/// What a measure needs to know of the part around it.
1428struct PartContext<'a> {
1429    instruments: &'a [PlacedInstrument],
1430    /// The instruments standing in this staff itself, in the order it holds
1431    /// them, which is where a note looks for the one that plays it.
1432    own_instruments: Vec<(FloatType, &'a Instrument)>,
1433    /// The clefs placing pitches that stand in this staff, in order.
1434    own_clefs: Vec<(FloatType, &'a Clef)>,
1435    /// The metre in force where the measure being written starts.
1436    meter: Option<TimeSignature>,
1437    last_divisions: Option<FloatType>,
1438    /// The score's spanners, each with the number it is written with and
1439    /// the elements it joins.
1440    spanners: Vec<Numbered<'a>>,
1441    /// The part's metric modulations as they are once each has taken its
1442    /// numbers from the tempo in force before it.
1443    modulations: Vec<(*const StreamElement, MetricModulation)>,
1444    /// The clef in force where the writer has got to.
1445    clef: Option<Clef>,
1446    /// The staff before this one where it is one of several written as one
1447    /// part: a key or meter that staff already says is not said again.
1448    previous: Option<&'a Stream>,
1449    /// Where the voices with no id of their own start numbering, measure by
1450    /// measure, in a staff written with others.
1451    voice_bases: &'a std::collections::HashMap<usize, u32>,
1452}
1453
1454// ---------------------------------------------------------------- measure
1455
1456/// music21's `MeasureExporter`.
1457struct MeasureExporter<'a, 'b> {
1458    stream: &'a Stream,
1459    /// Where the measure starts within its part.
1460    start: FloatType,
1461    context: &'a mut PartContext<'b>,
1462    root: Element,
1463    offset_in_measure: FloatType,
1464    current_voice: Option<String>,
1465    next_free_voice: u32,
1466    arpeggios: ArpeggioNumbers,
1467    /// Where in the measure the element being written stands.
1468    element_offset: FloatType,
1469}
1470
1471impl<'a, 'b> MeasureExporter<'a, 'b> {
1472    fn new(stream: &'a Stream, start: FloatType, context: &'a mut PartContext<'b>) -> Self {
1473        let next_free_voice = context
1474            .voice_bases
1475            .get(&(stream as *const Stream as usize))
1476            .map_or(1, |base| base + 1);
1477        Self {
1478            stream,
1479            start,
1480            context,
1481            root: Element::new("measure"),
1482            offset_in_measure: 0.0,
1483            current_voice: None,
1484            next_free_voice,
1485            arpeggios: ArpeggioNumbers::default(),
1486            element_offset: 0.0,
1487        }
1488    }
1489
1490    /// music21's `_matchesPreviousPartStaffInGroup`: whether the staff above
1491    /// this one says the same at the start of its measure of this number.
1492    fn matches_previous_staff(
1493        &self,
1494        element: &StreamElement,
1495        pick: impl Fn(&StreamElement) -> bool,
1496        same: impl Fn(&StreamElement, &StreamElement) -> bool,
1497    ) -> bool {
1498        let Some(previous) = self.context.previous else {
1499            return false;
1500        };
1501        let Some(measure) = previous
1502            .measures()
1503            .into_iter()
1504            .find(|measure| measure.number() == self.stream.number())
1505        else {
1506            return false;
1507        };
1508        measure
1509            .events()
1510            .iter()
1511            .filter(|event| event.offset() == 0.0)
1512            .map(|event| event.element())
1513            .find(|candidate| pick(candidate))
1514            .is_some_and(|theirs| same(element, theirs))
1515    }
1516
1517    /// music21's `MeasureExporter.parse`.
1518    fn parse(mut self) -> Result<Element> {
1519        let transposition = self.transposition();
1520        // setMxAttributes
1521        self.root.set("number", self.stream.number_with_suffix());
1522        self.root.set(
1523            "implicit",
1524            if self.stream.number_hidden() {
1525                "yes"
1526            } else {
1527                "no"
1528            },
1529        );
1530        self.set_print();
1531        self.set_attributes_for_start_of_measure(transposition.as_ref())?;
1532        // music21's `setLeftBarline` and `setRightBarline`: a barline where
1533        // the measure has one, or where an ending opens or closes on it.
1534        let ending = self.stream.ending();
1535        let opens = ending.filter(|ending| ending.starts());
1536        if self.stream.left_barline().is_some() || opens.is_some() {
1537            let barline = barline_element(
1538                self.stream.left_barline(),
1539                "left",
1540                opens.map(|ending| (ending, "start")),
1541            );
1542            self.root.push(barline);
1543        }
1544        self.main_elements_parse()?;
1545        let closes = ending.filter(|ending| ending.stops());
1546        if self.stream.right_barline().is_some() || closes.is_some() {
1547            let barline = barline_element(
1548                self.stream.right_barline(),
1549                "right",
1550                closes.map(|ending| (ending, "stop")),
1551            );
1552            self.root.push(barline);
1553        }
1554        if let Some(meter) = last_of(self.stream, |element| match element {
1555            StreamElement::TimeSignature(meter) => Some(meter.clone()),
1556            _ => None,
1557        }) {
1558            self.context.meter = Some(meter);
1559        }
1560        Ok(self.root)
1561    }
1562
1563    /// music21's `setTranspose`: the transposition of an instrument starting
1564    /// within this measure.
1565    fn transposition(&self) -> Option<Interval> {
1566        let end = self.start + self.stream.end_offset();
1567        self.context
1568            .instruments
1569            .iter()
1570            .find(|placed| {
1571                placed.offset >= self.start && (placed.offset < end || placed.offset == self.start)
1572            })
1573            .and_then(|placed| placed.instrument.transposition().cloned())
1574    }
1575
1576    /// music21's `setMxPrint`: a `<print>` from the measure's first page,
1577    /// system and staff layouts, where it has any.
1578    fn set_print(&mut self) {
1579        let layouts: Vec<&Layout> = self
1580            .stream
1581            .events()
1582            .iter()
1583            .filter_map(|event| match event.element() {
1584                StreamElement::Layout(layout) => Some(layout),
1585                _ => None,
1586            })
1587            .collect();
1588        let mut print: Option<Element> = None;
1589        if let Some(page) = layouts.iter().find_map(|layout| match layout {
1590            Layout::Page(page) => Some(page),
1591            _ => None,
1592        }) {
1593            let mut element = Element::new("print");
1594            set_yes_no(&mut element, "new-page", page.is_new);
1595            if let Some(number) = page.page_number {
1596                element.set("page-number", number.to_string());
1597            }
1598            let laid = page_layout_element(page);
1599            if laid.len() > 0 {
1600                element.push(laid);
1601            }
1602            print = Some(element);
1603        }
1604        if let Some(system) = layouts.iter().find_map(|layout| match layout {
1605            Layout::System(system) => Some(system),
1606            _ => None,
1607        }) {
1608            let element = print.get_or_insert_with(|| Element::new("print"));
1609            set_yes_no(element, "new-system", system.is_new);
1610            let laid = system_layout_element(system);
1611            if laid.len() > 0 {
1612                element.push(laid);
1613            }
1614        }
1615        if let Some(staff) = layouts.iter().find_map(|layout| match layout {
1616            Layout::Staff(staff) => Some(staff),
1617            _ => None,
1618        }) {
1619            print
1620                .get_or_insert_with(|| Element::new("print"))
1621                .push(staff_layout_element(staff));
1622        }
1623        // music21 says how measures are numbered only in a measure it lays
1624        // out.
1625        if !layouts.is_empty()
1626            && let Some(numbering) = self.stream.measure_numbering()
1627        {
1628            print
1629                .get_or_insert_with(|| Element::new("print"))
1630                .sub_text("measure-numbering", numbering.to_string());
1631        }
1632        if let Some(print) = print {
1633            self.root.push(print);
1634        }
1635    }
1636
1637    /// music21's `setMxAttributesObjectForStartOfMeasure`.
1638    fn set_attributes_for_start_of_measure(
1639        &mut self,
1640        transposition: Option<&Interval>,
1641    ) -> Result<()> {
1642        let mut attributes = Element::new("attributes");
1643        if self.context.last_divisions != Some(DIVISIONS_PER_QUARTER) {
1644            attributes.sub_text("divisions", format_int(DIVISIONS_PER_QUARTER));
1645            self.context.last_divisions = Some(DIVISIONS_PER_QUARTER);
1646        }
1647        for event in self
1648            .stream
1649            .events()
1650            .iter()
1651            .filter(|event| event.offset() == 0.0)
1652        {
1653            if let Some(key) = key_element(event.element()) {
1654                let is_key = |element: &StreamElement| {
1655                    matches!(
1656                        element,
1657                        StreamElement::Key(_) | StreamElement::KeySignature(_)
1658                    )
1659                };
1660                if !self.matches_previous_staff(event.element(), is_key, same_key) {
1661                    attributes.push(key?);
1662                }
1663                break;
1664            }
1665        }
1666        for event in self
1667            .stream
1668            .events()
1669            .iter()
1670            .filter(|event| event.offset() == 0.0)
1671        {
1672            if let StreamElement::TimeSignature(meter) = event.element() {
1673                let is_meter =
1674                    |element: &StreamElement| matches!(element, StreamElement::TimeSignature(_));
1675                if !self.matches_previous_staff(event.element(), is_meter, same_meter) {
1676                    attributes.push(time_signature_element(meter)?);
1677                }
1678                self.context.meter = Some(meter.clone());
1679                break;
1680            }
1681        }
1682        for event in self
1683            .stream
1684            .events()
1685            .iter()
1686            .filter(|event| event.offset() == 0.0)
1687        {
1688            if let StreamElement::Clef(clef) = event.element() {
1689                attributes.push(clef_element(clef)?);
1690                break;
1691            }
1692        }
1693        if let Some(staff) = self
1694            .stream
1695            .events()
1696            .iter()
1697            .find_map(|event| match event.element() {
1698                StreamElement::Layout(Layout::Staff(staff)) => Some(staff),
1699                _ => None,
1700            })
1701        {
1702            attributes.push(staff_details_element(staff));
1703        }
1704        if let Some(interval) = transposition {
1705            attributes.push(transpose_element(interval));
1706        }
1707        if attributes.len() > 0 || attributes.has_attributes() {
1708            self.root.push(attributes);
1709        }
1710        Ok(())
1711    }
1712
1713    /// music21's `mainElementsParse`.
1714    fn main_elements_parse(&mut self) -> Result<()> {
1715        let voices: Vec<&Stream> = self.stream.voices();
1716        if voices.is_empty() {
1717            return self.parse_flat_elements(self.stream, false, false);
1718        }
1719        self.parse_flat_elements(self.stream, false, true)?;
1720        let count = voices.len();
1721        for (index, voice) in voices.into_iter().enumerate() {
1722            self.parse_flat_elements(voice, true, index + 1 != count)?;
1723        }
1724        Ok(())
1725    }
1726
1727    /// music21's `parseFlatElements`: the elements of a measure or voice,
1728    /// grouped by offset, marks before notes within each group.
1729    fn parse_flat_elements(
1730        &mut self,
1731        stream: &Stream,
1732        is_voice: bool,
1733        backup_afterwards: bool,
1734    ) -> Result<()> {
1735        self.offset_in_measure = 0.0;
1736        self.current_voice = if is_voice {
1737            Some(match stream.id() {
1738                Some(id) => id.to_string(),
1739                None => {
1740                    let number = self.next_free_voice;
1741                    self.next_free_voice += 1;
1742                    number.to_string()
1743                }
1744            })
1745        } else {
1746            None
1747        };
1748
1749        let events: Vec<(FloatType, &StreamElement)> = stream
1750            .events()
1751            .iter()
1752            .filter(|event| !matches!(event.element(), StreamElement::Stream(inner) if inner.kind() == StreamKind::Voice))
1753            .map(|event| (event.offset(), event.element()))
1754            .collect();
1755        let mut index = 0;
1756        while index < events.len() {
1757            let group_offset = events[index].0;
1758            let mut end = index;
1759            while end < events.len() && events[end].0 == group_offset {
1760                end += 1;
1761            }
1762            let group = &events[index..end];
1763            let forward = group_offset - self.offset_in_measure;
1764            if forward > 0.0
1765                && group.iter().any(|(_, element)| {
1766                    is_general_note(element) || matches!(element, StreamElement::Clef(_))
1767                })
1768            {
1769                self.move_forward(forward);
1770            }
1771            let mut notes_for_later = Vec::new();
1772            for &(offset, element) in group {
1773                if is_written_as_note(element) {
1774                    notes_for_later.push((offset, element));
1775                } else {
1776                    self.parse_one_element(offset, element)?;
1777                }
1778            }
1779            for (offset, element) in notes_for_later {
1780                // An unprinted rest of a length no note value writes is
1781                // left as a gap, which the next element is moved past.
1782                if let StreamElement::Rest(rest) = element
1783                    && rest.hidden()
1784                    && rest.quarter_length() != 0.0
1785                    && rest.duration().components().is_empty()
1786                {
1787                    continue;
1788                }
1789                self.parse_one_element(offset, element)?;
1790            }
1791            index = end;
1792        }
1793
1794        if backup_afterwards && self.offset_in_measure != 0.0 {
1795            self.move_backward(self.offset_in_measure);
1796        }
1797        self.current_voice = None;
1798        Ok(())
1799    }
1800
1801    /// music21's `moveForward`.
1802    fn move_forward(&mut self, by: FloatType) {
1803        let amount = divisions(by);
1804        if amount != 0 {
1805            self.root
1806                .sub("forward")
1807                .sub_text("duration", amount.to_string());
1808            self.offset_in_measure += by;
1809        }
1810    }
1811
1812    /// music21's `moveBackward`.
1813    fn move_backward(&mut self, by: FloatType) {
1814        let amount = divisions(by);
1815        if amount != 0 {
1816            self.root
1817                .sub("backup")
1818                .sub_text("duration", amount.to_string());
1819            self.offset_in_measure -= by;
1820        }
1821    }
1822
1823    /// music21's `parseOneElement`.
1824    fn parse_one_element(&mut self, offset: FloatType, element: &StreamElement) -> Result<()> {
1825        let position = element as *const StreamElement;
1826        // Asked before the writer moves past the element, which is where a
1827        // pedal's resumed line counts its offset from.
1828        let (before, after) =
1829            related_spanners(position, &self.context.spanners, self.offset_in_measure);
1830        if is_written_as_note(element) {
1831            self.offset_in_measure += element.quarter_length();
1832        }
1833        for direction in before {
1834            self.root.push(direction);
1835        }
1836        self.write_element(offset, element, position)?;
1837        for direction in after {
1838            self.root.push(direction);
1839        }
1840        Ok(())
1841    }
1842
1843    /// The element itself, as `parseOneElement` writes it between the
1844    /// directions its spanners open and close with.
1845    fn write_element(
1846        &mut self,
1847        offset: FloatType,
1848        element: &StreamElement,
1849        position: *const StreamElement,
1850    ) -> Result<()> {
1851        self.element_offset = offset;
1852        match element {
1853            StreamElement::Note(note) => {
1854                let mxnote = self.note_element(note, 0, None, Some(position))?;
1855                self.root.push(mxnote);
1856            }
1857            // music21 sorts a chord in place before writing it.
1858            StreamElement::Chord(chord) => {
1859                self.chord_elements(&written_order(chord), Some(position))?
1860            }
1861            StreamElement::Unpitched(stroke) => {
1862                let mxnote =
1863                    self.written_note_element(stroke.written(), 0, None, Some(position), true)?;
1864                self.root.push(mxnote);
1865            }
1866            StreamElement::PercussionChord(chord) => {
1867                // Unlike a chord of pitches, its members stay in the order
1868                // they were given.
1869                let written = chord.written();
1870                for (index, note) in written.notes().iter().enumerate() {
1871                    let element = self.written_note_element(
1872                        note,
1873                        index,
1874                        Some(written),
1875                        Some(position),
1876                        chord.is_unpitched(index),
1877                    )?;
1878                    self.root.push(element);
1879                }
1880            }
1881            StreamElement::Rest(rest) => {
1882                let mxnote = self.rest_element(rest, offset, Some(position))?;
1883                self.root.push(mxnote);
1884            }
1885            StreamElement::Dynamic(dynamic) => self.dynamic_element(dynamic, offset),
1886            StreamElement::MetronomeMark(mark) => self.tempo_elements(mark, offset)?,
1887            StreamElement::MetricModulation(modulation) => {
1888                let resolved = self
1889                    .context
1890                    .modulations
1891                    .iter()
1892                    .find(|(held, _)| std::ptr::eq(*held, position))
1893                    .map_or(&**modulation, |(_, resolved)| resolved);
1894                let direction = self.metric_modulation_element(resolved, offset)?;
1895                self.root.push(direction);
1896            }
1897            StreamElement::TempoText(text) => {
1898                let direction = self.tempo_words(text.text(), text.placement(), offset);
1899                self.root.push(direction);
1900            }
1901            StreamElement::TextExpression(text) => {
1902                // music21's `textExpressionToXml`.
1903                let words = Element::with_text("words", text.content());
1904                let direction = self.direction(words, text.placement(), offset, false);
1905                self.root.push(direction);
1906            }
1907            StreamElement::RepeatExpression(mark) => {
1908                let direction = self.repeat_expression_element(mark, offset);
1909                self.root.push(direction);
1910            }
1911            StreamElement::KeySignature(_) | StreamElement::Key(_) => {
1912                if let Some(key) = key_element(element) {
1913                    let key = key?;
1914                    self.wrap_in_attributes(key);
1915                }
1916            }
1917            StreamElement::TimeSignature(meter) => {
1918                let meter = time_signature_element(meter)?;
1919                self.wrap_in_attributes(meter);
1920            }
1921            StreamElement::Clef(clef) => {
1922                self.context.clef = Some(clef.clone());
1923                let clef = clef_element(clef)?;
1924                self.wrap_in_attributes(clef);
1925            }
1926            // music21 writes nothing for an instrument inside a measure: it
1927            // is read into the part list instead.
1928            StreamElement::Instrument(_) => {}
1929            StreamElement::ChordSymbol(symbol) => {
1930                let harmony = self.harmony_element(symbol, offset)?;
1931                self.root.push(harmony);
1932            }
1933            StreamElement::Stream(inner) => {
1934                return Err(export_error(format!(
1935                    "a {} inside a measure cannot be written",
1936                    inner.kind()
1937                )));
1938            }
1939            StreamElement::PedalObject(object) => self.pedal_object_elements(object, position),
1940            StreamElement::RehearsalMark(mark) => {
1941                // music21's `rehearsalMarkToXml`: a mark is centred on where
1942                // it stands, as its class starts every mark out.
1943                let mut rehearsal = Element::with_text("rehearsal", mark.content());
1944                if let Some(enclosure) = mark.enclosure() {
1945                    rehearsal.set("enclosure", enclosure);
1946                }
1947                rehearsal.set("halign", "center");
1948                rehearsal.set("valign", "middle");
1949                let direction = self.direction(rehearsal, mark.placement(), offset, true);
1950                self.root.push(direction);
1951            }
1952            // music21 writes nothing for a barline standing inside a
1953            // measure: `Barline` is one of `ignoreOnParseClasses`. A
1954            // manuscript's break is no class its exporter knows.
1955            StreamElement::Barline(_)
1956            | StreamElement::Break(_)
1957            | StreamElement::Layout(_)
1958            | StreamElement::TextBox(_)
1959            | StreamElement::Variant(_) => {}
1960        }
1961        Ok(())
1962    }
1963
1964    /// music21's `pedalObjectToXml`: a bounce or a gap in a held pedal, as
1965    /// the pedal mark holding it is drawn. One no pedal mark holds writes
1966    /// nothing.
1967    fn pedal_object_elements(&mut self, object: &PedalObject, position: *const StreamElement) {
1968        let Some(pedal) = self
1969            .context
1970            .spanners
1971            .iter()
1972            .find(|numbered| {
1973                numbered.spanner.kind() == SpannerKind::PedalMark && numbered.holds(position)
1974            })
1975            .map(|numbered| numbered.spanner.pedal().unwrap_or_default())
1976        else {
1977            return;
1978        };
1979        let lined = pedal.form.is_some_and(PedalForm::has_line);
1980        // Soft and silent pedals have no start of their own in MusicXML, and
1981        // music21 writes them, and a pedal that does not say, as `sustain`.
1982        let down = match pedal.pedal_type {
1983            Some(PedalType::Sostenuto) => "sostenuto",
1984            Some(PedalType::Sustain) => "start",
1985            _ => "sustain",
1986        };
1987        let types: &[&str] = match object.kind() {
1988            PedalObjectKind::Bounce => match pedal.form {
1989                Some(PedalForm::Line | PedalForm::SymbolLine) => &["change"],
1990                Some(PedalForm::SymbolAlt) => &[down],
1991                Some(PedalForm::Symbol) => &["stop", down],
1992                None => return,
1993            },
1994            PedalObjectKind::GapStart => &["discontinue"],
1995            PedalObjectKind::GapEnd => &["resume"],
1996        };
1997        for kind in types {
1998            let mut mark = Element::new("pedal");
1999            mark.set("type", *kind);
2000            mark.set(if lined { "line" } else { "sign" }, "yes");
2001            let direction = self.direction(mark, object.placement(), self.element_offset, true);
2002            self.root.push(direction);
2003        }
2004    }
2005
2006    /// music21's `wrapObjectInAttributes`: a mark after the start of a
2007    /// measure gets an `<attributes>` of its own; one at the start is already
2008    /// in the measure's first.
2009    fn wrap_in_attributes(&mut self, element: Element) {
2010        if self.offset_in_measure == 0.0 {
2011            return;
2012        }
2013        let mut attributes = Element::new("attributes");
2014        attributes.push(element);
2015        self.root.push(attributes);
2016    }
2017
2018    // --------------------------------------------------------- notes
2019
2020    /// music21's `noteToXml`, for a note alone or the `index`th of a chord.
2021    fn note_element(
2022        &self,
2023        note: &Note,
2024        index: usize,
2025        chord: Option<&Chord>,
2026        position: Option<*const StreamElement>,
2027    ) -> Result<Element> {
2028        self.written_note_element(note, index, chord, position, false)
2029    }
2030
2031    /// `noteToXml` for a note, or `unpitchedToXml` for a stroke with no
2032    /// pitch, which is written where it is displayed and has no accidental.
2033    fn written_note_element(
2034        &self,
2035        note: &Note,
2036        index: usize,
2037        chord: Option<&Chord>,
2038        position: Option<*const StreamElement>,
2039        unpitched: bool,
2040    ) -> Result<Element> {
2041        let in_chord = index != 0;
2042        let mut mxnote = Element::new("note");
2043        if chord.is_some()
2044            && let Some(color) = note.color()
2045        {
2046            mxnote.set("color", normalize_color(color)?);
2047        }
2048        if let Some(color) = chord.map_or(note.color(), Chord::color) {
2049            mxnote.set("color", normalize_color(color)?);
2050        }
2051        let volume = match chord {
2052            Some(chord) => chord.has_volume_information().then(|| chord.volume()),
2053            None => note.has_volume_information().then(|| note.volume()),
2054        };
2055        if let Some(scalar) = volume.and_then(|volume| volume.velocity_scalar()) {
2056            let velocity = scalar * 100.0 * (127.0 / 90.0);
2057            mxnote.set("dynamics", format!("{velocity:.2}"));
2058        }
2059        // An id is written only where it is a name XML takes as one.
2060        if let Some(id) = note.id()
2061            && id
2062                .chars()
2063                .next()
2064                .is_some_and(|first| first.is_alphabetic() || first == '_')
2065            && id
2066                .chars()
2067                .all(|c| c.is_alphanumeric() || matches!(c, '_' | '-' | '.'))
2068        {
2069            mxnote.set("id", id);
2070        }
2071        let duration = chord
2072            .map_or_else(|| note.duration(), Chord::duration)
2073            .cloned()
2074            .unwrap_or_default();
2075        if note.hidden() {
2076            mxnote.set("print-object", "no");
2077            mxnote.set("print-spacing", "yes");
2078        }
2079        if chord
2080            .map_or(note.articulations(), Chord::articulations)
2081            .iter()
2082            .any(|articulation| articulation.is_a("Pizzicato"))
2083        {
2084            mxnote.set("pizzicato", "yes");
2085        }
2086
2087        if let Some(grace) = duration.grace() {
2088            let element = mxnote.sub("grace");
2089            element.set("slash", yes_no(grace.slash()));
2090            if let Some(share) = grace.steal_time_previous() {
2091                element.set("steal-time-previous", fraction_to_percent(share));
2092            }
2093            if let Some(share) = grace.steal_time_following() {
2094                element.set("steal-time-following", fraction_to_percent(share));
2095            }
2096        }
2097        if in_chord {
2098            mxnote.sub("chord");
2099        }
2100        if unpitched {
2101            let displayed = mxnote.sub("unpitched");
2102            displayed.sub_text("display-step", note.pitch().step().as_char().to_string());
2103            displayed.sub_text("display-octave", note.pitch().implicit_octave().to_string());
2104        } else {
2105            mxnote.push(pitch_element(note.pitch()));
2106        }
2107        if !duration.is_grace() {
2108            mxnote.sub_text("duration", divisions(duration.quarter_length()).to_string());
2109        }
2110        if let Some(tie) = note.tie() {
2111            for element in tie_elements(tie) {
2112                mxnote.push(element);
2113            }
2114        }
2115        let stored = chord.map_or(note.stored_instrument(), Chord::stored_instrument);
2116        self.set_note_instrument(&mut mxnote, stored)?;
2117        if let Some(voice) = &self.current_voice {
2118            mxnote.sub_text("voice", voice.as_str());
2119        }
2120        // A grace note with no written value of its own is an eighth.
2121        let (written_type, dots) = match written_type(&duration) {
2122            Err(_) if duration.is_grace() && duration.components().is_empty() => ("eighth", 0),
2123            other => other?,
2124        };
2125        let written = mxnote.sub("type");
2126        written.set_text(written_type);
2127        if let Some(size) = note.size() {
2128            written.set("size", size.as_str());
2129        }
2130        for _ in 0..dots {
2131            mxnote.sub("dot");
2132        }
2133        if !unpitched
2134            && let Some(accidental) = note.pitch().written_accidental()
2135            && accidental.display_status() != Some(false)
2136        {
2137            mxnote.push(accidental_element(accidental, "accidental")?);
2138        }
2139        if let Some(modification) = time_modification(&duration)? {
2140            mxnote.push(modification);
2141        }
2142
2143        let chord_stem = chord.map_or(note.stem_direction(), Chord::stem_direction);
2144        let stem = if !in_chord && chord_stem != StemDirection::Unspecified {
2145            Some(chord_stem)
2146        } else if chord.is_some() && note.stem_direction() != StemDirection::Unspecified {
2147            Some(note.stem_direction())
2148        } else {
2149            None
2150        };
2151        if let Some(stem) = stem {
2152            let text = match stem {
2153                StemDirection::NoStem => "none",
2154                other => other.as_str(),
2155            };
2156            mxnote.sub_text("stem", text);
2157        }
2158
2159        if let Some(notehead) = notehead_for(note, chord)? {
2160            mxnote.push(notehead);
2161        }
2162        if !in_chord {
2163            let beams = chord.map_or(note.beams(), Chord::beams);
2164            for element in beam_elements(beams)? {
2165                mxnote.push(element);
2166            }
2167        }
2168
2169        let spanners: &[Numbered<'_>] = match position {
2170            Some(_) => &self.context.spanners,
2171            None => &[],
2172        };
2173        let pitch = note.pitch().ps();
2174        let mut notations = note_notations(
2175            note,
2176            index,
2177            chord,
2178            position,
2179            spanners,
2180            &self.arpeggios,
2181            pitch,
2182        )?;
2183        if !in_chord {
2184            notations.extend(tuplet_notations(&duration)?);
2185        }
2186        if !notations.is_empty() {
2187            let mxnotations = mxnote.sub("notations");
2188            for element in notations {
2189                mxnotations.push(element);
2190            }
2191        }
2192        if !in_chord {
2193            let lyrics = chord.map_or(note.lyrics(), Chord::lyrics);
2194            for lyric in lyrics {
2195                if lyric.explicit_text().is_none() {
2196                    continue;
2197                }
2198                mxnote.push(lyric_element(lyric)?);
2199            }
2200        }
2201        Ok(mxnote)
2202    }
2203
2204    /// music21's `setNoteInstrument`: which instrument plays a note, where
2205    /// a part has more than one.
2206    ///
2207    /// The instrument is the one kept on the note or its chord, or else the
2208    /// last one standing in the staff at or before the note; it is named by
2209    /// the id of the part's first instrument of the same kind.
2210    fn set_note_instrument(&self, mxnote: &mut Element, stored: Option<&Instrument>) -> Result<()> {
2211        if self.context.instruments.len() <= 1 {
2212            return Ok(());
2213        }
2214        let at = self.start + self.element_offset;
2215        let default;
2216        let closest = match stored.or_else(|| {
2217            self.context
2218                .own_instruments
2219                .iter()
2220                .rev()
2221                .find(|(offset, _)| *offset <= at)
2222                .map(|(_, instrument)| *instrument)
2223        }) {
2224            Some(instrument) => instrument,
2225            None => {
2226                default = Instrument::new();
2227                &default
2228            }
2229        };
2230        let named = self
2231            .context
2232            .instruments
2233            .iter()
2234            .find(|placed| placed.instrument.kind() == closest.kind())
2235            .ok_or_else(|| {
2236                export_error(format!(
2237                    "Instrument instance {} for a note not found in instrumentStream",
2238                    closest.kind()
2239                ))
2240            })?;
2241        let element = mxnote.sub("instrument");
2242        if let Some(id) = named.instrument.instrument_id() {
2243            element.set("id", id);
2244        }
2245        Ok(())
2246    }
2247
2248    /// music21's `chordToXml`: a `<note>` for each of the chord's notes,
2249    /// in the order the chord holds them, every one after the first marked `<chord/>`.
2250    fn chord_elements(
2251        &mut self,
2252        chord: &Chord,
2253        position: Option<*const StreamElement>,
2254    ) -> Result<()> {
2255        for (index, note) in chord.notes().iter().enumerate() {
2256            let element = self.note_element(note, index, Some(chord), position)?;
2257            self.root.push(element);
2258        }
2259        Ok(())
2260    }
2261
2262    /// music21's `restToXml`.
2263    fn rest_element(
2264        &self,
2265        rest: &Rest,
2266        offset: FloatType,
2267        position: Option<*const StreamElement>,
2268    ) -> Result<Element> {
2269        let mut mxnote = Element::new("note");
2270        if rest.hidden() {
2271            mxnote.set("print-object", "no");
2272            mxnote.set("print-spacing", "yes");
2273        }
2274        let duration = rest.duration();
2275        let full_measure = self
2276            .meter_at(offset)
2277            .is_some_and(|meter| meter.bar_quarter_length() == duration.quarter_length());
2278        let mxrest = mxnote.sub("rest");
2279        if full_measure {
2280            mxrest.set("measure", "yes");
2281        }
2282        if rest.step_shift() != 0 {
2283            // music21 counts from the middle line of the clef in force, a
2284            // treble clef's where there is none that places pitches.
2285            // The clef is the last one standing in the staff at or before
2286            // the rest, wherever the writer has got to in another voice.
2287            let at = self.start + offset;
2288            let lowest = self
2289                .context
2290                .own_clefs
2291                .iter()
2292                .rev()
2293                .find(|(clef_offset, _)| *clef_offset <= at)
2294                .and_then(|(_, clef)| clef.lowest_line())
2295                .or_else(|| Clef::treble().lowest_line())
2296                .unwrap_or(31);
2297            let (step, octave) = step_and_octave(lowest + 4 + rest.step_shift());
2298            mxrest.sub_text("display-step", step.to_string());
2299            mxrest.sub_text("display-octave", octave.to_string());
2300        }
2301        mxnote.sub_text("duration", divisions(duration.quarter_length()).to_string());
2302        if let Some(tie) = rest.tie() {
2303            for element in tie_elements(tie) {
2304                mxnote.push(element);
2305            }
2306        }
2307        if let Some(voice) = &self.current_voice {
2308            mxnote.sub_text("voice", voice.as_str());
2309        }
2310        if !full_measure {
2311            let (written_type, dots) = written_type(duration)?;
2312            let written = mxnote.sub("type");
2313            written.set_text(written_type);
2314            if let Some(size) = rest.size() {
2315                written.set("size", size.as_str());
2316            }
2317            for _ in 0..dots {
2318                mxnote.sub("dot");
2319            }
2320        }
2321        if let Some(modification) = time_modification(duration)? {
2322            mxnote.push(modification);
2323        }
2324        let mut notations = notations(
2325            Marks {
2326                expressions: rest.expressions(),
2327                articulations: rest.articulations(),
2328                tie: rest.tie(),
2329                index: 0,
2330                last: 0,
2331                arpeggios: &self.arpeggios,
2332                pitch: None,
2333            },
2334            position,
2335            &self.context.spanners,
2336        )?;
2337        notations.extend(tuplet_notations(duration)?);
2338        if !notations.is_empty() {
2339            let mxnotations = mxnote.sub("notations");
2340            for element in notations {
2341                mxnotations.push(element);
2342            }
2343        }
2344        for lyric in rest.lyrics() {
2345            if lyric.explicit_text().is_none() {
2346                continue;
2347            }
2348            mxnote.push(lyric_element(lyric)?);
2349        }
2350        Ok(mxnote)
2351    }
2352
2353    /// The metre in force at an offset within the measure: music21's
2354    /// `getContextByClass(TimeSignature)` from a rest.
2355    fn meter_at(&self, offset: FloatType) -> Option<TimeSignature> {
2356        self.stream
2357            .events()
2358            .iter()
2359            .rev()
2360            .filter(|event| event.offset() <= offset)
2361            .find_map(|event| match event.element() {
2362                StreamElement::TimeSignature(meter) => Some(meter.clone()),
2363                _ => None,
2364            })
2365            .or_else(|| self.context.meter.clone())
2366    }
2367
2368    // ---------------------------------------------------- directions
2369
2370    /// music21's `setOffsetOptional`: an `<offset>` for a direction that does
2371    /// not stand where the writer has got to.
2372    fn offset_element(&self, offset: FloatType, sound: bool) -> Option<Element> {
2373        offset_element_from(offset, self.offset_in_measure, sound)
2374    }
2375
2376    /// music21's `placeInDirection`.
2377    fn direction(
2378        &self,
2379        inner: Element,
2380        placement: Option<Placement>,
2381        offset: FloatType,
2382        sound: bool,
2383    ) -> Element {
2384        let mut direction = Element::new("direction");
2385        direction.sub("direction-type").push(inner);
2386        if let Some(placement) = placement {
2387            direction.set("placement", placement.as_str());
2388        }
2389        if let Some(element) = self.offset_element(offset, sound) {
2390            direction.push(element);
2391        }
2392        direction
2393    }
2394
2395    /// music21's `segnoToXml` and `codaToXml` for a sign, and
2396    /// `textExpressionToXml` for words: a sign stands two staff lines up,
2397    /// and words are justified as their kind of mark is.
2398    fn repeat_expression_element(&self, mark: &RepeatExpression, offset: FloatType) -> Element {
2399        if mark.use_symbol() && mark.kind().has_symbol() {
2400            let tag = match mark.kind() {
2401                RepeatExpressionKind::Segno => "segno",
2402                _ => "coda",
2403            };
2404            let mut sign = Element::new(tag);
2405            sign.set("default-y", "20");
2406            return self.direction(sign, mark.placement(), offset, true);
2407        }
2408        let mut words = Element::with_text("words", mark.text());
2409        words.set("justify", mark.kind().justify());
2410        self.direction(words, mark.placement(), offset, false)
2411    }
2412
2413    /// music21's `chordSymbolToXml`, and `noChordToXml` for a symbol saying
2414    /// nothing sounds.
2415    fn harmony_element(&self, symbol: &ChordSymbol, offset: FloatType) -> Result<Element> {
2416        let mut harmony = Element::new("harmony");
2417        if let Some(placement) = symbol.placement() {
2418            harmony.set("placement", placement.as_str());
2419        }
2420        let alter_of = |pitch: &Pitch| {
2421            pitch
2422                .written_accidental()
2423                .map(|accidental| accidental.alter())
2424        };
2425        if symbol.is_no_chord() {
2426            let root = harmony.sub("root");
2427            let step = root.sub("root-step");
2428            step.set_text("C");
2429            step.set("text", "");
2430            let kind = harmony.sub("kind");
2431            kind.set_text("none");
2432            kind.set(
2433                "text",
2434                symbol.kind_text().ok_or_else(|| {
2435                    export_error("NoChord object's chordKindStr must be non-empty")
2436                })?,
2437            );
2438        } else {
2439            let root = symbol.root();
2440            let written = harmony.sub("root");
2441            written.sub_text("root-step", root.step().as_char().to_string());
2442            if let Some(alter) = alter_of(root) {
2443                written.sub_text("root-alter", num_to_int_or_float(alter));
2444            }
2445            let mut kind = symbol.kind().unwrap_or("none");
2446            for (alias, target) in crate::chordsymbol::CHORD_KIND_ALIASES {
2447                if target == kind {
2448                    kind = alias;
2449                }
2450            }
2451            let written = harmony.sub("kind");
2452            written.set_text(kind);
2453            if let Some(text) = symbol.kind_text() {
2454                written.set("text", text);
2455            }
2456            // music21 reads the inversion off the chord the symbol sounds,
2457            // counted from the symbol's own root.
2458            let sounded = symbol.pitches().ok().filter(|pitches| !pitches.is_empty());
2459            let chord = sounded
2460                .as_ref()
2461                .and_then(|pitches| Chord::new(pitches.as_slice()).ok())
2462                .map(|mut chord| {
2463                    chord.set_root(Some(root.clone()));
2464                    chord
2465                });
2466            if let Some(inversion) = chord.as_ref().and_then(Chord::inversion)
2467                && inversion != 0
2468            {
2469                harmony.sub_text("inversion", inversion.to_string());
2470            }
2471            let bass = symbol.bass().cloned().or_else(|| {
2472                sounded
2473                    .as_ref()
2474                    .and_then(|pitches| pitches.first().cloned())
2475            });
2476            if let Some(bass) = bass
2477                && bass.name() != root.name()
2478            {
2479                let written = harmony.sub("bass");
2480                written.sub_text("bass-step", bass.step().as_char().to_string());
2481                if let Some(alter) = alter_of(&bass) {
2482                    written.sub_text("bass-alter", num_to_int_or_float(alter));
2483                }
2484            }
2485            for modification in symbol.chord_step_modifications() {
2486                let degree = harmony.sub("degree");
2487                degree.sub_text("degree-value", modification.degree().to_string());
2488                degree.sub_text(
2489                    "degree-alter",
2490                    num_to_int_or_float(modification.interval().semitones()),
2491                );
2492                degree.sub_text(
2493                    "degree-type",
2494                    modification.modification_type().music21_name(),
2495                );
2496            }
2497        }
2498        if let Some(element) = self.offset_element(offset, true) {
2499            harmony.push(element);
2500        }
2501        Ok(harmony)
2502    }
2503
2504    /// music21's `dynamicToXml`.
2505    fn dynamic_element(&mut self, dynamic: &Dynamic, offset: FloatType) {
2506        let mut dynamics = Element::new("dynamics");
2507        if DYNAMIC_MARKS.contains(&dynamic.value()) {
2508            dynamics.sub(dynamic_tag(dynamic.value()));
2509        } else {
2510            dynamics.sub_text("other-dynamics", dynamic.value());
2511        }
2512        // A dynamic's style starts music21 off at these positions.
2513        dynamics.set("default-x", "-36");
2514        dynamics.set("default-y", "-80");
2515        let mut direction = self.direction(dynamics, dynamic.placement(), offset, true);
2516        let volume = (dynamic.volume_scalar() * 127.0) as i64;
2517        direction.sub("sound").set("dynamics", volume.to_string());
2518        self.root.push(direction);
2519    }
2520
2521    /// music21's `tempoIndicationToXml` for a metronome mark: the mark, and
2522    /// its word as a direction of its own where the word was written.
2523    fn tempo_elements(&mut self, mark: &MetronomeMark, offset: FloatType) -> Result<()> {
2524        let shown = !mark.number_implicit() && mark.number().is_some();
2525        let direction = if shown {
2526            let mut metronome = Element::new("metronome");
2527            let (beat_unit, dots) = written_type(mark.referent())?;
2528            metronome.sub_text("beat-unit", beat_unit);
2529            for _ in 0..dots {
2530                metronome.sub("beat-unit-dot");
2531            }
2532            if let Some(number) = mark.number() {
2533                metronome.sub_text("per-minute", num_to_int_or_float(number));
2534            }
2535            metronome.set("parentheses", yes_no(mark.parentheses()));
2536            self.direction(metronome, mark.placement(), offset, true)
2537        } else {
2538            self.direction(Element::new("words"), mark.placement(), offset, true)
2539        };
2540        let mut direction = direction;
2541        if let Some(bpm) = mark.sounding_quarter_bpm().filter(|bpm| *bpm != 0.0) {
2542            direction
2543                .sub("sound")
2544                .set("tempo", num_to_int_or_float(bpm));
2545        }
2546        self.root.push(direction);
2547
2548        if let Some(text) = mark.text_expression(false) {
2549            let direction = self.tempo_words(text, None, offset);
2550            self.root.push(direction);
2551        }
2552        Ok(())
2553    }
2554
2555    /// music21's `tempoIndicationToXml` for a metric modulation: the two
2556    /// note values side by side, neither with its number, and the speed the
2557    /// new one is played at.
2558    fn metric_modulation_element(
2559        &self,
2560        modulation: &MetricModulation,
2561        offset: FloatType,
2562    ) -> Result<Element> {
2563        let mut metronome = Element::new("metronome");
2564        for side in [modulation.old_metronome(), modulation.new_metronome()] {
2565            let side = side.ok_or_else(|| {
2566                export_error("a metric modulation needs a mark on both sides to be written")
2567            })?;
2568            let (beat_unit, dots) = written_type(side.referent())?;
2569            metronome.sub_text("beat-unit", beat_unit);
2570            for _ in 0..dots {
2571                metronome.sub("beat-unit-dot");
2572            }
2573        }
2574        metronome.set("parentheses", yes_no(modulation.parentheses()));
2575        let mut direction = self.direction(metronome, modulation.placement(), offset, true);
2576        if let Some(bpm) = modulation
2577            .new_metronome()
2578            .and_then(MetronomeMark::sounding_quarter_bpm)
2579            .filter(|bpm| *bpm != 0.0)
2580        {
2581            direction
2582                .sub("sound")
2583                .set("tempo", num_to_int_or_float(bpm));
2584        }
2585        Ok(direction)
2586    }
2587
2588    /// A tempo said in words, as music21's `TempoText.applyTextFormatting`
2589    /// sets them: bold, four and a half staff lines up.
2590    fn tempo_words(&self, text: &str, placement: Option<Placement>, offset: FloatType) -> Element {
2591        let mut words = Element::with_text("words", text);
2592        words.set("default-y", "45");
2593        words.set("font-weight", "bold");
2594        self.direction(words, placement, offset, false)
2595    }
2596}
2597
2598/// music21's `noteToNotations`: what goes inside a note's `<notations>`,
2599/// less the tuplet brackets, which the caller adds after.
2600///
2601/// Expressions come from the chord where the note is one of a chord's. An
2602/// arpeggio is written on every note of it, a bracket against arpeggiating
2603/// on the bottom and top notes only, and everything else on the first note
2604/// alone; ornaments are gathered into one `<ornaments>` at the end.
2605fn note_notations(
2606    note: &Note,
2607    index: usize,
2608    chord: Option<&Chord>,
2609    position: Option<*const StreamElement>,
2610    spanners: &[Numbered<'_>],
2611    arpeggios: &ArpeggioNumbers,
2612    pitch: FloatType,
2613) -> Result<Vec<Element>> {
2614    notations(
2615        Marks {
2616            expressions: chord.map_or(note.expressions(), Chord::expressions),
2617            articulations: chord.map_or(note.articulations(), Chord::articulations),
2618            tie: note.tie(),
2619            index,
2620            last: chord.map_or(0, |chord| chord.notes().len().saturating_sub(1)),
2621            arpeggios,
2622            pitch: Some(pitch),
2623        },
2624        position,
2625        spanners,
2626    )
2627}
2628
2629/// What goes into one `<note>`'s notations: the expressions and
2630/// articulations it is written with, its tie, and which note of its chord it
2631/// is, the last being the top.
2632struct Marks<'a> {
2633    expressions: &'a [Expression],
2634    articulations: &'a [Articulation],
2635    tie: Option<&'a Tie>,
2636    index: usize,
2637    last: usize,
2638    /// The numbers the measure's arpeggios across chords are written with.
2639    arpeggios: &'a ArpeggioNumbers,
2640    /// The note's pitch space, where it is a note.
2641    pitch: Option<FloatType>,
2642}
2643
2644/// music21's `noteToNotations`, for a note or a rest.
2645fn notations(
2646    marks: Marks<'_>,
2647    position: Option<*const StreamElement>,
2648    spanners: &[Numbered<'_>],
2649) -> Result<Vec<Element>> {
2650    let index = marks.index;
2651    let first = index == 0;
2652    let mut notations = Vec::new();
2653    let (articulations, technical) = articulation_elements(marks.articulations, index);
2654    let mut ornaments: Option<Element> = None;
2655    for expression in marks.expressions {
2656        match expression {
2657            Expression::Arpeggio(ArpeggioType::NonArpeggio) => {
2658                let last = marks.last;
2659                let side = if first {
2660                    Some("bottom")
2661                } else if index == last {
2662                    Some("top")
2663                } else {
2664                    None
2665                };
2666                if let Some(side) = side {
2667                    let mut element = Element::new("non-arpeggiate");
2668                    element.set("type", side);
2669                    notations.push(element);
2670                }
2671            }
2672            Expression::Arpeggio(arpeggio) => {
2673                let mut element = Element::new("arpeggiate");
2674                if *arpeggio != ArpeggioType::Normal {
2675                    element.set("direction", arpeggio.as_str());
2676                }
2677                notations.push(element);
2678            }
2679            Expression::Fermata(fermata) if first => {
2680                let mut element = Element::new("fermata");
2681                element.set("type", fermata.fermata_type().as_str());
2682                if let Some(shape @ ("angled" | "square")) = fermata.shape() {
2683                    element.set_text(shape);
2684                }
2685                notations.push(element);
2686            }
2687            Expression::Ornament(ornament) if first => {
2688                let written = ornaments.get_or_insert_with(|| Element::new("ornaments"));
2689                written.push(ornament_element(ornament));
2690                for mark in ornament_accidental_marks(ornament)? {
2691                    written.push(mark);
2692                }
2693            }
2694            Expression::Fermata(_) | Expression::Ornament(_) => {}
2695        }
2696    }
2697    if let Some(tie) = marks.tie {
2698        notations.extend(tied_elements(tie));
2699    }
2700    if let Some(position) = position {
2701        notations.extend(spanner_notations(position, spanners, &marks));
2702    }
2703    notations.extend(articulations);
2704    notations.extend(technical);
2705    notations.extend(ornaments);
2706    Ok(notations)
2707}
2708
2709/// A chord in the order music21's exporter writes it: sorted by staff
2710/// position and pitch space, each fingering moved with the note it is the
2711/// turn of. A chord where a note with no fingering would sort before one
2712/// with a fingering is written as it stands.
2713fn written_order(chord: &Chord) -> Chord {
2714    let order = chord.ascending_order();
2715    if order
2716        .iter()
2717        .enumerate()
2718        .all(|(place, &index)| place == index)
2719    {
2720        return chord.clone();
2721    }
2722    let slots: Vec<usize> = chord
2723        .articulations()
2724        .iter()
2725        .enumerate()
2726        .filter(|(_, articulation)| articulation.kind() == ArticulationKind::Fingering)
2727        .map(|(slot, _)| slot)
2728        .collect();
2729    let fingered = slots.len().min(order.len());
2730    if order[..fingered].iter().any(|&index| index >= fingered) {
2731        return chord.clone();
2732    }
2733    let mut sorted = chord.sort_ascending();
2734    let articulations = sorted.articulations_mut();
2735    for (place, &index) in order[..fingered].iter().enumerate() {
2736        articulations[slots[place]] = chord.articulations()[slots[index]].clone();
2737    }
2738    sorted
2739}
2740
2741/// The `<articulations>` and `<technical>` of a note, the `index`th of its
2742/// chord where it is one: a fingering is written on the note it is the
2743/// turn of, anything else on the first note only. A pizzicato is written as
2744/// an attribute of the note instead, and a string numbered below one not at
2745/// all.
2746fn articulation_elements(
2747    articulations: &[Articulation],
2748    index: usize,
2749) -> (Option<Element>, Option<Element>) {
2750    let mut fingering = 0;
2751    let mut applicable = Vec::new();
2752    for articulation in articulations {
2753        if articulation.is_a("Fingering") {
2754            if fingering == index {
2755                applicable.push(articulation);
2756            }
2757            fingering += 1;
2758        } else if index == 0 && !articulation.is_a("HammerOn") && !articulation.is_a("PullOff") {
2759            applicable.push(articulation);
2760        }
2761    }
2762    let mut written: Option<Element> = None;
2763    let mut technical: Option<Element> = None;
2764    for articulation in applicable {
2765        if articulation.is_a("Pizzicato")
2766            || (articulation.is_a("StringIndication") && articulation.number() < 1)
2767        {
2768            continue;
2769        }
2770        if articulation.is_a("TechnicalIndication") {
2771            technical
2772                .get_or_insert_with(|| Element::new("technical"))
2773                .push(technical_element(articulation));
2774        } else {
2775            written
2776                .get_or_insert_with(|| Element::new("articulations"))
2777                .push(articulation_element(articulation));
2778        }
2779    }
2780    (written, technical)
2781}
2782
2783/// music21's `xmlObjects.ARTICULATION_MARKS_REV`, in the order it is
2784/// searched: the staccato and the accent last, as they are what others
2785/// descend from.
2786const ARTICULATION_NAMES: [(&str, &str); 15] = [
2787    ("StrongAccent", "strong-accent"),
2788    ("Staccatissimo", "staccatissimo"),
2789    ("Spiccato", "spiccato"),
2790    ("Tenuto", "tenuto"),
2791    ("DetachedLegato", "detached-legato"),
2792    ("Scoop", "scoop"),
2793    ("Plop", "plop"),
2794    ("Doit", "doit"),
2795    ("Falloff", "falloff"),
2796    ("BreathMark", "breath-mark"),
2797    ("Caesura", "caesura"),
2798    ("Stress", "stress"),
2799    ("Unstress", "unstress"),
2800    ("Staccato", "staccato"),
2801    ("Accent", "accent"),
2802];
2803
2804/// music21's `articulationToXmlArticulation`.
2805fn articulation_element(articulation: &Articulation) -> Element {
2806    let name = ARTICULATION_NAMES
2807        .iter()
2808        .find(|(class, _)| articulation.is_a(class))
2809        .map_or("other-articulation", |(_, name)| name);
2810    let mut element = Element::new(name);
2811    if let Some(placement) = articulation.placement() {
2812        element.set("placement", placement);
2813    }
2814    match name {
2815        "strong-accent" => {
2816            element.set("type", articulation.point_direction().unwrap_or("None"));
2817        }
2818        "breath-mark" => {
2819            if let Some(symbol) = articulation.symbol() {
2820                element.set_text(symbol);
2821            }
2822        }
2823        "other-articulation" => {
2824            if let Some(text) = articulation.display_text() {
2825                element.set_text(text);
2826            }
2827        }
2828        _ => {}
2829    }
2830    element
2831}
2832
2833/// music21's `xmlObjects.TECHNICAL_MARKS_REV`, in the order it is searched.
2834const TECHNICAL_NAMES: [(&str, &str); 20] = [
2835    ("UpBow", "up-bow"),
2836    ("DownBow", "down-bow"),
2837    ("StringHarmonic", "harmonic"),
2838    ("OpenString", "open-string"),
2839    ("StringThumbPosition", "thumb-position"),
2840    ("Fingering", "fingering"),
2841    ("FrettedPluck", "pluck"),
2842    ("DoubleTongue", "double-tongue"),
2843    ("TripleTongue", "triple-tongue"),
2844    ("Stopped", "stopped"),
2845    ("SnapPizzicato", "snap-pizzicato"),
2846    ("StringIndication", "string"),
2847    ("FretBend", "bend"),
2848    ("FretTap", "tap"),
2849    ("FretIndication", "fret"),
2850    ("OrganHeel", "heel"),
2851    ("OrganToe", "toe"),
2852    ("HarpFingerNails", "fingernails"),
2853    ("HandbellIndication", "handbell"),
2854    ("Harmonic", "harmonic"),
2855];
2856
2857/// music21's `articulationToXmlTechnical`.
2858fn technical_element(articulation: &Articulation) -> Element {
2859    use crate::articulations::Finger;
2860    let name = TECHNICAL_NAMES
2861        .iter()
2862        .find(|(class, _)| articulation.is_a(class))
2863        .map_or("other-technical", |(_, name)| name);
2864    let mut element = Element::new(name);
2865    if let Some(placement) = articulation.placement() {
2866        element.set("placement", placement);
2867    }
2868    match name {
2869        "fingering" => {
2870            element.set_text(match articulation.finger() {
2871                Some(Finger::Number(number)) => number.to_string(),
2872                Some(Finger::Written(text)) => text.clone(),
2873                None => "None".to_string(),
2874            });
2875            element.set("alternate", yes_no(articulation.alternate()));
2876            element.set("substitution", yes_no(articulation.substitution()));
2877        }
2878        "heel" | "toe" => element.set("substitution", yes_no(articulation.substitution())),
2879        "string" | "fret" => element.set_text(articulation.number().to_string()),
2880        "bend" => {
2881            let alter = element.sub("bend-alter");
2882            if let Some(interval) = articulation.bend_alter() {
2883                alter.set_text(num_to_int_or_float(interval.semitones()));
2884            }
2885            if articulation.pre_bend() {
2886                element.sub("pre-bend");
2887            }
2888            if let Some(release) = articulation.release() {
2889                let offset = (DIVISIONS_PER_QUARTER * release) as i64;
2890                element.sub("release").set("offset", offset.to_string());
2891            }
2892            if let Some(with_bar) = articulation.with_bar() {
2893                element.sub_text("with-bar", with_bar);
2894            }
2895        }
2896        "harmonic" => {
2897            if articulation.is_a("StringHarmonic") {
2898                match articulation.harmonic_type() {
2899                    Some("artificial") => {
2900                        element.sub("artificial");
2901                    }
2902                    Some("natural") => {
2903                        element.sub("natural");
2904                    }
2905                    _ => {}
2906                }
2907                match articulation.pitch_type() {
2908                    Some("base") => {
2909                        element.sub("base-pitch");
2910                    }
2911                    Some("sounding") => {
2912                        element.sub("sounding-pitch");
2913                    }
2914                    Some("touching") => {
2915                        element.sub("touching-pitch");
2916                    }
2917                    _ => {}
2918                }
2919            }
2920        }
2921        "handbell" | "other-technical" => {
2922            if let Some(text) = articulation.display_text() {
2923                element.set_text(text);
2924            }
2925        }
2926        _ => {}
2927    }
2928    element
2929}
2930
2931/// music21's `relatedSpanners`: the directions a hairpin opens with before
2932/// the element it starts on, and closes with after the one it ends on. A
2933/// hairpin over one element does both. A pedal drawn as a sign and a line
2934/// resumes its line straight after the sign, at the pedal mark's own offset
2935/// counted from `offset_in_measure`: music21's `makePedalResumeLineXml`.
2936fn related_spanners(
2937    position: *const StreamElement,
2938    spanners: &[Numbered<'_>],
2939    offset_in_measure: FloatType,
2940) -> (Vec<Element>, Vec<Element>) {
2941    let mut before = Vec::new();
2942    let mut after = Vec::new();
2943    // music21 asks the octave lines first, then the hairpins, the lines and
2944    // the pedal marks, and of each only those holding the element.
2945    let of_kind = |wanted: fn(SpannerKind) -> bool| {
2946        spanners
2947            .iter()
2948            .filter(move |numbered| wanted(numbered.spanner.kind()))
2949    };
2950    let ordered = of_kind(|kind| kind == SpannerKind::Ottava)
2951        .chain(of_kind(SpannerKind::is_wedge))
2952        .chain(of_kind(|kind| kind == SpannerKind::Line))
2953        .chain(of_kind(|kind| kind == SpannerKind::PedalMark));
2954    for numbered in ordered {
2955        let (spanner, id_local) = (numbered.spanner, numbered.id_local);
2956        if !numbered.holds(position) {
2957            continue;
2958        }
2959        let single = numbered.elements.len() == 1;
2960        let starts = single || numbered.is_first(position);
2961        let stops = single || numbered.is_last(position);
2962        for (here, start) in [(starts, true), (stops, false)] {
2963            if !here {
2964                continue;
2965            }
2966            let mark = match spanner.kind() {
2967                SpannerKind::Ottava => {
2968                    let mut shift = Element::new("octave-shift");
2969                    shift.set("number", id_local.to_string());
2970                    let octave = spanner.shift();
2971                    // An `8va` is written `down`: the notes are engraved
2972                    // below where they sound.
2973                    shift.set(
2974                        "type",
2975                        match (start, octave.is_some_and(|octave| octave.up())) {
2976                            (false, _) => "stop",
2977                            (true, true) => "down",
2978                            (true, false) => "up",
2979                        },
2980                    );
2981                    if let Some(octave) = octave {
2982                        shift.set("size", octave.magnitude().to_string());
2983                    }
2984                    shift
2985                }
2986                SpannerKind::Line => {
2987                    let mut bracket = Element::new("bracket");
2988                    bracket.set("number", id_local.to_string());
2989                    bracket.set("line-type", spanner.line_type().unwrap_or("solid"));
2990                    bracket.set("type", if start { "start" } else { "stop" });
2991                    let ends = spanner.ends();
2992                    let (tick, height) = if start {
2993                        (ends.start, ends.start_height)
2994                    } else {
2995                        (ends.end, ends.end_height)
2996                    };
2997                    bracket.set("line-end", tick.as_str());
2998                    if let Some(height) = height {
2999                        bracket.set("end-length", num_to_int_or_float(height));
3000                    }
3001                    bracket
3002                }
3003                SpannerKind::PedalMark => pedal_element(spanner, id_local, start),
3004                _ => wedge_element(spanner, id_local, start),
3005            };
3006            let mut direction = Element::new("direction");
3007            if let Some(placement) = spanner.placement() {
3008                direction.set("placement", placement.as_str());
3009            }
3010            direction.sub("direction-type").push(mark);
3011            if start {
3012                before.push(direction);
3013            } else {
3014                after.push(direction);
3015            }
3016            if start
3017                && spanner.kind() == SpannerKind::PedalMark
3018                && spanner.pedal().and_then(|pedal| pedal.form) == Some(PedalForm::SymbolLine)
3019            {
3020                let mut line = Element::new("pedal");
3021                line.set("type", "resume");
3022                line.set("line", "yes");
3023                let mut direction = Element::new("direction");
3024                direction.sub("direction-type").push(line);
3025                if let Some(placement) = spanner.placement() {
3026                    direction.set("placement", placement.as_str());
3027                }
3028                if let Some(offset) = offset_element_from(spanner.offset(), offset_in_measure, true)
3029                {
3030                    direction.push(offset);
3031                }
3032                before.push(direction);
3033            }
3034        }
3035    }
3036    (before, after)
3037}
3038
3039/// music21's `setOffsetOptional`: an `<offset>` for a direction at `offset`
3040/// that does not stand where the writer has got to, `offset_in_measure`.
3041fn offset_element_from(
3042    offset: FloatType,
3043    offset_in_measure: FloatType,
3044    sound: bool,
3045) -> Option<Element> {
3046    // music21 counts its place in the measure as a float, and holds an
3047    // offset no float spells exactly -- a triplet's -- as a `Fraction`,
3048    // which compares unequal to every float.
3049    if offset == offset_in_measure && !is_fraction_offset(offset) {
3050        return None;
3051    }
3052    let difference = ((offset - offset_in_measure) * DIVISIONS_PER_QUARTER) as i64;
3053    let mut element = Element::with_text("offset", difference.to_string());
3054    if sound {
3055        element.set("sound", "yes");
3056    }
3057    Some(element)
3058}
3059
3060/// A pedal mark's `<pedal>`, where it goes down or where it comes up:
3061/// music21's `_spannerStartParameters` and `_spannerEndParameters`.
3062///
3063/// MusicXML has no start for any pedal but the sustaining and sostenuto
3064/// ones, so music21 writes the soft and silent pedals as a plain start.
3065fn pedal_element(spanner: &Spanner, id_local: u32, start: bool) -> Element {
3066    let pedal = spanner.pedal().unwrap_or_default();
3067    let mut element = Element::new("pedal");
3068    element.set("number", id_local.to_string());
3069    let line = pedal.form.is_some_and(PedalForm::has_line);
3070    if start {
3071        element.set(
3072            "type",
3073            if pedal.pedal_type == Some(PedalType::Sostenuto) {
3074                "sostenuto"
3075            } else {
3076                "start"
3077            },
3078        );
3079        if pedal.form == Some(PedalForm::Line) {
3080            element.set("line", "yes");
3081        } else {
3082            element.set("sign", "yes");
3083        }
3084        if pedal.abbreviated {
3085            element.set("abbreviated", "yes");
3086        }
3087    } else {
3088        element.set("type", "stop");
3089        if line {
3090            element.set("line", "yes");
3091        } else {
3092            element.set("sign", "yes");
3093        }
3094    }
3095    element
3096}
3097
3098/// A hairpin's `<wedge>`, where it opens or where it closes: a crescendo
3099/// opens from nothing and a diminuendo closes to nothing.
3100fn wedge_element(spanner: &Spanner, id_local: u32, start: bool) -> Element {
3101    let crescendo = spanner.kind() == SpannerKind::Crescendo;
3102    let mut wedge = Element::new("wedge");
3103    wedge.set("number", id_local.to_string());
3104    wedge.set(
3105        "type",
3106        match (start, crescendo) {
3107            (true, true) => "crescendo",
3108            (true, false) => "diminuendo",
3109            (false, _) => "stop",
3110        },
3111    );
3112    if start == crescendo {
3113        wedge.set("spread", "0");
3114    } else if let Some(spread) = spanner.spread() {
3115        wedge.set("spread", num_to_int_or_float(spread));
3116    }
3117    wedge
3118}
3119
3120/// music21's `objectAttachedSpannersToNotations`: a slur's start on its
3121/// first note and its stop on its last, and nothing on the notes between.
3122fn spanner_notations(
3123    position: *const StreamElement,
3124    spanners: &[Numbered<'_>],
3125    marks: &Marks<'_>,
3126) -> Vec<Element> {
3127    let mut notations = Vec::new();
3128    // music21's `appendArpeggioMarkSpannersToNotations`: on every note of
3129    // every chord the arpeggio holds, or a bracket on its lowest and highest
3130    // notes alone; numbered when it holds more than one note.
3131    for numbered in spanners {
3132        let spanner = numbered.spanner;
3133        if spanner.kind() != SpannerKind::ArpeggioMark || !numbered.holds(position) {
3134            continue;
3135        }
3136        let mut element = if spanner.arpeggio() == ArpeggioType::NonArpeggio {
3137            let Some(((low, low_ps), (high, high_ps))) = numbered.note_extremes() else {
3138                continue;
3139            };
3140            let is = |at: *const StreamElement, ps: FloatType| {
3141                std::ptr::eq(at, position) && marks.pitch == Some(ps)
3142            };
3143            let side = if is(low, low_ps) {
3144                "bottom"
3145            } else if is(high, high_ps) {
3146                "top"
3147            } else {
3148                continue;
3149            };
3150            let mut element = Element::new("non-arpeggiate");
3151            element.set("type", side);
3152            element
3153        } else {
3154            let mut element = Element::new("arpeggiate");
3155            if spanner.arpeggio() != ArpeggioType::Normal {
3156                element.set("direction", spanner.arpeggio().as_str());
3157            }
3158            element
3159        };
3160        let notes: usize = numbered
3161            .elements
3162            .iter()
3163            .flatten()
3164            .map(|element| match element {
3165                StreamElement::Chord(chord) => chord.notes().len(),
3166                _ => 1,
3167            })
3168            .sum();
3169        if numbered.elements.len() > 1 || notes > 1 {
3170            element.set("number", marks.arpeggios.number(spanner).to_string());
3171        }
3172        notations.push(element);
3173    }
3174    if marks.index != 0 {
3175        return notations;
3176    }
3177    for numbered in spanners {
3178        let (spanner, id_local) = (numbered.spanner, numbered.id_local);
3179        if spanner.kind() != SpannerKind::Slur {
3180            continue;
3181        }
3182        let mut slur = Element::new("slur");
3183        if numbered.is_first(position) {
3184            slur.set("type", "start");
3185            if let Some(line_type) = spanner.line_type() {
3186                slur.set("line-type", line_type);
3187            }
3188            if let Some(placement) = spanner.placement() {
3189                slur.set("placement", placement.as_str());
3190            }
3191        } else if numbered.is_last(position) {
3192            slur.set("type", "stop");
3193        } else {
3194            continue;
3195        }
3196        slur.set("number", id_local.to_string());
3197        notations.push(slur);
3198    }
3199    // A glissando, or a slide where it is played without steps, written as
3200    // a slur is; only its start carries its words.
3201    for numbered in spanners {
3202        let (spanner, id_local) = (numbered.spanner, numbered.id_local);
3203        if spanner.kind() != SpannerKind::Glissando {
3204            continue;
3205        }
3206        let details = spanner.glissando_details().cloned().unwrap_or_default();
3207        let mut glissando = Element::new(if details.slide_type == SlideType::Continuous {
3208            "slide"
3209        } else {
3210            "glissando"
3211        });
3212        glissando.set("number", id_local.to_string());
3213        if let Some(line_type) = spanner.line_type() {
3214            glissando.set("line-type", line_type);
3215        }
3216        if numbered.is_first(position) {
3217            if let Some(label) = details.label {
3218                glissando.set_text(label);
3219            }
3220            glissando.set("type", "start");
3221        } else if numbered.is_last(position) {
3222            glissando.set("type", "stop");
3223        } else {
3224            continue;
3225        }
3226        notations.push(glissando);
3227    }
3228    // A tremolo between notes, then a trill's wavy line: where it starts,
3229    // where it stops, and both on a line over one note. These go in an
3230    // `<ornaments>` of their own.
3231    let mut ornaments = Element::new("ornaments");
3232    for numbered in spanners {
3233        let spanner = numbered.spanner;
3234        if spanner.kind() != SpannerKind::TremoloSpanner || !numbered.holds(position) {
3235            continue;
3236        }
3237        let mut tremolo = Element::with_text(
3238            "tremolo",
3239            spanner.number_of_marks().unwrap_or(3).to_string(),
3240        );
3241        if numbered.is_first(position) {
3242            tremolo.set("type", "start");
3243            if let Some(placement) = spanner.placement() {
3244                tremolo.set("placement", placement.as_str());
3245            }
3246        } else if numbered.is_last(position) {
3247            tremolo.set("type", "stop");
3248        }
3249        ornaments.push(tremolo);
3250    }
3251    for numbered in spanners {
3252        let (spanner, id_local) = (numbered.spanner, numbered.id_local);
3253        if spanner.kind() != SpannerKind::TrillExtension {
3254            continue;
3255        }
3256        let first = numbered.is_first(position);
3257        let last = numbered.is_last(position);
3258        if !first && !last {
3259            continue;
3260        }
3261        let mut line = Element::new("wavy-line");
3262        line.set("number", id_local.to_string());
3263        if first {
3264            line.set("type", "start");
3265            if let Some(placement) = spanner.placement() {
3266                line.set("placement", placement.as_str());
3267            }
3268        } else {
3269            line.set("type", "stop");
3270        }
3271        ornaments.push(line);
3272        if first && last {
3273            let mut stop = Element::new("wavy-line");
3274            stop.set("number", id_local.to_string());
3275            stop.set("type", "stop");
3276            ornaments.push(stop);
3277        }
3278    }
3279    if ornaments.len() > 0 {
3280        notations.push(ornaments);
3281    }
3282    notations
3283}
3284
3285/// music21's `expressionToXml` for an ornament.
3286fn ornament_element(ornament: &Ornament) -> Element {
3287    let tag = if ornament.is_a("Turn") {
3288        match (ornament.is_a("InvertedTurn"), ornament.is_delayed()) {
3289            (true, true) => "delayed-inverted-turn",
3290            (true, false) => "inverted-turn",
3291            (false, true) => "delayed-turn",
3292            (false, false) => "turn",
3293        }
3294    } else {
3295        [
3296            ("Trill", "trill-mark"),
3297            ("InvertedMordent", "inverted-mordent"),
3298            ("Mordent", "mordent"),
3299            ("Shake", "shake"),
3300            ("Schleifer", "schleifer"),
3301            ("Tremolo", "tremolo"),
3302        ]
3303        .into_iter()
3304        .find(|(class, _)| ornament.is_a(class))
3305        .map_or("other-ornament", |(_, tag)| tag)
3306    };
3307    let mut element = Element::new(tag);
3308    if let Some(placement) = ornament.placement() {
3309        element.set("placement", placement);
3310    }
3311    if ornament.is_a("Tremolo") {
3312        element.set("type", "single");
3313        element.set_text(ornament.number_of_marks().to_string());
3314    }
3315    element
3316}
3317
3318/// music21's `ornamentToMxAccidentalMarks`: the accidentals an ornament's
3319/// neighbours are shown with, above or below it.
3320fn ornament_accidental_marks(ornament: &Ornament) -> Result<Vec<Element>> {
3321    let shown = |accidental: Option<&Accidental>| {
3322        accidental
3323            .filter(|accidental| accidental.display_status() == Some(true))
3324            .cloned()
3325    };
3326    let mut marks = Vec::new();
3327    let mut mark = |accidental: Accidental, placement: &str| -> Result<()> {
3328        let mut element = accidental_element(&accidental, "accidental-mark")?;
3329        element.set("placement", placement);
3330        marks.push(element);
3331        Ok(())
3332    };
3333    if ornament.is_a("Turn") {
3334        if let Some(upper) = shown(ornament.upper_accidental()) {
3335            mark(upper, "above")?;
3336        }
3337        if let Some(lower) = shown(ornament.lower_accidental()) {
3338            mark(lower, "below")?;
3339        }
3340    } else if (ornament.is_a("GeneralMordent") || ornament.is_a("Trill"))
3341        && let Some(accidental) = shown(ornament.accidental())
3342    {
3343        let below = ornament.direction() == Some(crate::interval::IntervalDirection::Descending);
3344        mark(accidental, if below { "below" } else { "above" })?;
3345    }
3346    Ok(marks)
3347}
3348
3349/// music21's `setBarline`, with `barlineToXml` and `repeatToXml`: a plain
3350/// barline says how it is drawn, a repeat sign only which way it repeats.
3351fn barline_element(
3352    barline: Option<&Barline>,
3353    location: &str,
3354    ending: Option<(&Ending, &str)>,
3355) -> Element {
3356    let mut element = Element::new("barline");
3357    let repeat = barline.and_then(Barline::repeat_direction);
3358    if let Some(barline) = barline
3359        && repeat.is_none()
3360    {
3361        element.sub_text("bar-style", barline.bar_type().musicxml_bar_style());
3362    }
3363    element.set("location", location);
3364    if let Some((ending, kind)) = ending {
3365        let written = element.sub("ending");
3366        written.set("number", ending.number_text());
3367        written.set("type", kind);
3368    }
3369    if let (Some(barline), Some(direction)) = (barline, repeat) {
3370        let written = element.sub("repeat");
3371        written.set(
3372            "direction",
3373            match direction {
3374                RepeatDirection::Start => "forward",
3375                RepeatDirection::End => "backward",
3376            },
3377        );
3378        if let Some(times) = barline.repeat_times() {
3379            written.set("times", times.to_string());
3380        }
3381    }
3382    element
3383}
3384
3385/// The last thing of one sort a stream holds.
3386fn last_of<T>(stream: &Stream, read: impl Fn(&StreamElement) -> Option<T>) -> Option<T> {
3387    stream
3388        .events()
3389        .iter()
3390        .rev()
3391        .find_map(|event| read(event.element()))
3392}
3393
3394/// Whether an element is one of music21's `GeneralNote`s.
3395fn is_general_note(element: &StreamElement) -> bool {
3396    matches!(
3397        element,
3398        StreamElement::Note(_)
3399            | StreamElement::Chord(_)
3400            | StreamElement::Unpitched(_)
3401            | StreamElement::PercussionChord(_)
3402            | StreamElement::Rest(_)
3403            | StreamElement::ChordSymbol(_)
3404    )
3405}
3406
3407/// Whether an element is written as a `<note>`, which a chord symbol is not
3408/// unless it asks to be.
3409fn is_written_as_note(element: &StreamElement) -> bool {
3410    matches!(
3411        element,
3412        StreamElement::Note(_)
3413            | StreamElement::Chord(_)
3414            | StreamElement::Unpitched(_)
3415            | StreamElement::PercussionChord(_)
3416            | StreamElement::Rest(_)
3417    )
3418}
3419
3420/// The step and octave of a diatonic note number, as music21's
3421/// `diatonicNoteNum` setter reads one: the octave truncated toward nought.
3422fn step_and_octave(number: i32) -> (char, i32) {
3423    let octave = (number - 1) / 7;
3424    let index = (number - 1 - 7 * octave).rem_euclid(7);
3425    (['C', 'D', 'E', 'F', 'G', 'A', 'B'][index as usize], octave)
3426}
3427
3428/// music21's `xmlObjects.fractionToPercent`: a share as a whole percent,
3429/// truncated.
3430fn fraction_to_percent(share: FloatType) -> String {
3431    ((share * 100.0) as i64).to_string()
3432}
3433
3434/// A length in divisions, rounded as Python's `round` rounds.
3435fn divisions(quarter_length: FloatType) -> i64 {
3436    (quarter_length * DIVISIONS_PER_QUARTER).round_ties_even() as i64
3437}
3438
3439fn format_int(value: FloatType) -> String {
3440    (value as i64).to_string()
3441}
3442
3443/// music21's `common.numToIntOrFloat`, written as Python writes the result.
3444fn num_to_int_or_float(value: FloatType) -> String {
3445    let rounded = value.round_ties_even();
3446    if (rounded - value).abs() < 1e-6 {
3447        format!("{}", rounded as i64)
3448    } else {
3449        format!("{value}")
3450    }
3451}
3452
3453/// music21's `typeToMusicXMLType`.
3454fn musicxml_type(duration_type: DurationType) -> Result<&'static str> {
3455    match duration_type {
3456        DurationType::Longa => Ok("long"),
3457        DurationType::TwentyFortyEighth => Err(export_error(
3458            "Cannot convert \"2048th\" duration to MusicXML (too short).",
3459        )),
3460        DurationType::DuplexMaxima => Err(export_error(
3461            "Cannot convert \"duplex-maxima\" duration to MusicXML (too long).",
3462        )),
3463        DurationType::Zero => Err(export_error(
3464            "Cannot convert durations without types to MusicXML.",
3465        )),
3466        other => Ok(other.music21_name()),
3467    }
3468}
3469
3470/// The one written value a duration is, as music21's `type` and `dots`
3471/// read it, and the errors `typeToMusicXMLType` raises for the rest.
3472fn written_type(duration: &Duration) -> Result<(&'static str, u32)> {
3473    let components = duration.components();
3474    match components.as_slice() {
3475        [(duration_type, dots)] => Ok((musicxml_type(*duration_type)?, *dots)),
3476        [] if duration.quarter_length() == 0.0 => musicxml_type(DurationType::Zero).map(|t| (t, 0)),
3477        [] => Err(export_error(
3478            "Cannot convert inexpressible durations to MusicXML.",
3479        )),
3480        _ => Err(export_error(
3481            "Cannot convert complex durations to MusicXML. Try exporting with \
3482             makeNotation=True or manually running splitAtDurations()",
3483        )),
3484    }
3485}
3486
3487/// The `<time-modification>` a duration inside tuplets carries.
3488fn time_modification(duration: &Duration) -> Result<Option<Element>> {
3489    let tuplets = duration.tuplets();
3490    match tuplets.as_slice() {
3491        [] => Ok(None),
3492        [tuplet] => tuplet_to_time_modification(tuplet).map(Some),
3493        _ => {
3494            let multiplier = duration.aggregate_tuplet_multiplier();
3495            let (numerator, denominator) = (
3496                *multiplier.numer().unwrap_or(&1),
3497                *multiplier.denom().unwrap_or(&1),
3498            );
3499            let composite = Tuplet::ratio(
3500                u32::try_from(denominator).unwrap_or(1),
3501                u32::try_from(numerator).unwrap_or(1),
3502            );
3503            tuplet_to_time_modification(&composite).map(Some)
3504        }
3505    }
3506}
3507
3508/// music21's `tupletToTimeModification`.
3509fn tuplet_to_time_modification(tuplet: &Tuplet) -> Result<Element> {
3510    let mut element = Element::new("time-modification");
3511    element.sub_text("actual-notes", tuplet.actual().to_string());
3512    element.sub_text("normal-notes", tuplet.normal().to_string());
3513    if let Some((duration_type, dots)) = tuplet.duration_normal() {
3514        element.sub_text("normal-type", musicxml_type(duration_type)?);
3515        for _ in 0..dots {
3516            element.sub("normal-dot");
3517        }
3518    }
3519    Ok(element)
3520}
3521
3522/// Every tuplet bracket a duration's tuplets open or close, numbered from
3523/// the outermost.
3524fn tuplet_notations(duration: &Duration) -> Result<Vec<Element>> {
3525    let mut out = Vec::new();
3526    for (index, tuplet) in duration.tuplets().iter().enumerate() {
3527        out.extend(tuplet_elements(tuplet, index + 1)?);
3528    }
3529    Ok(out)
3530}
3531
3532/// music21's `tupletToXmlTuplet`: a bracket's start, with how it is drawn
3533/// and what it shows, or its stop, or both for a tuplet of one note.
3534fn tuplet_elements(tuplet: &Tuplet, number: usize) -> Result<Vec<Element>> {
3535    use crate::duration::{TupletBracket, TupletShow, TupletType};
3536    let types: &[&str] = match tuplet.tuplet_type() {
3537        None => return Ok(Vec::new()),
3538        Some(TupletType::Start) => &["start"],
3539        Some(TupletType::Stop) => &["stop"],
3540        Some(TupletType::StartStop) => &["start", "stop"],
3541    };
3542    let mut out = Vec::new();
3543    for kind in types {
3544        let mut element = Element::new("tuplet");
3545        element.set("type", *kind);
3546        element.set("number", number.to_string());
3547        if *kind == "start" {
3548            element.set("bracket", yes_no(tuplet.bracket() != TupletBracket::None));
3549            if let Some(placement) = tuplet.placement() {
3550                element.set("placement", placement.as_str());
3551            }
3552            let actual = tuplet.actual_show();
3553            let normal = tuplet.normal_show();
3554            let shows_number = |show: Option<TupletShow>| {
3555                matches!(show, Some(TupletShow::Both | TupletShow::Number))
3556            };
3557            let shows_type = |show: Option<TupletShow>| {
3558                matches!(show, Some(TupletShow::Both | TupletShow::Type))
3559            };
3560            if actual.is_none() {
3561                element.set("show-number", "none");
3562            } else if shows_number(actual) && shows_number(normal) {
3563                element.set("show-number", "both");
3564            } else if actual == Some(TupletShow::Both) {
3565                // music21 asks whether the actual count is shown as `both` or
3566                // as `actual`, which it never is.
3567                element.set("show-number", "actual");
3568            }
3569            if shows_type(actual) && shows_type(normal) {
3570                element.set("show-type", "both");
3571            } else if shows_type(actual) {
3572                element.set("show-type", "actual");
3573            }
3574            if tuplet.bracket() == TupletBracket::Slur {
3575                element.set("line-shape", "curved");
3576            }
3577            for (tag, count, value) in [
3578                ("tuplet-actual", tuplet.actual(), tuplet.duration_actual()),
3579                ("tuplet-normal", tuplet.normal(), tuplet.duration_normal()),
3580            ] {
3581                let side = element.sub(tag);
3582                side.sub_text("tuplet-number", count.to_string());
3583                if let Some((duration_type, dots)) = value {
3584                    side.sub_text("tuplet-type", musicxml_type(duration_type)?);
3585                    for _ in 0..dots {
3586                        side.sub("tuplet-dot");
3587                    }
3588                }
3589            }
3590        }
3591        out.push(element);
3592    }
3593    Ok(out)
3594}
3595
3596/// music21's `pitchToXml`.
3597fn pitch_element(pitch: &Pitch) -> Element {
3598    let mut element = Element::new("pitch");
3599    element.sub_text("step", pitch.step().as_char().to_string());
3600    if let Some(accidental) = pitch.written_accidental() {
3601        element.sub_text("alter", num_to_int_or_float(accidental.alter()));
3602    }
3603    element.sub_text("octave", pitch.implicit_octave().to_string());
3604    element
3605}
3606
3607/// The accidental names music21 writes as they stand: its own names, and the
3608/// MusicXML ones it has no name for.
3609const KNOWN_ACCIDENTALS: [&str; 40] = [
3610    "natural",
3611    "sharp",
3612    "double-sharp",
3613    "triple-sharp",
3614    "quadruple-sharp",
3615    "flat",
3616    "double-flat",
3617    "triple-flat",
3618    "quadruple-flat",
3619    "half-sharp",
3620    "one-and-a-half-sharp",
3621    "half-flat",
3622    "one-and-a-half-flat",
3623    "double-sharp-down",
3624    "double-sharp-up",
3625    "flat-flat-down",
3626    "flat-flat-up",
3627    "arrow-down",
3628    "arrow-up",
3629    "other",
3630    "sharp-down",
3631    "sharp-up",
3632    "natural-down",
3633    "natural-up",
3634    "flat-down",
3635    "flat-up",
3636    "slash-quarter-sharp",
3637    "slash-sharp",
3638    "slash-flat",
3639    "double-slash-flat",
3640    "sharp-1",
3641    "sharp-2",
3642    "sharp-3",
3643    "sharp-5",
3644    "flat-1",
3645    "flat-2",
3646    "flat-3",
3647    "flat-4",
3648    "sori",
3649    "koron",
3650];
3651
3652/// music21's `accidentalToMx`.
3653fn accidental_element(accidental: &Accidental, tag: &'static str) -> Result<Element> {
3654    let name = match accidental.name() {
3655        "half-sharp" => "quarter-sharp",
3656        "one-and-a-half-sharp" => "three-quarters-sharp",
3657        "half-flat" => "quarter-flat",
3658        "one-and-a-half-flat" => "three-quarters-flat",
3659        "double-flat" => "flat-flat",
3660        other if KNOWN_ACCIDENTALS.contains(&other) => other,
3661        _ => "other",
3662    };
3663    let mut element = Element::with_text(tag, name);
3664    let style = accidental.display_style();
3665    if matches!(style, "parentheses" | "both") {
3666        element.set("parentheses", "yes");
3667    }
3668    if matches!(style, "bracket" | "both") {
3669        element.set("bracket", "yes");
3670    }
3671    if let Some(color) = accidental.color() {
3672        element.set("color", normalize_color(color)?);
3673    }
3674    Ok(element)
3675}
3676
3677/// music21's `tieToXmlTie`.
3678fn tie_elements(tie: &Tie) -> Vec<Element> {
3679    tie_like("tie", tie)
3680}
3681
3682fn tie_like(tag: &'static str, tie: &Tie) -> Vec<Element> {
3683    let continues = tie.tie_type() == crate::notation::TieType::Continue;
3684    let mut first = Element::new(tag);
3685    first.set(
3686        "type",
3687        if continues {
3688            "stop"
3689        } else {
3690            tie.tie_type().as_str()
3691        },
3692    );
3693    let mut out = vec![first];
3694    if continues {
3695        let mut second = Element::new(tag);
3696        second.set("type", "start");
3697        out.push(second);
3698    }
3699    out
3700}
3701
3702/// music21's `tieToXmlTied`.
3703fn tied_elements(tie: &Tie) -> Vec<Element> {
3704    use crate::notation::{TieStyle, TieType};
3705    if tie.style() == TieStyle::Hidden {
3706        return Vec::new();
3707    }
3708    let mut out = tie_like("tied", tie);
3709    let last = out.last_mut().expect("a tie writes at least one element");
3710    if tie.style() != TieStyle::Normal && tie.tie_type() != TieType::Stop {
3711        last.set("line-type", tie.style().as_str());
3712    }
3713    if let Some(placement) = tie.placement() {
3714        last.set("placement", placement.as_str());
3715        last.set(
3716            "orientation",
3717            match placement {
3718                Placement::Above => "over",
3719                Placement::Below => "under",
3720            },
3721        );
3722    }
3723    out
3724}
3725
3726/// music21's `dealWithNotehead` and `noteheadToXml`.
3727fn notehead_for(note: &Note, chord: Option<&Chord>) -> Result<Option<Element>> {
3728    let marked =
3729        |notehead: Notehead, parenthesis: bool, fill: Option<bool>, color: Option<&str>| {
3730            notehead != Notehead::Normal || parenthesis || fill.is_some() || color.is_some()
3731        };
3732    let (notehead, parenthesis, fill, color) = if marked(
3733        note.notehead(),
3734        note.notehead_parenthesis(),
3735        note.notehead_fill(),
3736        note.color(),
3737    ) {
3738        (
3739            note.notehead(),
3740            note.notehead_parenthesis(),
3741            note.notehead_fill(),
3742            note.color(),
3743        )
3744    } else if let Some(chord) = chord
3745        && marked(
3746            chord.notehead(),
3747            chord.notehead_parenthesis(),
3748            chord.notehead_fill(),
3749            chord.color(),
3750        )
3751    {
3752        (
3753            chord.notehead(),
3754            chord.notehead_parenthesis(),
3755            chord.notehead_fill(),
3756            chord.color(),
3757        )
3758    } else {
3759        return Ok(None);
3760    };
3761    let mut element = Element::with_text("notehead", notehead.as_str());
3762    if let Some(fill) = fill {
3763        element.set("filled", yes_no(fill));
3764    }
3765    element.set("parentheses", yes_no(parenthesis));
3766    if let Some(color) = color {
3767        element.set("color", normalize_color(color)?);
3768    }
3769    Ok(Some(element))
3770}
3771
3772fn yes_no(value: bool) -> &'static str {
3773    if value { "yes" } else { "no" }
3774}
3775
3776/// music21's `beamsToXml`.
3777fn beam_elements(beams: &Beams) -> Result<Vec<Element>> {
3778    beams.beams().iter().map(beam_element).collect()
3779}
3780
3781/// music21's `beamToXml`.
3782fn beam_element(beam: &Beam) -> Result<Element> {
3783    let text = match beam.beam_type() {
3784        Some(BeamType::Start) => "begin",
3785        Some(BeamType::Continue) => "continue",
3786        Some(BeamType::Stop) => "end",
3787        Some(BeamType::PartialBeam) => match beam.direction() {
3788            Some(BeamDirection::Left) => "backward hook",
3789            Some(BeamDirection::Right) => "forward hook",
3790            None => {
3791                return Err(export_error(
3792                    "partial beam defined without a proper direction set (set to None)",
3793                ));
3794            }
3795        },
3796        None => {
3797            return Err(export_error("unexpected beam type encountered (None)"));
3798        }
3799    };
3800    let mut element = Element::with_text("beam", text);
3801    element.set(
3802        "number",
3803        beam.number()
3804            .map_or_else(|| "None".to_string(), |number| number.to_string()),
3805    );
3806    Ok(element)
3807}
3808
3809/// music21's `lyricToXml`.
3810fn lyric_element(lyric: &Lyric) -> Result<Element> {
3811    let mut element = Element::new("lyric");
3812    let write_syllable = |element: &mut Element, lyric: &Lyric| {
3813        if let Some(syllabic) = lyric.explicit_syllabic() {
3814            element.sub_text("syllabic", syllabic.as_str());
3815        }
3816        element.sub_text("text", lyric.explicit_text().unwrap_or_default());
3817    };
3818    if lyric.is_composite() {
3819        for (index, component) in lyric.components().iter().enumerate() {
3820            if component.is_composite() {
3821                continue;
3822            }
3823            if index >= 1 {
3824                let elision = element.sub("elision");
3825                if !component.elision_before().is_empty() {
3826                    elision.set_text(component.elision_before());
3827                }
3828            }
3829            write_syllable(&mut element, component);
3830        }
3831    } else {
3832        write_syllable(&mut element, lyric);
3833    }
3834    element.set("name", lyric.identifier());
3835    element.set("number", lyric.number().to_string());
3836    if let Some(justify) = lyric.justify() {
3837        element.set("justify", justify.as_str());
3838    }
3839    if let Some(placement) = lyric.placement() {
3840        element.set("placement", placement.as_str());
3841    }
3842    if lyric.is_hidden() {
3843        element.set("print-object", "no");
3844    }
3845    if let Some(color) = lyric.color() {
3846        element.set("color", normalize_color(color)?);
3847    }
3848    Ok(element)
3849}
3850
3851/// The `<key>` for a key or a key signature, and nothing for anything else.
3852fn key_element(element: &StreamElement) -> Option<Result<Element>> {
3853    match element {
3854        StreamElement::KeySignature(signature) => Some(key_signature_element(signature, None)),
3855        StreamElement::Key(key) => {
3856            let mut signature = key.key_signature();
3857            signature.set_color(key.color().map(str::to_string));
3858            Some(key_signature_element(&signature, Some(key.mode())))
3859        }
3860        _ => None,
3861    }
3862}
3863
3864/// music21's `keySignatureToXml`, with the mode a `Key` carries.
3865fn key_signature_element(key: &KeySignature, mode: Option<&str>) -> Result<Element> {
3866    let mut element = Element::new("key");
3867    if let Some(color) = key.color() {
3868        element.set("color", normalize_color(color)?);
3869    }
3870    let altered = key.altered_pitches()?;
3871    match key.sharps().filter(|_| !key.is_non_traditional()) {
3872        Some(sharps) => {
3873            element.sub_text("fifths", sharps.to_string());
3874            if let Some(mode) = mode {
3875                element.sub_text("mode", mode);
3876            }
3877        }
3878        None => {
3879            for pitch in &altered {
3880                element.sub_text("key-step", pitch.step().as_char().to_string());
3881                let alter = pitch.written_accidental().map_or(0.0, Accidental::alter);
3882                element.sub_text("key-alter", num_to_int_or_float(alter));
3883            }
3884        }
3885    }
3886    for (index, pitch) in altered.iter().enumerate() {
3887        if let Some(octave) = pitch.octave() {
3888            let mut octave_element = Element::with_text("key-octave", octave.to_string());
3889            octave_element.set("number", (index + 1).to_string());
3890            element.push(octave_element);
3891        }
3892    }
3893    Ok(element)
3894}
3895
3896/// music21's `timeSignatureToXml`.
3897fn time_signature_element(meter: &TimeSignature) -> Result<Element> {
3898    let mut element = Element::new("time");
3899    if let Some(color) = meter.color() {
3900        element.set("color", normalize_color(color)?);
3901    }
3902    let parts: Vec<(String, String)> = meter
3903        .display_sequence()
3904        .flattened()
3905        .iter()
3906        .map(|part| (part.numerator().to_string(), part.denominator().to_string()))
3907        .collect();
3908    let parts = if meter.summed_numerator() {
3909        fraction_to_slash_mixed(parts)
3910    } else {
3911        parts
3912    };
3913    for (beats, beat_type) in parts {
3914        element.sub_text("beats", beats);
3915        element.sub_text("beat-type", beat_type);
3916    }
3917    if meter.symbolize_denominator() {
3918        element.set("symbol", "note");
3919    } else if let Some(symbol) = meter.symbol() {
3920        element.set("symbol", symbol);
3921    }
3922    if meter.is_hidden() {
3923        element.set("print-object", "no");
3924    }
3925    Ok(element)
3926}
3927
3928/// music21's `meter.tools.fractionToSlashMixed`: neighbouring parts over one
3929/// denominator written as one, their numerators summed with `+`.
3930fn fraction_to_slash_mixed(parts: Vec<(String, String)>) -> Vec<(String, String)> {
3931    let mut out: Vec<(String, String)> = Vec::new();
3932    for (numerator, denominator) in parts {
3933        match out.last_mut() {
3934            Some((numerators, last)) if *last == denominator => {
3935                numerators.push('+');
3936                numerators.push_str(&numerator);
3937            }
3938            _ => out.push((numerator, denominator)),
3939        }
3940    }
3941    out
3942}
3943
3944/// music21's `clefToXml`.
3945fn clef_element(clef: &Clef) -> Result<Element> {
3946    let mut element = Element::new("clef");
3947    if let Some(color) = clef.color() {
3948        element.set("color", normalize_color(color)?);
3949    }
3950    element.sub_text("sign", clef.sign().unwrap_or("G"));
3951    if let Some(line) = clef.line() {
3952        element.sub_text("line", line.to_string());
3953    }
3954    if clef.octave_change() != 0 {
3955        element.sub_text("clef-octave-change", clef.octave_change().to_string());
3956    }
3957    Ok(element)
3958}
3959
3960/// music21's `intervalToXmlTranspose`.
3961fn transpose_element(interval: &Interval) -> Element {
3962    let generic = interval.generic().directed();
3963    let steps = generic.abs() - 1;
3964    let (mut octave_shift, mut diatonic) = (steps / 7, steps % 7);
3965    let mut chromatic = (interval.semitones().abs() as i64) % 12;
3966    if generic < 0 {
3967        diatonic = -diatonic;
3968        octave_shift = -octave_shift;
3969        chromatic = -chromatic;
3970    }
3971    let mut element = Element::new("transpose");
3972    element.sub_text("diatonic", diatonic.to_string());
3973    element.sub_text("chromatic", chromatic.to_string());
3974    if octave_shift != 0 {
3975        element.sub_text("octave-change", octave_shift.to_string());
3976    }
3977    element
3978}
3979
3980/// music21's `xmlObjects.DYNAMIC_MARKS`.
3981const DYNAMIC_MARKS: [&str; 27] = [
3982    "p",
3983    "pp",
3984    "ppp",
3985    "pppp",
3986    "ppppp",
3987    "pppppp",
3988    "f",
3989    "ff",
3990    "fff",
3991    "ffff",
3992    "fffff",
3993    "ffffff",
3994    "mp",
3995    "mf",
3996    "sf",
3997    "sfp",
3998    "sfpp",
3999    "fp",
4000    "rf",
4001    "rfz",
4002    "sfz",
4003    "sffz",
4004    "fz",
4005    "n",
4006    "pf",
4007    "sfzp",
4008    "other-dynamics",
4009];
4010
4011/// The tag a dynamic mark is written as, which is the mark itself.
4012fn dynamic_tag(mark: &str) -> &'static str {
4013    DYNAMIC_MARKS
4014        .iter()
4015        .find(|known| **known == mark)
4016        .copied()
4017        .unwrap_or("other-dynamics")
4018}
4019
4020/// Whether music21 holds this offset as a `Fraction` rather than a float:
4021/// `opFrac` keeps a float only where its denominator is a power of two
4022/// below 65535.
4023fn is_fraction_offset(offset: FloatType) -> bool {
4024    let scaled = offset * 32768.0;
4025    scaled.fract() != 0.0
4026}
4027
4028/// music21's `normalizeColor`: a hex colour upper-cased, and a CSS colour
4029/// name written as its hex value.
4030fn normalize_color(color: &str) -> Result<String> {
4031    if color.is_empty() {
4032        return Ok(String::new());
4033    }
4034    if color.contains('#') {
4035        return Ok(color.to_uppercase());
4036    }
4037    css_color_hex(color)
4038        .map(str::to_string)
4039        .ok_or_else(|| export_error(format!("{color:?} is not a CSS colour name")))
4040}
4041
4042/// The hex value of a CSS3 colour name, as `#FF0000` for `red`, whatever
4043/// case the name is written in; `None` for a name CSS does not have.
4044///
4045/// ```
4046/// use music21_rs::musicxml::css_color_hex;
4047///
4048/// assert_eq!(css_color_hex("Red"), Some("#FF0000"));
4049/// assert_eq!(css_color_hex("grey"), css_color_hex("gray"));
4050/// assert_eq!(css_color_hex("reddish"), None);
4051/// ```
4052pub fn css_color_hex(name: &str) -> Option<&'static str> {
4053    let name = name.to_ascii_lowercase();
4054    CSS_COLORS
4055        .binary_search_by(|(known, _)| known.cmp(&name.as_str()))
4056        .ok()
4057        .map(|index| CSS_COLORS[index].1)
4058}
4059
4060/// The CSS3 colour names and their hex values, sorted by name: the table
4061/// music21 reads a colour name out of, through `webcolors`.
4062pub const CSS_COLORS: [(&str, &str); 147] = [
4063    ("aliceblue", "#F0F8FF"),
4064    ("antiquewhite", "#FAEBD7"),
4065    ("aqua", "#00FFFF"),
4066    ("aquamarine", "#7FFFD4"),
4067    ("azure", "#F0FFFF"),
4068    ("beige", "#F5F5DC"),
4069    ("bisque", "#FFE4C4"),
4070    ("black", "#000000"),
4071    ("blanchedalmond", "#FFEBCD"),
4072    ("blue", "#0000FF"),
4073    ("blueviolet", "#8A2BE2"),
4074    ("brown", "#A52A2A"),
4075    ("burlywood", "#DEB887"),
4076    ("cadetblue", "#5F9EA0"),
4077    ("chartreuse", "#7FFF00"),
4078    ("chocolate", "#D2691E"),
4079    ("coral", "#FF7F50"),
4080    ("cornflowerblue", "#6495ED"),
4081    ("cornsilk", "#FFF8DC"),
4082    ("crimson", "#DC143C"),
4083    ("cyan", "#00FFFF"),
4084    ("darkblue", "#00008B"),
4085    ("darkcyan", "#008B8B"),
4086    ("darkgoldenrod", "#B8860B"),
4087    ("darkgray", "#A9A9A9"),
4088    ("darkgreen", "#006400"),
4089    ("darkgrey", "#A9A9A9"),
4090    ("darkkhaki", "#BDB76B"),
4091    ("darkmagenta", "#8B008B"),
4092    ("darkolivegreen", "#556B2F"),
4093    ("darkorange", "#FF8C00"),
4094    ("darkorchid", "#9932CC"),
4095    ("darkred", "#8B0000"),
4096    ("darksalmon", "#E9967A"),
4097    ("darkseagreen", "#8FBC8F"),
4098    ("darkslateblue", "#483D8B"),
4099    ("darkslategray", "#2F4F4F"),
4100    ("darkslategrey", "#2F4F4F"),
4101    ("darkturquoise", "#00CED1"),
4102    ("darkviolet", "#9400D3"),
4103    ("deeppink", "#FF1493"),
4104    ("deepskyblue", "#00BFFF"),
4105    ("dimgray", "#696969"),
4106    ("dimgrey", "#696969"),
4107    ("dodgerblue", "#1E90FF"),
4108    ("firebrick", "#B22222"),
4109    ("floralwhite", "#FFFAF0"),
4110    ("forestgreen", "#228B22"),
4111    ("fuchsia", "#FF00FF"),
4112    ("gainsboro", "#DCDCDC"),
4113    ("ghostwhite", "#F8F8FF"),
4114    ("gold", "#FFD700"),
4115    ("goldenrod", "#DAA520"),
4116    ("gray", "#808080"),
4117    ("green", "#008000"),
4118    ("greenyellow", "#ADFF2F"),
4119    ("grey", "#808080"),
4120    ("honeydew", "#F0FFF0"),
4121    ("hotpink", "#FF69B4"),
4122    ("indianred", "#CD5C5C"),
4123    ("indigo", "#4B0082"),
4124    ("ivory", "#FFFFF0"),
4125    ("khaki", "#F0E68C"),
4126    ("lavender", "#E6E6FA"),
4127    ("lavenderblush", "#FFF0F5"),
4128    ("lawngreen", "#7CFC00"),
4129    ("lemonchiffon", "#FFFACD"),
4130    ("lightblue", "#ADD8E6"),
4131    ("lightcoral", "#F08080"),
4132    ("lightcyan", "#E0FFFF"),
4133    ("lightgoldenrodyellow", "#FAFAD2"),
4134    ("lightgray", "#D3D3D3"),
4135    ("lightgreen", "#90EE90"),
4136    ("lightgrey", "#D3D3D3"),
4137    ("lightpink", "#FFB6C1"),
4138    ("lightsalmon", "#FFA07A"),
4139    ("lightseagreen", "#20B2AA"),
4140    ("lightskyblue", "#87CEFA"),
4141    ("lightslategray", "#778899"),
4142    ("lightslategrey", "#778899"),
4143    ("lightsteelblue", "#B0C4DE"),
4144    ("lightyellow", "#FFFFE0"),
4145    ("lime", "#00FF00"),
4146    ("limegreen", "#32CD32"),
4147    ("linen", "#FAF0E6"),
4148    ("magenta", "#FF00FF"),
4149    ("maroon", "#800000"),
4150    ("mediumaquamarine", "#66CDAA"),
4151    ("mediumblue", "#0000CD"),
4152    ("mediumorchid", "#BA55D3"),
4153    ("mediumpurple", "#9370DB"),
4154    ("mediumseagreen", "#3CB371"),
4155    ("mediumslateblue", "#7B68EE"),
4156    ("mediumspringgreen", "#00FA9A"),
4157    ("mediumturquoise", "#48D1CC"),
4158    ("mediumvioletred", "#C71585"),
4159    ("midnightblue", "#191970"),
4160    ("mintcream", "#F5FFFA"),
4161    ("mistyrose", "#FFE4E1"),
4162    ("moccasin", "#FFE4B5"),
4163    ("navajowhite", "#FFDEAD"),
4164    ("navy", "#000080"),
4165    ("oldlace", "#FDF5E6"),
4166    ("olive", "#808000"),
4167    ("olivedrab", "#6B8E23"),
4168    ("orange", "#FFA500"),
4169    ("orangered", "#FF4500"),
4170    ("orchid", "#DA70D6"),
4171    ("palegoldenrod", "#EEE8AA"),
4172    ("palegreen", "#98FB98"),
4173    ("paleturquoise", "#AFEEEE"),
4174    ("palevioletred", "#DB7093"),
4175    ("papayawhip", "#FFEFD5"),
4176    ("peachpuff", "#FFDAB9"),
4177    ("peru", "#CD853F"),
4178    ("pink", "#FFC0CB"),
4179    ("plum", "#DDA0DD"),
4180    ("powderblue", "#B0E0E6"),
4181    ("purple", "#800080"),
4182    ("red", "#FF0000"),
4183    ("rosybrown", "#BC8F8F"),
4184    ("royalblue", "#4169E1"),
4185    ("saddlebrown", "#8B4513"),
4186    ("salmon", "#FA8072"),
4187    ("sandybrown", "#F4A460"),
4188    ("seagreen", "#2E8B57"),
4189    ("seashell", "#FFF5EE"),
4190    ("sienna", "#A0522D"),
4191    ("silver", "#C0C0C0"),
4192    ("skyblue", "#87CEEB"),
4193    ("slateblue", "#6A5ACD"),
4194    ("slategray", "#708090"),
4195    ("slategrey", "#708090"),
4196    ("snow", "#FFFAFA"),
4197    ("springgreen", "#00FF7F"),
4198    ("steelblue", "#4682B4"),
4199    ("tan", "#D2B48C"),
4200    ("teal", "#008080"),
4201    ("thistle", "#D8BFD8"),
4202    ("tomato", "#FF6347"),
4203    ("turquoise", "#40E0D0"),
4204    ("violet", "#EE82EE"),
4205    ("wheat", "#F5DEB3"),
4206    ("white", "#FFFFFF"),
4207    ("whitesmoke", "#F5F5F5"),
4208    ("yellow", "#FFFF00"),
4209    ("yellowgreen", "#9ACD32"),
4210];
4211
4212#[cfg(test)]
4213mod tests {
4214    //! Each expectation is the one music21's own docstring for the method
4215    //! prints, read off `m21ToXml.py`.
4216
4217    use super::*;
4218    use crate::notation::{BeamType, Syllabic, TieStyle, TieType};
4219    use crate::volume::Volume;
4220
4221    static NO_BASES: std::sync::LazyLock<std::collections::HashMap<usize, u32>> =
4222        std::sync::LazyLock::new(std::collections::HashMap::new);
4223
4224    fn with_measure<T>(run: impl FnOnce(&mut MeasureExporter<'_, '_>) -> T) -> T {
4225        let stream = Stream::with_kind(StreamKind::Measure);
4226        let mut context = PartContext {
4227            instruments: &[],
4228            own_instruments: Vec::new(),
4229            own_clefs: Vec::new(),
4230            meter: None,
4231            last_divisions: None,
4232            spanners: Vec::new(),
4233            clef: None,
4234            previous: None,
4235            voice_bases: &NO_BASES,
4236            modulations: Vec::new(),
4237        };
4238        let mut exporter = MeasureExporter::new(&stream, 0.0, &mut context);
4239        run(&mut exporter)
4240    }
4241
4242    #[test]
4243    fn a_segno_and_a_coda_are_signs_and_the_rest_are_words() {
4244        // music21's `segnoToXml` and `codaToXml` docstrings.
4245        let sign = |kind| {
4246            with_measure(|exporter| {
4247                exporter
4248                    .repeat_expression_element(&RepeatExpression::new(kind), 0.0)
4249                    .dump()
4250            })
4251        };
4252        assert_eq!(
4253            sign(RepeatExpressionKind::Segno),
4254            "<direction>
4255  <direction-type>
4256    <segno default-y=\"20\" />
4257  </direction-type>
4258</direction>"
4259        );
4260        assert_eq!(
4261            sign(RepeatExpressionKind::Coda),
4262            "<direction>
4263  <direction-type>
4264    <coda default-y=\"20\" />
4265  </direction-type>
4266</direction>"
4267        );
4268        assert_eq!(
4269            sign(RepeatExpressionKind::Fine),
4270            "<direction>
4271  <direction-type>
4272    <words justify=\"right\">fine</words>
4273  </direction-type>
4274</direction>"
4275        );
4276    }
4277
4278    #[test]
4279    fn a_pedal_goes_down_and_comes_up() {
4280        use crate::spanner::Pedal;
4281        // music21's `_spannerStartParameters` and `_spannerEndParameters`.
4282        let pedal = |pedal_type, form| {
4283            Spanner::pedal_mark(
4284                Pedal {
4285                    pedal_type,
4286                    form,
4287                    abbreviated: false,
4288                },
4289                vec![0, 1],
4290            )
4291        };
4292        let symbol = pedal(Some(PedalType::Sustain), Some(PedalForm::Symbol));
4293        assert_eq!(
4294            pedal_element(&symbol, 1, true).dump(),
4295            "<pedal number=\"1\" sign=\"yes\" type=\"start\" />"
4296        );
4297        assert_eq!(
4298            pedal_element(&symbol, 1, false).dump(),
4299            "<pedal number=\"1\" sign=\"yes\" type=\"stop\" />"
4300        );
4301        let line = pedal(Some(PedalType::Sostenuto), Some(PedalForm::Line));
4302        assert_eq!(
4303            pedal_element(&line, 2, true).dump(),
4304            "<pedal line=\"yes\" number=\"2\" type=\"sostenuto\" />"
4305        );
4306        assert_eq!(
4307            pedal_element(&line, 2, false).dump(),
4308            "<pedal line=\"yes\" number=\"2\" type=\"stop\" />"
4309        );
4310    }
4311
4312    #[test]
4313    fn glissandi_a_tremolo_and_a_pedal_with_a_bounce_are_written_as_music21_writes_them() {
4314        use crate::spanner::{Glissando, Pedal};
4315        // Read off music21's own export of the same two bars.
4316        let bar = |number, names: [&str; 4]| {
4317            let mut measure = Stream::with_kind(StreamKind::Measure);
4318            measure.set_number(number);
4319            for name in names {
4320                measure.push(Note::from_name(name).unwrap());
4321            }
4322            measure
4323        };
4324        let mut first = bar(1, ["C4", "D4", "E4", "F4"]);
4325        first.insert(3.0, PedalObject::new(PedalObjectKind::Bounce));
4326        let mut part = Stream::with_kind(StreamKind::Part);
4327        part.push(first);
4328        part.push(bar(2, ["G4", "A4", "B4", "C5"]));
4329        let mut score = Stream::with_kind(StreamKind::Score);
4330        score.push(part);
4331        // The notes of the first bar stand at 0 to 3, the bounce at 4, and
4332        // the notes of the second at 5 to 8.
4333        let kinds: Vec<bool> = score
4334            .leaves()
4335            .iter()
4336            .map(|(_, element)| matches!(element, StreamElement::PedalObject(_)))
4337            .collect();
4338        assert_eq!(kinds.iter().position(|bounce| *bounce), Some(4));
4339
4340        // A pedal from the second note to the seventh, its bounce joined
4341        // last, so that is where it comes up.
4342        let pedal = Spanner::pedal_mark(
4343            Pedal {
4344                pedal_type: Some(PedalType::Sustain),
4345                form: Some(PedalForm::SymbolLine),
4346                abbreviated: false,
4347            },
4348            vec![1, 7, 4],
4349        );
4350        score.add_spanner(pedal);
4351        score.add_spanner(Spanner::glissando(vec![2, 3]));
4352        let mut slide = Spanner::glissando(vec![5, 6]);
4353        slide.set_glissando_details(Some(Glissando {
4354            slide_type: SlideType::Continuous,
4355            label: Some("gl.".to_string()),
4356        }));
4357        score.add_spanner(slide);
4358        let mut tremolo = Spanner::tremolo(3, vec![7, 8]).unwrap();
4359        tremolo.set_placement(Some(Placement::Above));
4360        score.add_spanner(tremolo);
4361
4362        let written = to_musicxml(&score, &ExportOptions::default()).unwrap();
4363        let direction = |pedal: &str, offset: &str| {
4364            format!(
4365                "      <direction>\n        <direction-type>\n          {pedal}\n        \
4366                 </direction-type>\n{offset}      </direction>\n"
4367            )
4368        };
4369        // The sign, and the line resumed where the pedal mark stands: the
4370        // start of the part, a quarter before the note it goes down on.
4371        assert!(written.contains(&format!(
4372            "{}{}",
4373            direction("<pedal number=\"1\" sign=\"yes\" type=\"start\" />", ""),
4374            direction(
4375                "<pedal line=\"yes\" type=\"resume\" />",
4376                "        <offset sound=\"yes\">-10080</offset>\n"
4377            )
4378        )));
4379        // The bounce, and after it the pedal coming up.
4380        assert!(written.contains(&format!(
4381            "{}{}",
4382            direction("<pedal line=\"yes\" type=\"change\" />", ""),
4383            direction("<pedal line=\"yes\" number=\"1\" type=\"stop\" />", "")
4384        )));
4385        for notation in [
4386            "<glissando line-type=\"wavy\" number=\"1\" type=\"start\" />",
4387            "<glissando line-type=\"wavy\" number=\"1\" type=\"stop\" />",
4388            "<slide line-type=\"wavy\" number=\"2\" type=\"start\">gl.</slide>",
4389            "<slide line-type=\"wavy\" number=\"2\" type=\"stop\" />",
4390            "<tremolo placement=\"above\" type=\"start\">3</tremolo>",
4391            "<tremolo type=\"stop\">3</tremolo>",
4392        ] {
4393            assert!(written.contains(notation), "{notation} in\n{written}");
4394        }
4395    }
4396
4397    #[test]
4398    fn a_bounce_is_written_as_its_pedal_is_drawn() {
4399        use crate::spanner::Pedal;
4400        // music21's `pedalObjectToXml`.
4401        let written = |form, pedal_type, kind| {
4402            let mut measure = Stream::with_kind(StreamKind::Measure);
4403            measure.set_number(1);
4404            measure.push(Note::from_name("C4").unwrap());
4405            measure.insert(1.0, PedalObject::new(kind));
4406            measure.push(Note::from_name("D4").unwrap());
4407            let mut part = Stream::with_kind(StreamKind::Part);
4408            part.push(measure);
4409            part.add_spanner(Spanner::pedal_mark(
4410                Pedal {
4411                    pedal_type,
4412                    form: Some(form),
4413                    abbreviated: false,
4414                },
4415                vec![0, 1, 2],
4416            ));
4417            let mut score = Stream::with_kind(StreamKind::Score);
4418            score.push(part);
4419            let document = to_musicxml(&score, &ExportOptions::default()).unwrap();
4420            document
4421                .lines()
4422                .map(str::trim)
4423                .filter(|line| line.starts_with("<pedal") && !line.contains("number"))
4424                .collect::<Vec<_>>()
4425                .join("")
4426        };
4427        let sustain = Some(PedalType::Sustain);
4428        assert_eq!(
4429            written(PedalForm::Line, sustain, PedalObjectKind::Bounce),
4430            "<pedal line=\"yes\" type=\"change\" />"
4431        );
4432        assert_eq!(
4433            written(PedalForm::Symbol, sustain, PedalObjectKind::Bounce),
4434            "<pedal sign=\"yes\" type=\"stop\" /><pedal sign=\"yes\" type=\"start\" />"
4435        );
4436        assert_eq!(
4437            written(
4438                PedalForm::SymbolAlt,
4439                Some(PedalType::Sostenuto),
4440                PedalObjectKind::Bounce
4441            ),
4442            "<pedal sign=\"yes\" type=\"sostenuto\" />"
4443        );
4444        assert_eq!(
4445            written(
4446                PedalForm::Symbol,
4447                Some(PedalType::Soft),
4448                PedalObjectKind::Bounce
4449            ),
4450            "<pedal sign=\"yes\" type=\"stop\" /><pedal sign=\"yes\" type=\"sustain\" />"
4451        );
4452        assert_eq!(
4453            written(PedalForm::Line, sustain, PedalObjectKind::GapStart),
4454            "<pedal line=\"yes\" type=\"discontinue\" />"
4455        );
4456        assert_eq!(
4457            written(PedalForm::Symbol, sustain, PedalObjectKind::GapEnd),
4458            "<pedal sign=\"yes\" type=\"resume\" />"
4459        );
4460    }
4461
4462    #[test]
4463    fn a_mark_at_a_triplet_offset_always_says_its_offset() {
4464        // music21 holds 8/3 as a Fraction, which equals no float, so the
4465        // offset is written even where it comes to nothing.
4466        with_measure(|exporter| {
4467            exporter.offset_in_measure = 8.0 / 3.0;
4468            let written = exporter.offset_element(8.0 / 3.0, true).unwrap();
4469            assert_eq!(written.dump(), "<offset sound=\"yes\">0</offset>");
4470            exporter.offset_in_measure = 2.5;
4471            assert!(exporter.offset_element(2.5, true).is_none());
4472        });
4473    }
4474
4475    #[test]
4476    fn a_stroke_is_written_where_it_is_displayed() {
4477        // music21's `unpitchedToXml` docstring.
4478        let stroke = crate::percussion::Unpitched::from_display_name("D5")
4479            .unwrap()
4480            .with_duration(Duration::quarter());
4481        let written = with_measure(|exporter| {
4482            exporter
4483                .written_note_element(stroke.written(), 0, None, None, true)
4484                .unwrap()
4485        });
4486        assert_eq!(
4487            written.dump(),
4488            [
4489                "<note>",
4490                "  <unpitched>",
4491                "    <display-step>D</display-step>",
4492                "    <display-octave>5</display-octave>",
4493                "  </unpitched>",
4494                "  <duration>10080</duration>",
4495                "  <type>quarter</type>",
4496                "</note>",
4497            ]
4498            .join(
4499                "
4500"
4501            )
4502        );
4503    }
4504
4505    #[test]
4506    fn a_colour_is_written_in_hex() {
4507        assert_eq!(normalize_color("").unwrap(), "");
4508        assert_eq!(normalize_color("red").unwrap(), "#FF0000");
4509        assert_eq!(normalize_color("#00ff00").unwrap(), "#00FF00");
4510        assert!(normalize_color("reddish").is_err());
4511    }
4512
4513    #[test]
4514    fn a_note_writes_as_music21_writes_it() {
4515        let mut note = Note::from_name("D#5")
4516            .unwrap()
4517            .with_duration(Duration::new(3.0).unwrap());
4518        note.set_volume(Some(Volume::from_velocity_scalar(0.5).unwrap()));
4519        note.set_color(Some("#C0C0C0".to_string()));
4520        let xml =
4521            with_measure(|exporter| exporter.note_element(&note, 0, None, None).unwrap()).dump();
4522        assert_eq!(
4523            xml,
4524            "<note color=\"#C0C0C0\" dynamics=\"70.56\">
4525  <pitch>
4526    <step>D</step>
4527    <alter>1</alter>
4528    <octave>5</octave>
4529  </pitch>
4530  <duration>30240</duration>
4531  <type>half</type>
4532  <dot />
4533  <accidental>sharp</accidental>
4534  <notehead color=\"#C0C0C0\" parentheses=\"no\">normal</notehead>
4535</note>"
4536        );
4537    }
4538
4539    #[test]
4540    fn a_chord_writes_a_note_for_each_of_its_notes() {
4541        let chord = Chord::new("A-2 D3 E4")
4542            .unwrap()
4543            .with_duration(Duration::half());
4544        let root = with_measure(|exporter| {
4545            exporter.chord_elements(&chord, None).unwrap();
4546            exporter.root.clone()
4547        });
4548        assert_eq!(
4549            root.dump(),
4550            "<measure>
4551  <note>
4552    <pitch>
4553      <step>A</step>
4554      <alter>-1</alter>
4555      <octave>2</octave>
4556    </pitch>
4557    <duration>20160</duration>
4558    <type>half</type>
4559    <accidental>flat</accidental>
4560  </note>
4561  <note>
4562    <chord />
4563    <pitch>
4564      <step>D</step>
4565      <octave>3</octave>
4566    </pitch>
4567    <duration>20160</duration>
4568    <type>half</type>
4569  </note>
4570  <note>
4571    <chord />
4572    <pitch>
4573      <step>E</step>
4574      <octave>4</octave>
4575    </pitch>
4576    <duration>20160</duration>
4577    <type>half</type>
4578  </note>
4579</measure>"
4580        );
4581    }
4582
4583    #[test]
4584    fn a_rest_filling_its_bar_is_a_measure_rest() {
4585        let mut measure = Stream::with_kind(StreamKind::Measure);
4586        measure.insert(0.0, TimeSignature::new(4, 4).unwrap());
4587        let rest = Rest::new(Duration::half());
4588        let mut context = PartContext {
4589            instruments: &[],
4590            own_instruments: Vec::new(),
4591            own_clefs: Vec::new(),
4592            meter: None,
4593            last_divisions: None,
4594            spanners: Vec::new(),
4595            clef: None,
4596            previous: None,
4597            voice_bases: &NO_BASES,
4598            modulations: Vec::new(),
4599        };
4600        let exporter = MeasureExporter::new(&measure, 0.0, &mut context);
4601        assert_eq!(
4602            exporter.rest_element(&rest, 0.0, None).unwrap().dump(),
4603            "<note>\n  <rest />\n  <duration>20160</duration>\n  <type>half</type>\n</note>"
4604        );
4605
4606        let mut measure = Stream::with_kind(StreamKind::Measure);
4607        measure.insert(0.0, TimeSignature::new(2, 4).unwrap());
4608        let exporter = MeasureExporter::new(&measure, 0.0, &mut context);
4609        assert_eq!(
4610            exporter.rest_element(&rest, 0.0, None).unwrap().dump(),
4611            "<note>\n  <rest measure=\"yes\" />\n  <duration>20160</duration>\n</note>"
4612        );
4613    }
4614
4615    #[test]
4616    fn a_triplet_carries_its_time_modification() {
4617        let rest = Rest::new(Duration::new(1.0 / 3.0).unwrap());
4618        let xml = with_measure(|exporter| exporter.rest_element(&rest, 0.0, None).unwrap()).dump();
4619        assert_eq!(
4620            xml,
4621            "<note>
4622  <rest />
4623  <duration>3360</duration>
4624  <type>eighth</type>
4625  <time-modification>
4626    <actual-notes>3</actual-notes>
4627    <normal-notes>2</normal-notes>
4628    <normal-type>eighth</normal-type>
4629  </time-modification>
4630</note>"
4631        );
4632    }
4633
4634    #[test]
4635    fn a_duration_needing_a_tie_is_refused() {
4636        let note = Note::from_name("C4")
4637            .unwrap()
4638            .with_duration(Duration::new(5.0).unwrap());
4639        let error =
4640            with_measure(|exporter| exporter.note_element(&note, 0, None, None).unwrap_err());
4641        assert_eq!(
4642            error,
4643            Error::MusicXml(
4644                "Cannot convert complex durations to MusicXML. Try exporting with \
4645                 makeNotation=True or manually running splitAtDurations()"
4646                    .to_string()
4647            )
4648        );
4649    }
4650
4651    #[test]
4652    fn moving_forward_and_back_counts_divisions() {
4653        let root = with_measure(|exporter| {
4654            exporter.move_forward(1.0);
4655            exporter.move_backward(2.0);
4656            exporter.root.clone()
4657        });
4658        assert_eq!(
4659            root.dump(),
4660            "<measure>
4661  <forward>
4662    <duration>10080</duration>
4663  </forward>
4664  <backup>
4665    <duration>20160</duration>
4666  </backup>
4667</measure>"
4668        );
4669    }
4670
4671    #[test]
4672    fn ties_are_written_twice_over() {
4673        let tie = Tie::new(TieType::Continue);
4674        let written: Vec<String> = tie_elements(&tie).iter().map(Element::dump).collect();
4675        assert_eq!(written, ["<tie type=\"stop\" />", "<tie type=\"start\" />"]);
4676        let tied: Vec<String> = tied_elements(&tie).iter().map(Element::dump).collect();
4677        assert_eq!(tied, ["<tied type=\"stop\" />", "<tied type=\"start\" />"]);
4678        let mut hidden = Tie::new(TieType::Continue);
4679        hidden.set_style(TieStyle::Hidden);
4680        assert!(tied_elements(&hidden).is_empty());
4681    }
4682
4683    #[test]
4684    fn beams_name_their_ends() {
4685        let mut beam = Beam::new(BeamType::Start, None);
4686        beam.set_number(Some(1));
4687        assert_eq!(
4688            beam_element(&beam).unwrap().dump(),
4689            "<beam number=\"1\">begin</beam>"
4690        );
4691        beam.set_beam_type(Some(BeamType::Stop));
4692        assert_eq!(
4693            beam_element(&beam).unwrap().dump(),
4694            "<beam number=\"1\">end</beam>"
4695        );
4696        beam.set_beam_type(Some(BeamType::PartialBeam));
4697        beam.set_direction(Some(BeamDirection::Left));
4698        assert_eq!(
4699            beam_element(&beam).unwrap().dump(),
4700            "<beam number=\"1\">backward hook</beam>"
4701        );
4702        beam.set_direction(None);
4703        assert_eq!(
4704            beam_element(&beam).unwrap_err(),
4705            Error::MusicXml(
4706                "partial beam defined without a proper direction set (set to None)".to_string()
4707            )
4708        );
4709    }
4710
4711    #[test]
4712    fn key_signatures_traditional_and_not() {
4713        assert_eq!(
4714            key_signature_element(&KeySignature::new(-3), None)
4715                .unwrap()
4716                .dump(),
4717            "<key>\n  <fifths>-3</fifths>\n</key>"
4718        );
4719        let pitches = vec![
4720            Pitch::from_name("C#").unwrap(),
4721            Pitch::from_name("E-4").unwrap(),
4722        ];
4723        assert_eq!(
4724            key_signature_element(&KeySignature::from_altered_pitches(pitches), None)
4725                .unwrap()
4726                .dump(),
4727            "<key>
4728  <key-step>C</key-step>
4729  <key-alter>1</key-alter>
4730  <key-step>E</key-step>
4731  <key-alter>-1</key-alter>
4732  <key-octave number=\"2\">4</key-octave>
4733</key>"
4734        );
4735    }
4736
4737    #[test]
4738    fn a_meter_written_in_parts_writes_each() {
4739        assert_eq!(
4740            time_signature_element(&TimeSignature::new(3, 4).unwrap())
4741                .unwrap()
4742                .dump(),
4743            "<time>\n  <beats>3</beats>\n  <beat-type>4</beat-type>\n</time>"
4744        );
4745        assert_eq!(
4746            time_signature_element(&TimeSignature::from_ratio_string("3/4+2/4").unwrap())
4747                .unwrap()
4748                .dump(),
4749            "<time>
4750  <beats>3</beats>
4751  <beat-type>4</beat-type>
4752  <beats>2</beats>
4753  <beat-type>4</beat-type>
4754</time>"
4755        );
4756    }
4757
4758    #[test]
4759    fn clefs_write_their_sign_line_and_octave() {
4760        assert_eq!(
4761            clef_element(&Clef::of_kind(crate::clef::ClefKind::GClef))
4762                .unwrap()
4763                .dump(),
4764            "<clef>\n  <sign>G</sign>\n</clef>"
4765        );
4766        assert_eq!(
4767            clef_element(&Clef::of_kind(crate::clef::ClefKind::Treble8vbClef))
4768                .unwrap()
4769                .dump(),
4770            "<clef>
4771  <sign>G</sign>
4772  <line>2</line>
4773  <clef-octave-change>-1</clef-octave-change>
4774</clef>"
4775        );
4776        assert_eq!(
4777            clef_element(&Clef::of_kind(crate::clef::ClefKind::PercussionClef))
4778                .unwrap()
4779                .dump(),
4780            "<clef>\n  <sign>percussion</sign>\n</clef>"
4781        );
4782    }
4783
4784    #[test]
4785    fn a_transposition_is_steps_semitones_and_octaves() {
4786        let dump = |name: &str| transpose_element(&Interval::from_name(name).unwrap()).dump();
4787        assert_eq!(
4788            dump("P5"),
4789            "<transpose>\n  <diatonic>4</diatonic>\n  <chromatic>7</chromatic>\n</transpose>"
4790        );
4791        assert_eq!(
4792            dump("A13"),
4793            "<transpose>
4794  <diatonic>5</diatonic>
4795  <chromatic>10</chromatic>
4796  <octave-change>1</octave-change>
4797</transpose>"
4798        );
4799        assert_eq!(
4800            dump("-M9"),
4801            "<transpose>
4802  <diatonic>-1</diatonic>
4803  <chromatic>-2</chromatic>
4804  <octave-change>-1</octave-change>
4805</transpose>"
4806        );
4807    }
4808
4809    #[test]
4810    fn a_dynamic_is_a_direction_with_its_loudness() {
4811        let root = with_measure(|exporter| {
4812            exporter.dynamic_element(&Dynamic::new("ppp"), 1.0);
4813            exporter.root.clone()
4814        });
4815        assert_eq!(
4816            root.dump(),
4817            "<measure>
4818  <direction>
4819    <direction-type>
4820      <dynamics default-x=\"-36\" default-y=\"-80\">
4821        <ppp />
4822      </dynamics>
4823    </direction-type>
4824    <offset sound=\"yes\">10080</offset>
4825    <sound dynamics=\"19\" />
4826  </direction>
4827</measure>"
4828        );
4829    }
4830
4831    #[test]
4832    fn a_metronome_mark_writes_its_beat_and_its_word() {
4833        let mark =
4834            MetronomeMark::with_number_and_text(40.0, "slow").with_referent(Duration::half());
4835        let root = with_measure(|exporter| {
4836            exporter.tempo_elements(&mark, 0.0).unwrap();
4837            exporter.root.clone()
4838        });
4839        assert_eq!(
4840            root.dump(),
4841            "<measure>
4842  <direction>
4843    <direction-type>
4844      <metronome parentheses=\"no\">
4845        <beat-unit>half</beat-unit>
4846        <per-minute>40</per-minute>
4847      </metronome>
4848    </direction-type>
4849    <sound tempo=\"80\" />
4850  </direction>
4851  <direction>
4852    <direction-type>
4853      <words default-y=\"45\" font-weight=\"bold\">slow</words>
4854    </direction-type>
4855  </direction>
4856</measure>"
4857        );
4858
4859        let sounding = MetronomeMark::default().with_number_sounding(60.0);
4860        let root = with_measure(|exporter| {
4861            exporter.tempo_elements(&sounding, 0.0).unwrap();
4862            exporter.root.clone()
4863        });
4864        assert_eq!(
4865            root.dump(),
4866            "<measure>
4867  <direction>
4868    <direction-type>
4869      <words />
4870    </direction-type>
4871    <sound tempo=\"60\" />
4872  </direction>
4873</measure>"
4874        );
4875    }
4876
4877    #[test]
4878    fn a_chord_symbol_is_a_harmony() {
4879        let dump = |figure: &str| {
4880            let symbol = ChordSymbol::parse_music21(figure).unwrap();
4881            with_measure(|exporter| exporter.harmony_element(&symbol, 0.0).unwrap()).dump()
4882        };
4883        // MusicXML calls a dominant seventh `dominant`.
4884        assert_eq!(
4885            dump("C7"),
4886            "<harmony>
4887  <root>
4888    <root-step>C</root-step>
4889  </root>
4890  <kind>dominant</kind>
4891</harmony>"
4892        );
4893        assert_eq!(
4894            dump("F sus add 9"),
4895            "<harmony>
4896  <root>
4897    <root-step>F</root-step>
4898  </root>
4899  <kind>suspended-fourth</kind>
4900  <degree>
4901    <degree-value>9</degree-value>
4902    <degree-alter>0</degree-alter>
4903    <degree-type>add</degree-type>
4904  </degree>
4905</harmony>"
4906        );
4907        let no_chord = ChordSymbol::no_chord(None);
4908        assert_eq!(
4909            with_measure(|exporter| exporter.harmony_element(&no_chord, 0.0).unwrap()).dump(),
4910            "<harmony>
4911  <root>
4912    <root-step text=\"\">C</root-step>
4913  </root>
4914  <kind text=\"N.C.\">none</kind>
4915</harmony>"
4916        );
4917    }
4918
4919    #[test]
4920    fn a_lyric_names_its_verse() {
4921        let mut lyric = Lyric::new("hel");
4922        lyric.set_syllabic(Syllabic::Begin);
4923        assert_eq!(
4924            lyric_element(&lyric).unwrap().dump(),
4925            "<lyric name=\"1\" number=\"1\">
4926  <syllabic>begin</syllabic>
4927  <text>hel</text>
4928</lyric>"
4929        );
4930    }
4931
4932    #[test]
4933    fn a_whole_score_writes_as_a_music21_fragment() {
4934        let mut measure = Stream::with_kind(StreamKind::Measure);
4935        measure.set_number(1);
4936        measure.insert(0.0, TimeSignature::new(1, 4).unwrap());
4937        measure.insert(0.0, Clef::treble());
4938        measure.push(Note::from_name("D#4").unwrap());
4939        let mut part = Stream::with_kind(StreamKind::Part);
4940        part.push(measure);
4941        let mut score = Stream::with_kind(StreamKind::Score);
4942        score.push(part);
4943        let options = ExportOptions {
4944            encoding_date: Some("2026-09-28".to_string()),
4945            software: "music21 v.11.0.0".to_string(),
4946            ..ExportOptions::default()
4947        };
4948        assert_eq!(
4949            to_musicxml(&score, &options).unwrap(),
4950            "<?xml version=\"1.0\" encoding=\"utf-8\"?>
4951<!DOCTYPE score-partwise  PUBLIC \"-//Recordare//DTD MusicXML 4.0 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">
4952<score-partwise version=\"4.0\">
4953  <movement-title>Music21 Fragment</movement-title>
4954  <identification>
4955    <creator type=\"composer\">Music21</creator>
4956    <encoding>
4957      <encoding-date>2026-09-28</encoding-date>
4958      <software>music21 v.11.0.0</software>
4959      <supports element=\"beam\" type=\"yes\" />
4960      <supports element=\"stem\" type=\"yes\" />
4961      <supports element=\"accidental\" type=\"yes\" />
4962    </encoding>
4963  </identification>
4964  <defaults>
4965    <scaling>
4966      <millimeters>7</millimeters>
4967      <tenths>40</tenths>
4968    </scaling>
4969  </defaults>
4970  <part-list>
4971    <score-part id=\"P1\">
4972      <part-name />
4973    </score-part>
4974  </part-list>
4975  <!--=========================== Part 1 ===========================-->
4976  <part id=\"P1\">
4977    <!--========================= Measure 1 ==========================-->
4978    <measure implicit=\"no\" number=\"1\">
4979      <attributes>
4980        <divisions>10080</divisions>
4981        <time>
4982          <beats>1</beats>
4983          <beat-type>4</beat-type>
4984        </time>
4985        <clef>
4986          <sign>G</sign>
4987          <line>2</line>
4988        </clef>
4989      </attributes>
4990      <note>
4991        <pitch>
4992          <step>D</step>
4993          <alter>1</alter>
4994          <octave>4</octave>
4995        </pitch>
4996        <duration>10080</duration>
4997        <type>quarter</type>
4998        <accidental>sharp</accidental>
4999      </note>
5000    </measure>
5001  </part>
5002</score-partwise>"
5003        );
5004    }
5005
5006    #[test]
5007    fn a_part_that_changes_instrument_says_who_plays_each_note() {
5008        // music21's `setNoteInstrument`: the last instrument standing at or
5009        // before a note plays it, unless the note keeps one of its own.
5010        let measure = |number, instrument: &str, stored: Option<&str>| {
5011            let mut measure = Stream::with_kind(StreamKind::Measure);
5012            measure.set_number(number);
5013            measure.insert(0.0, Instrument::of_kind(instrument).unwrap());
5014            let mut note = Note::from_name("C4")
5015                .unwrap()
5016                .with_duration(Duration::quarter());
5017            note.set_stored_instrument(stored.map(|kind| Instrument::of_kind(kind).unwrap()));
5018            measure.insert(0.0, note);
5019            measure
5020        };
5021        let mut part = Stream::with_kind(StreamKind::Part);
5022        part.insert(0.0, measure(1, "Violin", None));
5023        part.insert(1.0, measure(2, "Viola", None));
5024        part.insert(2.0, measure(3, "Viola", Some("Violin")));
5025        let mut score = Stream::with_kind(StreamKind::Score);
5026        score.push(part);
5027        let written = to_musicxml(&score, &ExportOptions::default()).unwrap();
5028        let ids: Vec<&str> = written
5029            .lines()
5030            .filter_map(|line| line.trim().strip_prefix("<instrument id=\""))
5031            .map(|rest| rest.trim_end_matches("\" />"))
5032            .collect();
5033        assert_eq!(ids.len(), 3);
5034        assert_ne!(ids[0], ids[1]);
5035        assert_eq!(ids[0], ids[2]);
5036        for id in &ids {
5037            assert!(written.contains(&format!("<score-instrument id=\"{id}\">")));
5038        }
5039    }
5040
5041    #[test]
5042    fn a_part_without_measures_is_refused() {
5043        let mut part = Stream::with_kind(StreamKind::Part);
5044        part.push(Note::from_name("C4").unwrap());
5045        let mut score = Stream::with_kind(StreamKind::Score);
5046        score.push(part);
5047        assert_eq!(
5048            to_musicxml(&score, &ExportOptions::default()).unwrap_err(),
5049            Error::MusicXml(
5050                "Cannot export with makeNotation=False if there are no measures".to_string()
5051            )
5052        );
5053    }
5054
5055    #[test]
5056    fn divider_comments_are_centred_in_sixty_signs() {
5057        let mut root = Element::new("score-partwise");
5058        add_divider_comment(&mut root, "second accidental below");
5059        assert_eq!(
5060            root.dump(),
5061            "<score-partwise>\n  <!--================== second accidental below ===================-->\n</score-partwise>"
5062        );
5063    }
5064}