Skip to main content

music21_rs/
capella.rs

1//! Capella's CapXML: music21's `capella.fromCapellaXML`.
2//!
3//! A `.capx` file is a zip archive holding the score as `score.xml`;
4//! [`from_capella`] reads that document's text, the caller having unpacked
5//! it, as this crate unpacks nothing.
6
7use crate::bar::{Barline, BarlineType, RepeatDirection};
8use crate::chord::Chord;
9use crate::clef::{Clef, ClefKind};
10use crate::defaults::{FloatType, IntegerType};
11use crate::duration::{Duration, Tuplet};
12use crate::error::{Error, Result};
13use crate::key::KeySignature;
14use crate::makenotation::{make_measures, op_frac};
15use crate::meter::TimeSignature;
16use crate::notation::{Lyric, Syllabic, Tie, TieType};
17use crate::note::Note;
18use crate::pitch::Pitch;
19use crate::pitch::accidental::Accidental;
20use crate::rest::Rest;
21use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
22use crate::xml::Xml;
23
24fn error(message: impl Into<String>) -> Error {
25    Error::Capella(message.into())
26}
27
28/// What a staff holds, each element at its offset.
29type Placed = Vec<(FloatType, StreamElement)>;
30
31/// A staff's part id and what it holds.
32type Staff = (String, Placed);
33
34/// Reads the text of a Capella `score.xml` into a score, as music21's
35/// `CapellaImporter.scoreFromFile` reads a `.capx`.
36///
37/// The score is read system by system, each staff of a system being a
38/// part's share of it, and a part's shares are put end to end, each system
39/// starting where the longest staff of the one before ended. A staff of one
40/// voice is read: clefs, key signatures, meters, rests, notes and chords
41/// with their accidentals, ties, triplets and other tuplets and lyrics, and
42/// barlines. A clef or key signature restating the one before is dropped,
43/// and each part is then cut into measures by its meters.
44///
45/// music21's limits are kept: a staff of more than one voice is read as
46/// empty, a barline is a mark standing in the measure rather than the
47/// measure's own (where a MusicXML writer passes it over).
48///
49/// ```
50/// use music21_rs::capella::from_capella;
51///
52/// let score = from_capella(
53///     "<score><systems><system><staves><staff layout=\"S\"><voices><voice><noteObjects>\
54///      <clefSign clef=\"treble\"/><timeSign time=\"2/4\"/>\
55///      <chord><duration base=\"1/4\"/><heads><head pitch=\"C5\"/></heads></chord>\
56///      <chord><duration base=\"1/4\"/><heads><head pitch=\"E5\"/></heads></chord>\
57///      </noteObjects></voice></voices></staff></staves></system></systems></score>",
58/// )?;
59/// let pitches = score.pitches();
60/// assert_eq!(pitches[0].name_with_octave(), "C4");
61/// # Ok::<(), music21_rs::Error>(())
62/// ```
63///
64/// # Errors
65///
66/// A document that is not XML, one with no systems or with a system, staff
67/// or voice missing what music21 asks for, a chord with no heads or with
68/// more than one duration, a head with no pitch, a barline or meter music21
69/// does not know, or a number that is not one.
70pub fn from_capella(document: &str) -> Result<Stream> {
71    let document = document.strip_prefix('\u{feff}').unwrap_or(document);
72    let root = without_namespaces(Xml::parse(document)?);
73
74    let mut systems_found = children(&root, "systems");
75    let systems = match (systems_found.next(), systems_found.next()) {
76        (Some(systems), None) => systems,
77        (None, _) => return Err(error("Cannot find a <systems> tag in the <score> object")),
78        (Some(_), Some(_)) => {
79            return Err(error(
80                "Found more than one <systems> tag in the <score> object, what does this mean?",
81            ));
82        }
83    };
84    let systems: Vec<&Xml> = children(systems, "system").collect();
85    if systems.is_empty() {
86        return Err(error(
87            "Cannot find any <system> tags in the <systems> tag in the <score> object",
88        ));
89    }
90
91    // Each part by its id, in the order first seen, with the place it held
92    // in the system where it was, and what it holds.
93    let mut parts: Vec<(String, usize, Placed)> = Vec::new();
94    let mut system_offset = 0.0;
95    for system in systems {
96        let staves = read_system(system)?;
97        let length = staves
98            .iter()
99            .map(|(_, elements)| end_of(elements))
100            .fold(0.0, FloatType::max);
101        for (place, (id, elements)) in staves.into_iter().enumerate() {
102            let index = match parts.iter().position(|(known, _, _)| *known == id) {
103                Some(index) => index,
104                None => {
105                    parts.push((id, place, Vec::new()));
106                    parts.len() - 1
107                }
108            };
109            for (offset, element) in elements {
110                parts[index]
111                    .2
112                    .push((op_frac(offset + system_offset), element));
113            }
114        }
115        system_offset += length;
116    }
117
118    // music21 puts each part at the place it held where it was first seen,
119    // in a list as long as there are parts.
120    let mut slots: Vec<Option<Staff>> = (0..parts.len()).map(|_| None).collect();
121    for (id, place, elements) in parts {
122        let slot = slots
123            .get_mut(place)
124            .ok_or_else(|| error("part entries do not match partDict!"))?;
125        *slot = Some((id, elements));
126    }
127    let mut score = Stream::with_kind(StreamKind::Score);
128    for (id, elements) in slots.into_iter().flatten() {
129        let mut part = Stream::with_kind(StreamKind::Part);
130        for (offset, element) in elements {
131            part.insert_sorted(vec![StreamEvent::new(offset, element)]);
132        }
133        let part = without_restatements(part);
134        let mut measured = make_measures(&part)?;
135        measured.set_id(Some(id));
136        score.insert(0.0, measured);
137    }
138    Ok(score)
139}
140
141/// The element with every tag's namespace taken off, as music21 strips
142/// them.
143fn without_namespaces(mut element: Xml) -> Xml {
144    if let Some((_, local)) = element.tag.rsplit_once(':') {
145        element.tag = local.to_string();
146    }
147    element.children = element
148        .children
149        .into_iter()
150        .map(without_namespaces)
151        .collect();
152    element
153}
154
155fn children<'a>(element: &'a Xml, tag: &'a str) -> impl Iterator<Item = &'a Xml> {
156    element
157        .children
158        .iter()
159        .filter(move |child| child.tag == tag)
160}
161
162/// The one child of a tag, where there is exactly one.
163fn only<'a>(element: &'a Xml, tag: &'a str, missing: &str, several: &str) -> Result<&'a Xml> {
164    let mut found = children(element, tag);
165    match (found.next(), found.next()) {
166        (Some(child), None) => Ok(child),
167        (None, _) => Err(error(missing)),
168        (Some(_), Some(_)) => Err(error(several)),
169    }
170}
171
172fn end_of(elements: &[(FloatType, StreamElement)]) -> FloatType {
173    elements
174        .iter()
175        .map(|(offset, element)| offset + element.quarter_length())
176        .fold(0.0, FloatType::max)
177}
178
179/// `systemFromSystem`: each staff's id and what its one voice holds.
180fn read_system(system: &Xml) -> Result<Vec<Staff>> {
181    let staves = only(
182        system,
183        "staves",
184        "No <staves> tag found in this <system> element",
185        "More than one <staves> tag found in this <system> element",
186    )?;
187    let mut out = Vec::new();
188    for staff in children(staves, "staff") {
189        let id = staff.get("layout").unwrap_or("UnknownPart").to_string();
190        let voices = children(staff, "voices").next().ok_or_else(|| {
191            error(
192                "No <voices> tag found in the <staff> tag for the <staves> element for this \
193                 <system> element",
194            )
195        })?;
196        let voice_list: Vec<&Xml> = children(voices, "voice").collect();
197        if voice_list.is_empty() {
198            return Err(error(
199                "No <voice> tag found in the <voices> tag for the <staff> tag for the <staves> \
200                 element for this <system> element",
201            ));
202        }
203        let mut elements = Vec::new();
204        if let [voice] = voice_list.as_slice() {
205            let objects = only(
206                voice,
207                "noteObjects",
208                "No <noteObjects> tag found in the <voice> tag",
209                "More than one <noteObjects> tag found in the <voice> tag",
210            )?;
211            for object in &objects.children {
212                for element in note_object(object)? {
213                    let at = end_of(&elements);
214                    elements.push((at, element));
215                }
216            }
217        }
218        out.push((id, elements));
219    }
220    Ok(out)
221}
222
223/// `streamFromNoteObjects`' mapping: what one note object is, if anything.
224fn note_object(object: &Xml) -> Result<Vec<StreamElement>> {
225    Ok(match object.tag.as_str() {
226        "clefSign" => clef(object)?.map(StreamElement::Clef).into_iter().collect(),
227        "keySign" => match object.get("fifths") {
228            Some(fifths) => vec![StreamElement::KeySignature(KeySignature::new(integer(
229                fifths,
230            )?))],
231            None => Vec::new(),
232        },
233        "timeSign" => match object.get("time") {
234            Some(time) if time != "infinite" => {
235                vec![StreamElement::TimeSignature(
236                    TimeSignature::from_ratio_string(time)?,
237                )]
238            }
239            _ => Vec::new(),
240        },
241        "rest" => {
242            let written = children(object, "duration")
243                .next()
244                .ok_or_else(|| error("a rest with no duration"))?;
245            vec![StreamElement::Rest(Rest::new(duration(written)?))]
246        }
247        "chord" => vec![chord(object)?],
248        "barline" => barlines(object)?,
249        _ => Vec::new(),
250    })
251}
252
253fn integer(text: &str) -> Result<IntegerType> {
254    text.trim()
255        .parse()
256        .map_err(|_| error(format!("{text:?} is not a number")))
257}
258
259/// `clefFromClefSign`.
260fn clef(object: &Xml) -> Result<Option<Clef>> {
261    let Some(value) = object.get("clef") else {
262        return Ok(None);
263    };
264    let kind = match value {
265        "treble" => Some(ClefKind::TrebleClef),
266        "bass" => Some(ClefKind::BassClef),
267        "alto" => Some(ClefKind::AltoClef),
268        "tenor" => Some(ClefKind::TenorClef),
269        "G2-" => Some(ClefKind::Treble8vbClef),
270        _ => None,
271    };
272    if let Some(kind) = kind {
273        return Ok(Some(Clef::of_kind(kind)));
274    }
275    if value.starts_with('p') {
276        return Ok(Some(Clef::of_kind(ClefKind::PercussionClef)));
277    }
278    let letters: Vec<char> = value.chars().collect();
279    if letters.len() > 1 {
280        let shift = match letters.get(2) {
281            Some('+') => 1,
282            Some('-') => -1,
283            _ => 0,
284        };
285        let sign_and_line: String = letters[..2].iter().collect();
286        return Clef::from_string(&sign_and_line, shift).map(Some);
287    }
288    Ok(None)
289}
290
291/// `chordOrNoteFromChord`.
292fn chord(object: &Xml) -> Result<StreamElement> {
293    let durations: Vec<&Xml> = children(object, "duration").collect();
294    let heads: Vec<&Xml> = children(object, "heads").collect();
295    let ([written], [heads]) = (durations.as_slice(), heads.as_slice()) else {
296        return Err(error("Malformed chord!"));
297    };
298    let mut notes = Vec::new();
299    for head in children(heads, "head") {
300        notes.push(note(head)?);
301    }
302    if notes.is_empty() {
303        return Err(error("Malformed chord!"));
304    }
305    let duration = duration(written)?;
306    let lyrics: Vec<Lyric> = match children(object, "lyric").next() {
307        Some(lyric) => children(lyric, "verse")
308            .map(verse)
309            .collect::<Result<Vec<_>>>()?
310            .into_iter()
311            .flatten()
312            .collect(),
313        None => Vec::new(),
314    };
315    Ok(if notes.len() == 1 {
316        let mut note = notes.remove(0);
317        note.set_duration(duration);
318        *note.lyrics_mut() = lyrics;
319        StreamElement::Note(note)
320    } else {
321        let mut chord = Chord::new(notes)?.with_duration(duration);
322        if let Some(first) = chord.notes_mut().first_mut() {
323            *first.lyrics_mut() = lyrics;
324        }
325        StreamElement::Chord(chord)
326    })
327}
328
329/// `noteFromHead`: Capella writes its octaves one higher than music21.
330fn note(head: &Xml) -> Result<Note> {
331    let written = head
332        .get("pitch")
333        .ok_or_else(|| error("Cannot deal with <head> element without pitch!"))?;
334    let mut pitch = Pitch::from_name(written)?;
335    let octave = pitch.octave().unwrap_or(4);
336    pitch.set_octave(Some(octave - 1));
337    let alters: Vec<&Xml> = children(head, "alter").collect();
338    match alters.as_slice() {
339        [] => {}
340        [alter] => pitch.set_accidental(accidental(alter)?),
341        _ => return Err(error("Cannot deal with multiple <alter> elements!")),
342    }
343    let mut note = Note::from_pitch(pitch);
344    let ties: Vec<&Xml> = children(head, "tie").collect();
345    match ties.as_slice() {
346        [] => {}
347        [tie] => note.set_tie(tie_of(tie)),
348        _ => return Err(error("Cannot deal with multiple <tie> elements!")),
349    }
350    Ok(note)
351}
352
353/// `accidentalFromAlter`.
354fn accidental(alter: &Xml) -> Result<Accidental> {
355    let steps = match alter.get("step") {
356        Some(step) => integer(step)?,
357        None => 0,
358    };
359    let name = match steps {
360        0 => "natural",
361        1 => "sharp",
362        -1 => "flat",
363        2 => "double-sharp",
364        -2 => "double-flat",
365        3 => "triple-sharp",
366        -3 => "triple-flat",
367        4 => "quadruple-sharp",
368        -4 => "quadruple-flat",
369        other => return Err(error(format!("no accidental of {other} steps"))),
370    };
371    let mut accidental = Accidental::new(name)?;
372    if alter.get("display") == Some("suppress") {
373        accidental.set_display_type("never")?;
374    }
375    Ok(accidental)
376}
377
378/// `tieFromTie`.
379fn tie_of(tie: &Xml) -> Option<Tie> {
380    let begins = tie.get("begin") == Some("true");
381    let ends = tie.get("end") == Some("true");
382    let kind = match (begins, ends) {
383        (true, true) => TieType::Continue,
384        (true, false) => TieType::Start,
385        (false, true) => TieType::Stop,
386        (false, false) => return None,
387    };
388    Some(Tie::new(kind))
389}
390
391/// `lyricFromVerse`: a verse's syllable, numbered from one, hyphenated to
392/// the next where it says so; nothing for a verse with no text.
393fn verse(verse: &Xml) -> Result<Option<Lyric>> {
394    let text = match verse.text() {
395        Some(text) if !text.is_empty() => text,
396        _ => return Ok(None),
397    };
398    let mut lyric = Lyric::new(text);
399    if let Some(index) = verse.get("i") {
400        lyric.set_number(integer(index)? + 1);
401    }
402    if verse.get("hyphen") == Some("true") {
403        lyric.set_syllabic(Syllabic::Begin);
404    }
405    Ok(Some(lyric))
406}
407
408/// `durationFromDuration`: a fraction of a whole note, its dots and its
409/// tuplets.
410fn duration(written: &Xml) -> Result<Duration> {
411    let mut length = 0.0;
412    if let Some(base) = written.get("base")
413        && let Some((numerator, denominator)) = base.split_once('/')
414    {
415        length =
416            4.0 * FloatType::from(integer(numerator)?) / FloatType::from(integer(denominator)?);
417    }
418    let mut duration = Duration::new(length)?;
419    if let Some(dots) = written.get("dots") {
420        let dots = u32::try_from(integer(dots)?).map_err(|_| error("a negative count of dots"))?;
421        if let Some((value, _)) = duration.type_and_dots() {
422            duration = Duration::from_type_with_dots(value, dots);
423            duration.set_expression_is_inferred(true);
424        }
425    }
426    for tuplet in children(written, "tuplet") {
427        duration.append_tuplet(tuplet_of(tuplet)?);
428    }
429    Ok(duration)
430}
431
432/// `tupletFromTuplet`: `count` notes in the time of the power of two below
433/// it, or of the one above it where `prolong` says `true`.
434fn tuplet_of(tuplet: &Xml) -> Result<Tuplet> {
435    let mut actual = 1;
436    let mut normal = 1;
437    if let Some(count) = tuplet.get("count") {
438        actual = u32::try_from(integer(count)?).map_err(|_| error("a negative tuplet"))?;
439        while actual > normal * 2 {
440            normal *= 2;
441        }
442    }
443    if tuplet.get("prolong") == Some("true") {
444        normal *= 2;
445    }
446    Ok(Tuplet::ratio(actual, normal))
447}
448
449/// `barlineListFromBarline`: a barline, or a repeat's end and start.
450fn barlines(object: &Xml) -> Result<Vec<StreamElement>> {
451    let Some(kind) = object.get("type") else {
452        return Ok(Vec::new());
453    };
454    let mut out = Vec::new();
455    if kind.starts_with("rep") {
456        let (end, start) = match kind {
457            "repEnd" => (true, false),
458            "repBegin" => (false, true),
459            "repEndBegin" => (true, true),
460            _ => (false, false),
461        };
462        if end {
463            out.push(StreamElement::Barline(Barline::repeat(
464                RepeatDirection::End,
465                None,
466            )));
467        }
468        if start {
469            out.push(StreamElement::Barline(Barline::repeat(
470                RepeatDirection::Start,
471                None,
472            )));
473        }
474    } else {
475        let bar_type = match kind {
476            "single" => BarlineType::Regular,
477            "double" => BarlineType::Double,
478            "end" => BarlineType::Final,
479            _ => return Ok(Vec::new()),
480        };
481        out.push(StreamElement::Barline(Barline::new(bar_type)));
482    }
483    Ok(out)
484}
485
486/// A part with every clef and key signature dropped that restates the one
487/// before it, as `partScoreFromSystemScore` drops them.
488fn without_restatements(part: Stream) -> Stream {
489    let mut last_clef: Option<Clef> = None;
490    let mut last_key: Option<KeySignature> = None;
491    let mut kept = Vec::new();
492    for event in part.events() {
493        match event.element() {
494            StreamElement::Clef(clef) => {
495                if last_clef.as_ref().is_some_and(|last| same_clef(last, clef)) {
496                    continue;
497                }
498                last_clef = Some(clef.clone());
499            }
500            StreamElement::KeySignature(signature) => {
501                // music21 compares two key signatures by what they hold.
502                if last_key
503                    .as_ref()
504                    .is_some_and(|last| last.sharps() == signature.sharps())
505                {
506                    continue;
507                }
508                last_key = Some(signature.clone());
509            }
510            _ => {}
511        }
512        kept.push(event.clone());
513    }
514    part.with_events(kept)
515}
516
517/// music21's `Clef.__eq__`: the same class, sign, line and octave change.
518fn same_clef(left: &Clef, right: &Clef) -> bool {
519    left.kind() == right.kind()
520        && left.sign() == right.sign()
521        && left.line() == right.line()
522        && left.octave_change() == right.octave_change()
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528
529    fn staff(objects: &str) -> String {
530        format!(
531            "<score xmlns=\"http://www.capella.de/CapXML/2.0\"><systems><system><staves>\
532             <staff layout=\"S\"><voices><voice><noteObjects>{objects}</noteObjects></voice>\
533             </voices></staff></staves></system></systems></score>"
534        )
535    }
536
537    #[test]
538    fn a_capella_octave_is_one_higher_than_music21_s() {
539        let score = from_capella(&staff(
540            "<chord><duration base=\"1/2\"/><heads><head pitch=\"G5\"><alter step=\"1\"/>\
541             </head></heads></chord>",
542        ))
543        .unwrap();
544        assert_eq!(score.pitches()[0].name_with_octave(), "G#4");
545    }
546
547    #[test]
548    fn a_tuplet_count_takes_the_power_of_two_below_it() {
549        let tuplet = Xml::parse("<tuplet count=\"5\"/>").unwrap();
550        assert_eq!(tuplet_of(&tuplet).unwrap(), Tuplet::ratio(5, 4));
551        let prolonged = Xml::parse("<tuplet count=\"3\" prolong=\"true\"/>").unwrap();
552        assert_eq!(tuplet_of(&prolonged).unwrap(), Tuplet::ratio(3, 4));
553        let plain = Xml::parse("<tuplet count=\"3\" prolong=\"false\"/>").unwrap();
554        assert_eq!(tuplet_of(&plain).unwrap(), Tuplet::ratio(3, 2));
555    }
556
557    #[test]
558    fn a_single_barline_is_a_regular_one() {
559        assert!(from_capella(&staff("<barline type=\"single\"/>")).is_ok());
560        assert!(from_capella(&staff("<barline/>")).is_ok());
561    }
562
563    #[test]
564    fn a_document_with_no_systems_is_refused() {
565        assert!(from_capella("<score/>").is_err());
566    }
567}