Skip to main content

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