Skip to main content

music21/humdrum/
mod.rs

1//! Humdrum: music21's `humdrum.spineParser`, which reads `**kern` scores.
2//!
3//! A Humdrum file is a table. Each column is a *spine* -- a part -- and each
4//! line a moment; a cell holds a note (`4c#` is a quarter C sharp above
5//! middle C), a barline (`=12`), an instruction (`*clefG2`, `*M3/4`) or a
6//! dot where nothing new happens. A spine may split into two voices (`*^`)
7//! and come together again (`*v`). `**dynam` spines carry dynamics and
8//! `**text` spines the words sung, and `**harm` spines the harmony read
9//! into the music, each said by `*staff` to belong to a `**kern` spine.
10
11mod comment;
12pub mod harm;
13mod tables;
14mod tandem;
15
16use std::collections::BTreeMap;
17
18use crate::articulations::{Articulation, ArticulationKind};
19use crate::bar::{Barline, BarlineType, RepeatDirection};
20use crate::chord::Chord;
21use crate::clef::{Clef, ClefKind};
22use crate::defaults::{FloatType, IntegerType};
23use crate::duration::{Duration, DurationType, Grace, Tuplet, TupletType};
24use crate::dynamics::Dynamic;
25use crate::error::{Error, Position, Result};
26use crate::expressions::{Expression, Fermata, Ornament, OrnamentKind};
27use crate::instrument::Instrument;
28use crate::key::{Key, KeySignature};
29use crate::makenotation::op_frac;
30use crate::metadata::{Metadata, is_contributor_unique_name};
31use crate::meter::TimeSignature;
32use crate::notation::{BeamDirection, BeamType, StemDirection, Tie, TieType};
33use crate::note::Note;
34use crate::pitch::{Accidental, PitchOptions};
35use crate::rest::Rest;
36use crate::roman::{Minor67Default, RomanNumeral};
37use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
38use crate::tempo::MetronomeMark;
39
40pub use comment::{GlobalComment, SpineComment};
41pub use tables::{INSTRUMENT_CLASSES, INSTRUMENTS};
42pub use tandem::MiscTandem;
43
44use tables::REFERENCE_NAMES;
45
46fn humdrum_error(message: impl Into<String>) -> Error {
47    Error::humdrum(message.into())
48}
49
50/// The tokens that say where a spine goes rather than what it holds.
51const SPINE_PATHS: [&str; 6] = ["*+", "*-", "*^", "*v", "*x", "*"];
52
53/// A barline as a spine states it: the measure it opens.
54#[derive(Clone, Debug, Default)]
55struct Measure {
56    number: IntegerType,
57    suffix: Option<String>,
58    left: Option<Barline>,
59    right: Option<Barline>,
60}
61
62/// What a cell of a spine came to.
63// Nearly every thing is an element, so boxing it would cost more than the
64// few smaller variants waste.
65#[allow(clippy::large_enum_variant)]
66#[derive(Clone, Debug)]
67enum Thing {
68    Element(StreamElement),
69    Measure(Measure),
70    /// An instruction with no meaning here, kept for `*staff`.
71    Tandem(String),
72    /// A word of a `**text` spine.
73    Word(String),
74}
75
76/// A thing in a spine's stream: where it stands, the line it was written
77/// on -- which music21 sorts by ahead of everything but the offset -- and
78/// the voice it belongs to while a spine is split.
79#[derive(Clone, Debug)]
80struct Placed {
81    offset: FloatType,
82    line: usize,
83    /// Nought for a thing no line can be asked for any more.
84    priority: usize,
85    voice: Option<usize>,
86    thing: Thing,
87}
88
89impl Placed {
90    fn length(&self) -> FloatType {
91        match &self.thing {
92            Thing::Element(element) => element.quarter_length(),
93            _ => 0.0,
94        }
95    }
96
97    fn class_order(&self) -> i32 {
98        match &self.thing {
99            Thing::Element(element) => element.class_sort_order(),
100            Thing::Measure(_) => -20,
101            _ => 0,
102        }
103    }
104}
105
106/// music21's stream order: offset, then the priority, then the class.
107fn sort(things: &mut [Placed]) {
108    things.sort_by(|left, right| {
109        left.offset
110            .total_cmp(&right.offset)
111            .then(left.priority.cmp(&right.priority))
112            .then(left.class_order().cmp(&right.class_order()))
113    });
114}
115
116fn highest(things: &[Placed]) -> FloatType {
117    things
118        .iter()
119        .map(|thing| thing.offset + thing.length())
120        .fold(0.0, FloatType::max)
121}
122
123/// One spine: music21's `HumdrumSpine`.
124#[derive(Clone, Debug, Default)]
125struct Spine {
126    events: Vec<(String, usize)>,
127    parent: Option<usize>,
128    first_voice: bool,
129    /// The spines this one splits into, by the line it splits on.
130    splits: BTreeMap<usize, Vec<usize>>,
131    kind: String,
132    stream: Vec<Placed>,
133}
134
135/// The lengths a number names: music21's `typeFromNumDict`.
136fn type_of_number(number: u64) -> Option<DurationType> {
137    ((1..=2048).contains(&number) && number.is_power_of_two())
138        .then(|| {
139            DurationType::ALL
140                .into_iter()
141                .find(|kind| kind.quarter_length() == 4.0 / number as FloatType)
142        })
143        .flatten()
144}
145
146fn first_number(text: &str) -> Option<&str> {
147    let start = text.find(|c: char| c.is_ascii_digit())?;
148    let rest = &text[start..];
149    let end = rest
150        .find(|c: char| !c.is_ascii_digit())
151        .unwrap_or(rest.len());
152    Some(&rest[..end])
153}
154
155/// music21's `hdStringToDuration`: `4` is a quarter, `8.` a dotted eighth,
156/// `12` an eighth of a triplet, `0` a breve, and `q` a grace note.
157fn duration_of(contents: &str) -> Result<Duration> {
158    let dots = contents.matches('.').count() as u32;
159    let has_grace = contents.contains('q') || contents.contains('Q');
160    let number = first_number(contents);
161    let rational = contents.contains('%').then(|| {
162        let at = contents.find('%')?;
163        let before = &contents[..at];
164        let start = before
165            .rfind(|c: char| !c.is_ascii_digit())
166            .map_or(0, |index| index + 1);
167        let first = before[start..].parse::<FloatType>().ok()?;
168        let second = first_number(&contents[at + 1..])
169            .filter(|_| contents[at + 1..].starts_with(|c: char| c.is_ascii_digit()))?
170            .parse::<FloatType>()
171            .ok()?;
172        Some((first, second))
173    });
174    let mut duration = if let Some(Some((first, second))) = rational {
175        let plain = Duration::new(op_frac(4.0 * second / first))?;
176        match plain.type_and_dots() {
177            Some((kind, _)) if dots > 0 => Duration::from_type_with_dots(kind, dots),
178            _ => plain,
179        }
180    } else if let Some(number) = number {
181        let value: u64 = number
182            .parse()
183            .map_err(|_| humdrum_error(format!("Could not find a duration in {contents:?}")))?;
184        if value == 0 {
185            let kind = match number {
186                "000" => DurationType::Maxima,
187                "00" => DurationType::Longa,
188                _ => DurationType::Breve,
189            };
190            Duration::from_type_with_dots(kind, dots)
191        } else if let Some(kind) = type_of_number(value) {
192            Duration::from_type_with_dots(kind, dots)
193        } else {
194            // A number that is no power of two is that many in the time of
195            // the power of two below it: twelfths are triplet eighths.
196            let base = 1_u64 << value.ilog2();
197            let kind = type_of_number(base)
198                .ok_or_else(|| humdrum_error(format!("no note value for {value}")))?;
199            let divisor = gcd(value, base);
200            let mut tuplet =
201                Tuplet::new((value / divisor) as u32, (base / divisor) as u32, kind, 0);
202            if dots > 0 {
203                tuplet.set_duration_normal(Some((kind, dots)));
204            }
205            let mut duration = Duration::from_type(kind);
206            duration.append_tuplet(tuplet);
207            duration
208        }
209    } else if has_grace {
210        Duration::from_type(DurationType::Eighth)
211    } else {
212        return Err(humdrum_error(format!(
213            "Could not find a duration in {contents:?}"
214        )));
215    };
216    if has_grace {
217        duration = duration.grace_duration();
218        let mut grace = Grace::new();
219        if contents.matches('q').count() != 1 {
220            grace.set_slash(false);
221        }
222        duration.set_grace(Some(grace));
223    } else if contents.contains('P') {
224        duration = duration.grace_duration();
225        duration.set_grace(Some(Grace::appoggiatura()));
226    }
227    Ok(duration)
228}
229
230fn gcd(mut left: u64, mut right: u64) -> u64 {
231    while right != 0 {
232        (left, right) = (right, left % right);
233    }
234    left
235}
236
237/// What is read off a note's token besides the note itself.
238struct Marks {
239    tie: Option<TieType>,
240    expressions: Vec<Expression>,
241    articulations: Vec<Articulation>,
242    stem: StemDirection,
243    beams: Vec<(BeamType, Option<BeamDirection>)>,
244}
245
246fn marks_of(contents: &str) -> Marks {
247    let has = |mark: char| contents.contains(mark);
248    let tie = if has('[') {
249        Some(TieType::Start)
250    } else if has(']') {
251        Some(TieType::Stop)
252    } else if has('_') {
253        Some(TieType::Continue)
254    } else {
255        None
256    };
257    let ornament = |kind: OrnamentKind| Expression::from(Ornament::of_kind(kind));
258    let mut expressions = Vec::new();
259    if has('t') {
260        expressions.push(ornament(OrnamentKind::HalfStepTrill));
261    } else if has('T') {
262        expressions.push(ornament(OrnamentKind::WholeStepTrill));
263    }
264    if has('w') {
265        expressions.push(ornament(OrnamentKind::HalfStepInvertedMordent));
266    } else if has('W') {
267        expressions.push(ornament(OrnamentKind::WholeStepInvertedMordent));
268    } else if has('m') {
269        expressions.push(ornament(OrnamentKind::HalfStepMordent));
270    } else if has('M') {
271        expressions.push(ornament(OrnamentKind::WholeStepMordent));
272    }
273    if has('S') {
274        expressions.push(ornament(OrnamentKind::Turn));
275    } else if has('$') {
276        expressions.push(ornament(OrnamentKind::InvertedTurn));
277    } else if has('R') {
278        expressions.push(ornament(OrnamentKind::Turn));
279    }
280    if has('O') {
281        expressions.push(ornament(OrnamentKind::Ornament));
282    }
283    let mut articulations = Vec::new();
284    let mut articulation = |class: &str| {
285        if let Some(kind) = ArticulationKind::from_class_name(class) {
286            articulations.push(Articulation::of_kind(kind));
287        }
288    };
289    if has('\'') {
290        articulation("Staccato");
291    }
292    if has('"') {
293        articulation("Pizzicato");
294    }
295    if has('`') {
296        articulation("Staccatissimo");
297    }
298    if has('~') {
299        articulation("Tenuto");
300    }
301    if has('^') {
302        articulation("Accent");
303    }
304    if has(';') {
305        expressions.push(Expression::Fermata(Fermata::new()));
306    }
307    if has('v') {
308        articulation("UpBow");
309    } else if has('u') {
310        articulation("DownBow");
311    }
312    let stem = if has('/') {
313        StemDirection::Up
314    } else if has('\\') {
315        StemDirection::Down
316    } else {
317        StemDirection::Unspecified
318    };
319    let mut beams = Vec::new();
320    for (mark, beam, direction) in [
321        ('L', BeamType::Start, None),
322        ('J', BeamType::Stop, None),
323        ('k', BeamType::PartialBeam, Some(BeamDirection::Left)),
324        ('K', BeamType::PartialBeam, Some(BeamDirection::Right)),
325    ] {
326        for _ in 0..contents.matches(mark).count() {
327            beams.push((beam, direction));
328        }
329    }
330    Marks {
331        tie,
332        expressions,
333        articulations,
334        stem,
335        beams,
336    }
337}
338
339/// music21's `hdStringToNote`: a note or a rest from its token.
340fn note_of(contents: &str, given: Option<&Duration>) -> Result<StreamElement> {
341    // A token with no length is a quarter, as music21 makes it with a
342    // warning.
343    let duration = match given {
344        Some(duration) => duration.clone(),
345        None => duration_of(contents).unwrap_or_else(|_| Duration::quarter()),
346    };
347    let marks = marks_of(contents);
348    if contents.contains('r') {
349        let mut rest = Rest::new(duration);
350        rest.set_tie(marks.tie.map(Tie::new));
351        *rest.expressions_mut() = marks.expressions;
352        *rest.articulations_mut() = marks.articulations;
353        return Ok(rest.into());
354    }
355    let letters = |c: char| matches!(c, 'a'..='g' | 'A'..='G');
356    let start = contents
357        .find(letters)
358        .ok_or_else(|| humdrum_error(format!("Could not parse {contents} for note information")))?;
359    let name: Vec<char> = contents[start..]
360        .chars()
361        .take_while(|c| letters(*c))
362        .collect();
363    let first = name[0];
364    let count = name.len() as IntegerType;
365    let octave = if first.is_ascii_lowercase() {
366        3 + count
367    } else {
368        4 - count
369    };
370    let mut pitch = PitchOptions::new()
371        .step(first.to_ascii_uppercase())
372        .octave(octave)
373        .build()?;
374    let sharps = contents.matches('#').count() as IntegerType;
375    let flats = contents.matches('-').count() as IntegerType;
376    if sharps > 0 {
377        pitch.set_written_accidental(Some(Accidental::new(sharps)?));
378    } else if flats > 0 {
379        pitch.set_written_accidental(Some(Accidental::new(-flats)?));
380    } else if contents.contains('n') {
381        pitch.set_written_accidental(Some(Accidental::new("n")?));
382    }
383    let mut note = Note::from_pitch(pitch).with_duration(duration);
384    note.set_tie(marks.tie.map(Tie::new));
385    *note.expressions_mut() = marks.expressions;
386    *note.articulations_mut() = marks.articulations;
387    note.set_stem_direction(marks.stem);
388    let mut beams = note.beams().clone();
389    for (beam, direction) in marks.beams {
390        beams.append(beam, direction);
391    }
392    note.set_beams(beams);
393    Ok(note.into())
394}
395
396/// music21's `hdStringToMeasure`: the measure a barline opens, and the
397/// barline it puts at the end of the measure before.
398fn measure_of(contents: &str, has_previous: bool) -> (Measure, Barline) {
399    let mut measure = Measure::default();
400    if let Some(number) = first_number(contents) {
401        measure.number = number.parse().unwrap_or(0);
402        let after = &contents[contents.find(number).unwrap_or(0) + number.len()..];
403        measure.suffix = after
404            .chars()
405            .next()
406            .filter(char::is_ascii_lowercase)
407            .map(String::from);
408    }
409    let bar_type = if contents.contains('-') {
410        Some(BarlineType::None)
411    } else if contents.contains('\'') {
412        Some(BarlineType::Short)
413    } else if contents.contains('`') {
414        Some(BarlineType::Tick)
415    } else if contents.contains("||") || contents.contains("==") {
416        Some(BarlineType::Double)
417    } else if contents.contains("!!") {
418        Some(BarlineType::HeavyHeavy)
419    } else if contents.contains("|!") {
420        Some(BarlineType::Final)
421    } else if contents.contains("!|") {
422        Some(BarlineType::HeavyLight)
423    } else if contents.contains('|') {
424        Some(BarlineType::Regular)
425    } else {
426        None
427    };
428    let chars: Vec<char> = contents.chars().collect();
429    let beside = |colon_first: bool| {
430        chars.windows(2).any(|pair| {
431            let (colon, bar) = if colon_first {
432                (pair[0], pair[1])
433            } else {
434                (pair[1], pair[0])
435            };
436            colon == ':' && matches!(bar, '|' | '!' | '=')
437        })
438    };
439    let both = contents.matches(':').count() > 1;
440    let end_repeat = both || beside(true);
441    let start_repeat = both || beside(false);
442    let make = |direction: Option<RepeatDirection>| {
443        let mut barline = match direction {
444            Some(direction) => Barline::repeat(direction, None),
445            None => Barline::new(BarlineType::Regular),
446        };
447        if let Some(bar_type) = bar_type {
448            barline.set_bar_type(bar_type);
449        }
450        barline
451    };
452    let right = make(end_repeat.then_some(RepeatDirection::End));
453    if has_previous {
454        if start_repeat {
455            measure.left = Some(make(Some(RepeatDirection::Start)));
456        }
457    } else {
458        measure.left = Some(if start_repeat {
459            make(Some(RepeatDirection::Start))
460        } else {
461            right.clone()
462        });
463    }
464    (measure, right)
465}
466
467/// music21's `kernTandemToObject`: what an instruction in a `**kern` spine
468/// stands for.
469fn tandem_of(tandem: &str) -> Result<Option<Thing>> {
470    let element = |element: StreamElement| Ok(Some(Thing::Element(element)));
471    if SPINE_PATHS.contains(&tandem) {
472        return Ok(None);
473    }
474    if let Some(kind) = tandem.strip_prefix("*clef") {
475        let clef = match kind {
476            "-" => Clef::of_kind(ClefKind::NoClef),
477            "X" => Clef::of_kind(ClefKind::PercussionClef),
478            "Gv2" | "Gv" => Clef::of_kind(ClefKind::Treble8vbClef),
479            "G^2" | "G^" => Clef::of_kind(ClefKind::Treble8vaClef),
480            "Fv4" | "Fv" => Clef::of_kind(ClefKind::Bass8vbClef),
481            other => Clef::from_string(other, 0)
482                .map_err(|_| humdrum_error(format!("Unknown clef type {tandem} found")))?,
483        };
484        return element(clef.into());
485    }
486    if let Some(text) = tandem.strip_prefix("*MM") {
487        let mark = match text.parse::<FloatType>() {
488            Ok(number) => MetronomeMark::new(number),
489            Err(_) => {
490                let text = text.strip_prefix('[').unwrap_or(text);
491                let text = text.trim_end();
492                MetronomeMark::from_text(text.strip_suffix(']').unwrap_or(text))
493            }
494        };
495        return element(mark.into());
496    }
497    if let Some(meter) = tandem.strip_prefix("*M") {
498        let digits = |text: &str| text.chars().take_while(char::is_ascii_digit).count();
499        let top = digits(meter);
500        let bottom = meter
501            .get(top + 1..)
502            .filter(|_| top > 0 && meter[top..].starts_with('/'))
503            .map(|rest| &rest[..digits(rest)])
504            .filter(|bottom| !bottom.is_empty())
505            .ok_or_else(|| humdrum_error(format!("Incorrect meter: {tandem} found")))?;
506        let numerator: u32 = meter[..top]
507            .parse()
508            .map_err(|_| humdrum_error(format!("Incorrect meter: {tandem} found")))?;
509        let signature = match bottom {
510            "0" => TimeSignature::new(numerator * 2, 1)?,
511            "00" => TimeSignature::new(numerator * 4, 1)?,
512            "000" => TimeSignature::new(numerator * 8, 1)?,
513            _ => TimeSignature::from_ratio_string(meter)?,
514        };
515        return element(signature.into());
516    }
517    let instrument = |table: &[(&str, &str)], code: &str| -> Option<Instrument> {
518        let class = table.iter().find(|(held, _)| *held == code)?.1;
519        Instrument::of_kind(class).ok()
520    };
521    if let Some(class) = tandem.strip_prefix("*IC") {
522        return Ok(Some(match instrument(INSTRUMENT_CLASSES, class) {
523            Some(instrument) => Thing::Element(instrument.into()),
524            None => Thing::Tandem(class.to_string()),
525        }));
526    }
527    if tandem.starts_with("*IG") || tandem.starts_with("*ITr") {
528        return Ok(Some(Thing::Tandem(tandem.to_string())));
529    }
530    if let Some(code) = tandem.strip_prefix("*I") {
531        return Ok(Some(match instrument(INSTRUMENTS, code) {
532            Some(instrument) => Thing::Element(instrument.into()),
533            None => Thing::Tandem(code.to_string()),
534        }));
535    }
536    if tandem.starts_with("*k") {
537        let mut sharps = tandem.matches('#').count() as IntegerType;
538        if sharps == 0 {
539            sharps = -(tandem.matches('-').count() as IntegerType);
540        }
541        return element(KeySignature::new(sharps).into());
542    }
543    if let Some(key) = tandem.strip_suffix(':') {
544        return element(StreamElement::Key(Key::from_tonic(&key[1..])?));
545    }
546    Ok(Some(Thing::Tandem(tandem.to_string())))
547}
548
549/// What a `**kern` spine keeps from one note to the next: music21's
550/// `KernSpine` state.
551#[derive(Default)]
552struct KernState {
553    /// music21's `inTuplet`, with its `currentTupletQL` and
554    /// `desiredTupletQL`, which are both zero whenever it is false.
555    tuplet: Option<TupletRun>,
556    /// music21's `lastNote`, as an index into the stream.
557    last_note: Option<usize>,
558    /// music21's `currentBeamNumbers`.
559    beams: usize,
560}
561
562/// A run of tuplet notes being read: how long it is so far and how long
563/// its bracket is.
564struct TupletRun {
565    /// music21's `currentTupletQL`.
566    length: FloatType,
567    /// music21's `desiredTupletQL`.
568    wanted: FloatType,
569}
570
571fn with_duration(element: &mut StreamElement, change: impl FnOnce(&mut Duration)) {
572    let Some(mut duration) = element.duration().cloned() else {
573        return;
574    };
575    change(&mut duration);
576    match element {
577        StreamElement::Note(note) => note.set_duration(duration),
578        StreamElement::Chord(chord) => chord.set_duration(duration),
579        StreamElement::Rest(rest) => rest.set_duration(duration),
580        _ => {}
581    }
582}
583
584fn mark_first_tuplet(element: &mut StreamElement, kind: TupletType) {
585    with_duration(element, |duration| {
586        let mut tuplets = duration.tuplets();
587        if let Some(first) = tuplets.first_mut() {
588            first.set_tuplet_type(Some(kind));
589        }
590        duration.set_tuplets(tuplets);
591    });
592}
593
594impl KernState {
595    /// music21's `setBeamsForNote`: a note between the start and the end of
596    /// a beam carries it on.
597    fn beam(&mut self, element: &mut StreamElement) {
598        let beams = match element {
599            StreamElement::Note(note) => note.beams().clone(),
600            StreamElement::Chord(chord) => chord.beams().clone(),
601            _ => return,
602        };
603        if self.beams != 0 && beams.is_empty() {
604            let mut carried = beams;
605            for _ in 0..self.beams {
606                carried.append(BeamType::Continue, None);
607            }
608            match element {
609                StreamElement::Note(note) => note.set_beams(carried),
610                StreamElement::Chord(chord) => chord.set_beams(carried),
611                _ => {}
612            }
613        } else if let Some(first) = beams.beams().first() {
614            if first.beam_type() == Some(BeamType::Stop) {
615                self.beams = 0;
616            } else {
617                self.beams += beams
618                    .beams()
619                    .iter()
620                    .filter(|beam| beam.beam_type() != Some(BeamType::Stop))
621                    .count();
622            }
623        }
624    }
625
626    /// music21's `setTupletTypeForNote`: where a run of tuplet notes starts
627    /// and where it has filled its bracket. A grace note neither counts
628    /// toward a tuplet nor ends one.
629    fn tuplet(&mut self, stream: &mut [Placed], element: &mut StreamElement) {
630        let Some(duration) = element.duration().cloned() else {
631            return;
632        };
633        if duration.is_grace() {
634            return;
635        }
636        let tuplets = duration.tuplets();
637        let Some(run) = &mut self.tuplet else {
638            if !tuplets.is_empty() {
639                self.tuplet = Some(TupletRun {
640                    length: duration.quarter_length(),
641                    wanted: tuplets[0].total_tuplet_length(),
642                });
643                mark_first_tuplet(element, TupletType::Start);
644            }
645            return;
646        };
647        if tuplets.is_empty() {
648            self.tuplet = None;
649            if let Some(last) = self.last_note
650                && let Thing::Element(last) = &mut stream[last].thing
651            {
652                mark_first_tuplet(last, TupletType::Stop);
653            }
654            return;
655        }
656
657        run.length = op_frac(run.length + duration.quarter_length());
658        if run.length >= run.wanted && run.length % run.wanted == 0.0 {
659            mark_first_tuplet(element, TupletType::Stop);
660            self.tuplet = None;
661        }
662    }
663}
664
665/// music21's `processChordEvent`: the notes of a cell holding several.
666fn chord_of(contents: &str) -> Result<StreamElement> {
667    let mut notes: Vec<Note> = Vec::new();
668    let mut first: Option<(Duration, String)> = None;
669    for token in contents.split_whitespace() {
670        let length: String = token
671            .chars()
672            .filter(|c| c.is_ascii_digit() || matches!(c, '.' | '%'))
673            .collect();
674        let bare: String = token
675            .chars()
676            .filter(|c| !(c.is_ascii_digit() || matches!(c, '.' | '%')))
677            .collect();
678        let element = match &first {
679            Some((duration, written)) if length.is_empty() || length == *written => {
680                note_of(&bare, Some(duration))?
681            }
682            _ => {
683                let element = note_of(token, None)?;
684                if first.is_none()
685                    && let Some(duration) = element.duration()
686                {
687                    first = Some((duration.clone(), length));
688                }
689                element
690            }
691        };
692        if let StreamElement::Note(note) = element {
693            notes.push(note);
694        }
695    }
696    let last_beams = notes
697        .last()
698        .map(|note| note.beams().clone())
699        .ok_or_else(|| humdrum_error("a chord with no notes in it"))?;
700    let duration = first.map(|(duration, _)| duration).unwrap_or_default();
701    let mut chord = Chord::new(notes)?.with_duration(duration);
702    chord.set_beams(last_beams);
703    Ok(chord.into())
704}
705
706/// Appends a thing to a spine's stream, after all it holds.
707fn append(stream: &mut Vec<Placed>, line: usize, priority: usize, thing: Thing) -> usize {
708    let offset = highest(stream);
709    stream.push(Placed {
710        offset,
711        line,
712        priority,
713        voice: None,
714        thing,
715    });
716    stream.len() - 1
717}
718
719/// music21's `KernSpine.parse`.
720fn parse_kern(events: &[(String, usize)]) -> Vec<Placed> {
721    let mut stream: Vec<Placed> = Vec::new();
722    let mut state = KernState::default();
723    // The measure a barline closes, by its place in the stream.
724    let mut last_measure: Option<usize> = None;
725    for (contents, line) in events {
726        let made: Result<Option<Thing>> = (|| {
727            if contents == "." || contents.is_empty() {
728                return Ok(None);
729            }
730            if contents.starts_with('*') {
731                return tandem_of(contents);
732            }
733            if contents.starts_with('=') {
734                // music21 starts every spine off with a measure nought of
735                // its own, so the first barline always has one before it.
736                let (measure, right) = measure_of(contents, true);
737                if let Some(previous) = last_measure
738                    && let Thing::Measure(previous) = &mut stream[previous].thing
739                {
740                    previous.right = Some(right);
741                }
742                return Ok(Some(Thing::Measure(measure)));
743            }
744            if contents.starts_with('!') {
745                // music21's `SpineComment`: the line without its bangs and
746                // one space, kept where it says anything.
747                let text = contents.trim_start_matches('!');
748                let text = text.strip_prefix(char::is_whitespace).unwrap_or(text);
749                if text.is_empty() {
750                    return Ok(None);
751                }
752                return Ok(Some(Thing::Element(SpineComment::new(text).into())));
753            }
754            let mut element = if contents.contains(' ') {
755                chord_of(contents)?
756            } else {
757                note_of(contents, None)?
758            };
759            state.beam(&mut element);
760            state.tuplet(&mut stream, &mut element);
761            Ok(Some(Thing::Element(element)))
762        })();
763        // A cell that cannot be read is passed over, as music21 passes over
764        // it with a warning.
765        let Ok(Some(thing)) = made else {
766            continue;
767        };
768        let is_measure = matches!(thing, Thing::Measure(_));
769        let sounds = matches!(
770            &thing,
771            Thing::Element(
772                element @ (StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_))
773            ) if !element.duration().is_some_and(Duration::is_grace)
774        );
775        let index = append(&mut stream, *line, *line, thing);
776        if is_measure {
777            last_measure = Some(index);
778        }
779        if sounds {
780            state.last_note = Some(index);
781        }
782    }
783    stream
784}
785
786/// music21's `DynamSpine.parse` and the generic one: the marks and words of
787/// a spine that is no `**kern`, each with its line.
788fn parse_other(events: &[(String, usize)], dynamics: bool) -> Vec<Placed> {
789    let mut stream = Vec::new();
790    for (contents, line) in events {
791        let thing = if contents == "." || contents.is_empty() {
792            continue;
793        } else if contents.starts_with('*') {
794            if SPINE_PATHS.contains(&contents.as_str()) {
795                continue;
796            }
797            Thing::Tandem(contents.clone())
798        } else if contents.starts_with('=') || contents.starts_with('!') {
799            continue;
800        } else if dynamics {
801            if contents.starts_with('<') || contents.starts_with('>') {
802                continue;
803            }
804            Thing::Element(Dynamic::new(contents.as_str()).into())
805        } else {
806            Thing::Word(contents.clone())
807        };
808        append(&mut stream, *line, 0, thing);
809    }
810    stream
811}
812
813/// music21's `HarmSpine.parse`: the harmonic annotations of a `**harm`
814/// spine, each read as a roman numeral in the key the spine last named.
815///
816/// A chord carries the numeral, as every numeral this crate reads does, so
817/// the notes are the ones the figure stands for in that key. The minor
818/// sixth and seventh are read as music21 reads them here -- the sixth flat
819/// and the seventh sharp -- and a token that is no `**harm` token is passed
820/// over, which is what music21 does after warning about it.
821fn parse_harm(events: &[(String, usize)]) -> Result<Vec<Placed>> {
822    let mut stream = Vec::new();
823    // music21's `HarmSpine` starts in C major and takes every later key
824    // from the spine's own tandems.
825    let mut key = Key::from_tonic("C")?;
826    for (contents, line) in events {
827        if contents == "." || contents.is_empty() {
828            continue;
829        }
830        if contents.starts_with('*') {
831            if SPINE_PATHS.contains(&contents.as_str()) {
832                continue;
833            }
834            match contents.strip_suffix(':') {
835                Some(named) => key = Key::from_tonic(&named[1..])?,
836                // Anything else is kept as it stands, `*staff` among it,
837                // which is what says whose harmony this is.
838                None => {
839                    append(&mut stream, *line, 0, Thing::Tandem(contents.clone()));
840                }
841            }
842            continue;
843        }
844        if contents.starts_with('=') || contents.starts_with('!') {
845            continue;
846        }
847        let Some(figure) = harm::convert_harm_to_roman(contents) else {
848            continue;
849        };
850        let numeral = RomanNumeral::with_minor_defaults(
851            &figure,
852            key.clone(),
853            Minor67Default::Flat,
854            Minor67Default::Sharp,
855        )?;
856        let chord = numeral.to_chord()?.with_numeral(numeral);
857        append(&mut stream, *line, 0, Thing::Element(chord.into()));
858    }
859    Ok(stream)
860}
861
862/// music21's `createHumdrumSpines`: the spines a table's columns run
863/// through, splitting, joining and changing places as the table says.
864fn spines_of(lines: &[(usize, Vec<String>)], width: usize) -> Result<Vec<Spine>> {
865    let mut spines: Vec<Spine> = Vec::new();
866    let mut current: Vec<Option<usize>> = Vec::new();
867    for (line, cells) in lines {
868        if current.len() < width {
869            current.resize(width, None);
870        }
871        for (column, cell) in cells.iter().enumerate().take(width) {
872            let spine = match current[column] {
873                Some(spine) => spine,
874                None => {
875                    spines.push(Spine::default());
876                    current[column] = Some(spines.len() - 1);
877                    spines.len() - 1
878                }
879            };
880            spines[spine].events.push((cell.clone(), *line));
881        }
882        if !cells
883            .iter()
884            .any(|cell| SPINE_PATHS.contains(&cell.as_str()))
885        {
886            continue;
887        }
888        let mut next: Vec<Option<usize>> = Vec::new();
889        let mut merging = false;
890        let mut merge_parent: Option<usize> = None;
891        let mut exchange: Option<usize> = None;
892        for (column, spine) in current.clone().into_iter().enumerate().take(width) {
893            let Some(cell) = cells.get(column) else {
894                if spine.is_some() {
895                    next.push(spine);
896                }
897                continue;
898            };
899            let Some(spine) = spine else {
900                return Err(humdrum_error(format!(
901                    "spine-path token at line {line} has no active spine"
902                ))
903                .placed(|| Position::line(*line)));
904            };
905            match cell.as_str() {
906                "*-" => {}
907                "*^" => {
908                    let mut voices = Vec::new();
909                    for first in [true, false] {
910                        spines.push(Spine {
911                            parent: Some(spine),
912                            first_voice: first,
913                            ..Spine::default()
914                        });
915                        voices.push(spines.len() - 1);
916                        next.push(Some(spines.len() - 1));
917                    }
918                    spines[spine].splits.insert(*line, voices);
919                }
920                "*v" => {
921                    if merging {
922                        let parent = spines[spine].parent.or(merge_parent);
923                        match parent {
924                            Some(parent) => next.push(Some(parent)),
925                            None => {
926                                spines.push(Spine::default());
927                                next.push(Some(spines.len() - 1));
928                            }
929                        }
930                        merging = false;
931                        merge_parent = None;
932                    } else {
933                        merging = true;
934                        merge_parent = spines[spine].parent;
935                    }
936                }
937                "*x" => match exchange.take() {
938                    None => exchange = Some(spine),
939                    Some(waiting) => {
940                        next.push(Some(spine));
941                        next.push(Some(waiting));
942                    }
943                },
944                _ => next.push(Some(spine)),
945            }
946        }
947        if exchange.is_some() {
948            return Err(humdrum_error(format!(
949                "ProtoSpine found with unpaired exchange instruction at line {line}"
950            ))
951            .placed(|| Position::line(*line)));
952        }
953        current = next;
954    }
955    Ok(spines)
956}
957
958/// The kind of a spine: what its `**` heading says, or its parent's.
959fn kind_of(spines: &[Spine], index: usize) -> Result<String> {
960    let mut at = Some(index);
961    while let Some(spine) = at {
962        if let Some(kind) = spines[spine]
963            .events
964            .iter()
965            .find_map(|(contents, _)| contents.strip_prefix("**"))
966        {
967            return Ok(kind.to_string());
968        }
969        at = spines[spine].parent;
970    }
971    Err(humdrum_error(format!(
972        "Could not determine spineType for spine with id {index}"
973    )))
974}
975
976/// music21's `performInsertions`: the voices a spine split into, put back
977/// into it where it split.
978fn insert_voices(spines: &mut [Spine], index: usize) {
979    let mut points: Vec<usize> = spines[index].splits.keys().copied().collect();
980    if points.is_empty() {
981        return;
982    }
983    let old = std::mem::take(&mut spines[index].stream);
984    let mut new: Vec<Placed> = Vec::new();
985    let insert = |new: &mut Vec<Placed>, spines: &[Spine], point: usize| {
986        let start = highest(new);
987        for (number, child) in spines[index].splits[&point].iter().enumerate() {
988            for thing in &spines[*child].stream {
989                if !spines[*child].first_voice && matches!(thing.thing, Thing::Measure(_)) {
990                    continue;
991                }
992                let mut thing = thing.clone();
993                thing.offset += start;
994                thing.voice.get_or_insert(number + 1);
995                new.push(thing);
996            }
997        }
998    };
999    let mut last: Option<usize> = None;
1000    for thing in old {
1001        // Python walks the list it is taking things out of, so the point
1002        // after one that is used is passed over until the next element.
1003        let mut at = 0;
1004        while at < points.len() {
1005            let point = points[at];
1006            if last.is_some_and(|last| last > point) || thing.line < point {
1007                at += 1;
1008                continue;
1009            }
1010            insert(&mut new, spines, point);
1011            points.remove(at);
1012            at += 1;
1013        }
1014        last = Some(thing.line);
1015        let offset = highest(&new);
1016        new.push(Placed { offset, ..thing });
1017    }
1018    for point in points {
1019        insert(&mut new, spines, point);
1020    }
1021    spines[index].stream = new;
1022}
1023
1024/// A measure while elements are gathered into it.
1025struct Gathered {
1026    measure: Measure,
1027    offset: FloatType,
1028    /// The line of the barline opening it, which music21 sorts it by.
1029    priority: usize,
1030    things: Vec<Placed>,
1031}
1032
1033/// music21's `moveElementsIntoMeasures`: the barlines of a spine as the
1034/// measures its notes are in.
1035fn into_measures(mut stream: Vec<Placed>) -> (Vec<Gathered>, Vec<Placed>) {
1036    sort(&mut stream);
1037    let mut measures: Vec<Gathered> = Vec::new();
1038    let mut loose: Vec<Placed> = Vec::new();
1039    let mut current = Gathered {
1040        measure: Measure::default(),
1041        offset: 0.0,
1042        priority: 0,
1043        things: Vec::new(),
1044    };
1045    let mut number = 0;
1046    let mut has_one = false;
1047    for thing in stream {
1048        match thing.thing {
1049            Thing::Measure(measure) => {
1050                if number != 0 || !current.things.is_empty() {
1051                    measures.push(current);
1052                }
1053                number = measure.number;
1054                has_one |= number == 1;
1055                current = Gathered {
1056                    measure,
1057                    offset: thing.offset,
1058                    priority: thing.priority,
1059                    things: Vec::new(),
1060                };
1061            }
1062            _ => {
1063                if number != 0 || thing.length() != 0.0 {
1064                    let offset = thing.offset - current.offset;
1065                    current.things.push(Placed { offset, ..thing });
1066                } else {
1067                    loose.push(thing);
1068                }
1069            }
1070        }
1071    }
1072    if !current.things.is_empty() {
1073        measures.push(current);
1074    }
1075    if let Some(first) = measures.first_mut() {
1076        if !has_one {
1077            first.measure.number = 1;
1078        }
1079        // What stands at the very start outside any measure goes into the
1080        // first, but for the instructions that name nothing.
1081        let (moved, kept): (Vec<Placed>, Vec<Placed>) = loose
1082            .into_iter()
1083            .partition(|thing| thing.offset == 0.0 && matches!(thing.thing, Thing::Element(_)));
1084        first.things.extend(moved);
1085        loose = kept;
1086    }
1087    (measures, loose)
1088}
1089
1090/// music21's `TupletFixer`: a run of tuplet notes that does not fill its
1091/// bracket is written in the longest or the shortest value among them.
1092fn fix_tuplets(things: &mut [Placed]) {
1093    let tuplets = |thing: &Placed| match &thing.thing {
1094        Thing::Element(
1095            element @ (StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_)),
1096        ) => Some(
1097            element
1098                .duration()
1099                .map(Duration::tuplets)
1100                .unwrap_or_default(),
1101        ),
1102        _ => None,
1103    };
1104    let mut groups: Vec<Vec<usize>> = Vec::new();
1105    let mut current: Vec<usize> = Vec::new();
1106    for (index, thing) in things.iter().enumerate() {
1107        let Some(held) = tuplets(thing) else {
1108            continue;
1109        };
1110        let Some(first) = held.first() else {
1111            if !current.is_empty() {
1112                groups.push(std::mem::take(&mut current));
1113            }
1114            continue;
1115        };
1116        current.push(index);
1117        if first.tuplet_type() == Some(TupletType::Stop) {
1118            groups.push(std::mem::take(&mut current));
1119        }
1120    }
1121    if !current.is_empty() {
1122        groups.push(current);
1123    }
1124    let ordinal = |kind: DurationType| DurationType::ALL.iter().position(|held| *held == kind);
1125    for group in groups {
1126        let Some(first) = tuplets(&things[group[0]]).and_then(|held| held.into_iter().next())
1127        else {
1128            continue;
1129        };
1130        let total = op_frac(first.total_tuplet_length());
1131        let mut length = 0.0;
1132        let mut smallest: Option<usize> = None;
1133        let mut largest: Option<usize> = None;
1134        for index in &group {
1135            length = op_frac(length + things[*index].length());
1136            let Some(kind) = tuplets(&things[*index])
1137                .and_then(|held| held.into_iter().next())
1138                .and_then(|tuplet| ordinal(tuplet.duration_type()))
1139            else {
1140                continue;
1141            };
1142            smallest = Some(smallest.map_or(kind, |held| held.max(kind)));
1143            largest = Some(largest.map_or(kind, |held| held.min(kind)));
1144        }
1145        if length == total || total == 0.0 {
1146            continue;
1147        }
1148        let excess = op_frac(length / total);
1149        let inverse = op_frac(1.0 / excess);
1150        let chosen = if excess.fract() == 0.0 {
1151            largest
1152        } else if inverse.fract() == 0.0 {
1153            smallest
1154        } else {
1155            None
1156        };
1157        let Some(kind) = chosen.map(|index| DurationType::ALL[index]) else {
1158            continue;
1159        };
1160        for index in group {
1161            if let Thing::Element(element) = &mut things[index].thing {
1162                with_duration(element, |duration| {
1163                    let mut held = duration.tuplets();
1164                    if let Some(first) = held.first_mut() {
1165                        let normal = first.duration_normal().map_or(0, |(_, dots)| dots);
1166                        let actual = first.duration_actual().map_or(0, |(_, dots)| dots);
1167                        first.set_duration_normal(Some((kind, normal)));
1168                        first.set_duration_actual(Some((kind, actual)));
1169                    }
1170                    duration.set_tuplets(held);
1171                });
1172            }
1173        }
1174    }
1175}
1176
1177/// music21's `makeVoices`: the things of a split spine, in the voices they
1178/// came from.
1179fn measure_stream(gathered: Gathered) -> Stream {
1180    let mut things = gathered.things;
1181    sort(&mut things);
1182    fix_tuplets(&mut things);
1183    let lowest = things
1184        .iter()
1185        .find(|thing| thing.voice == Some(1))
1186        .map(|thing| thing.offset);
1187    let mut voices: BTreeMap<usize, Vec<Placed>> = BTreeMap::new();
1188    let mut own: Vec<Placed> = Vec::new();
1189    for thing in things {
1190        match (lowest, thing.voice) {
1191            (Some(lowest), Some(voice)) => voices.entry(voice).or_default().push(Placed {
1192                offset: thing.offset - lowest,
1193                ..thing
1194            }),
1195            _ => own.push(thing),
1196        }
1197    }
1198    let events = |things: Vec<Placed>| -> Vec<(FloatType, usize, i32, StreamElement)> {
1199        things
1200            .into_iter()
1201            .filter_map(|thing| {
1202                let priority = thing.priority;
1203                let element = element_of(thing.thing, priority)?;
1204                Some((thing.offset, priority, element.class_sort_order(), element))
1205            })
1206            .collect()
1207    };
1208    let mut held = events(own);
1209    for (_, things) in voices {
1210        let voice = Stream::with_kind(StreamKind::Voice).with_events(
1211            events(things)
1212                .into_iter()
1213                .map(|(offset, priority, _, element)| placed_event(offset, priority, element))
1214                .collect(),
1215        );
1216        held.push((lowest.unwrap_or(0.0), 0, 5, voice.into()));
1217    }
1218    held.sort_by(|left, right| {
1219        left.0
1220            .total_cmp(&right.0)
1221            .then(left.1.cmp(&right.1))
1222            .then(left.2.cmp(&right.2))
1223    });
1224    let mut measure = Stream::with_kind(StreamKind::Measure).with_events(
1225        held.into_iter()
1226            .map(|(offset, priority, _, element)| placed_event(offset, priority, element))
1227            .collect(),
1228    );
1229    measure.set_number(gathered.measure.number);
1230    measure.set_number_suffix(gathered.measure.suffix);
1231    measure.set_left_barline(gathered.measure.left);
1232    measure.set_right_barline(gathered.measure.right);
1233    measure
1234}
1235
1236/// An element where it stands, sorted by the line it was written on.
1237fn placed_event(offset: FloatType, priority: usize, element: StreamElement) -> StreamEvent {
1238    StreamEvent::new(offset, element).with_priority(i32::try_from(priority).unwrap_or(i32::MAX))
1239}
1240
1241/// What a thing stands in the score as: an element as itself, an
1242/// instruction as a `MiscTandem` sorted by its priority, and nothing for
1243/// the rest.
1244fn element_of(thing: Thing, priority: usize) -> Option<StreamElement> {
1245    match thing {
1246        Thing::Element(element) => Some(element),
1247        Thing::Tandem(tandem) => Some(MiscTandem::new(tandem, priority as i32).into()),
1248        Thing::Measure(_) | Thing::Word(_) => None,
1249    }
1250}
1251
1252/// A first measure shorter than the bar of the meter it opens with is a
1253/// pickup, padded on the left by what it lacks: music21's
1254/// `moveElementsIntoMeasures`.
1255fn pad_pickup(measure: &mut Stream, length: FloatType) {
1256    let opening = measure
1257        .events()
1258        .iter()
1259        .find_map(|event| match event.element() {
1260            StreamElement::TimeSignature(meter) if event.offset() == 0.0 => Some(meter),
1261            _ => None,
1262        });
1263    let Some(meter) = opening else {
1264        return;
1265    };
1266    let bar = meter.bar_duration().quarter_length();
1267    if length < bar {
1268        measure.set_padding_left(op_frac(bar - length));
1269    }
1270}
1271
1272/// The staff a spine says it belongs to, or the staves.
1273fn staves_of(things: &[Placed]) -> Vec<IntegerType> {
1274    things
1275        .iter()
1276        .find_map(|thing| match &thing.thing {
1277            Thing::Tandem(tandem) => tandem.strip_prefix("*staff"),
1278            _ => None,
1279        })
1280        .map(|staves| {
1281            staves
1282                .split('/')
1283                .filter_map(|staff| staff.parse().ok())
1284                .collect()
1285        })
1286        .unwrap_or_default()
1287}
1288
1289/// music21's `GlobalReference.updateMetadata`: a `!!!` line as a fact about
1290/// the piece.
1291fn reference(metadata: &mut Metadata, line: &str) -> Result<()> {
1292    let text = line.trim_start_matches('!');
1293    let (code, value) = text
1294        .split_once(':')
1295        .ok_or_else(|| humdrum_error(format!("a reference line with no code: {line}")))?;
1296    let code = code.trim().replace("@@", "@");
1297    let code = code.split('@').next().unwrap_or("");
1298    let value = value.trim();
1299    match REFERENCE_NAMES.iter().find(|(held, _)| *held == code) {
1300        Some((_, "")) => metadata.add_text(format!("humdrum:{code}"), value),
1301        Some((_, name)) if is_contributor_unique_name(name) => {
1302            metadata.add_contributor(*name, value);
1303        }
1304        Some((_, name)) => metadata.add_text(*name, value),
1305        None => metadata.add_text(code, value),
1306    }
1307    Ok(())
1308}
1309
1310/// music21's `parseNonOpus`: one table as a score.
1311fn score_of(lines: &[&str]) -> Result<Stream> {
1312    let mut metadata = Metadata::new();
1313    let mut rows: Vec<(usize, Vec<String>)> = Vec::new();
1314    let mut globals: Vec<GlobalLine> = Vec::new();
1315    let mut run = 0;
1316    let mut width = 0;
1317    for (index, line) in lines.iter().enumerate() {
1318        let line = line.trim_end();
1319        if line.is_empty() {
1320            run = 0;
1321            continue;
1322        }
1323        if line.starts_with("!!") {
1324            run += 1;
1325        } else {
1326            run = 0;
1327        }
1328        if line.starts_with("!!!") {
1329            reference(&mut metadata, line)
1330                .map_err(|error| error.placed(|| Position::line(index + 1)))?;
1331        } else if line.starts_with("!!") {
1332            globals.push(GlobalLine {
1333                line: index + 1,
1334                run,
1335                comment: comment_text(line).to_string(),
1336            });
1337        } else {
1338            let cells: Vec<String> = line
1339                .split('\t')
1340                .enumerate()
1341                .filter(|(place, cell)| *place == 0 || !cell.is_empty())
1342                .map(|(_, cell)| cell.to_string())
1343                .collect();
1344            width = width.max(cells.len());
1345            rows.push((index + 1, cells));
1346        }
1347    }
1348    let mut spines = spines_of(&rows, width)?;
1349    for index in 0..spines.len() {
1350        spines[index].kind = kind_of(&spines, index)?;
1351    }
1352    for spine in &mut spines {
1353        spine.stream = match spine.kind.as_str() {
1354            "kern" => parse_kern(&spine.events),
1355            "dynam" => parse_other(&spine.events, true),
1356            "harm" => parse_harm(&spine.events)?,
1357            _ => parse_other(&spine.events, false),
1358        };
1359    }
1360    let places = line_places(&spines);
1361    for index in 0..spines.len() {
1362        if spines[index].parent.is_none() {
1363            insert_voices(&mut spines, index);
1364        }
1365    }
1366
1367    // The top spines as measures, with what stands outside them.
1368    let mut parts: Vec<(usize, Vec<Gathered>, Vec<Placed>)> = Vec::new();
1369    for (index, spine) in spines.iter().enumerate() {
1370        if spine.parent.is_none() && spine.kind == "kern" {
1371            let (measures, loose) = into_measures(spine.stream.clone());
1372            parts.push((index, measures, loose));
1373        }
1374    }
1375    // Which part each staff is: the first `*staff` a spine leaves standing
1376    // outside its measures.
1377    let staff_of = |loose: &[Placed]| -> Option<IntegerType> {
1378        loose.iter().find_map(|thing| match &thing.thing {
1379            Thing::Tandem(tandem) => tandem
1380                .strip_prefix("*staff")
1381                .and_then(|staff| staff.parse().ok()),
1382            _ => None,
1383        })
1384    };
1385    for spine in spines
1386        .iter()
1387        .filter(|spine| spine.parent.is_none() && spine.kind != "kern")
1388    {
1389        let staves = staves_of(&spine.stream);
1390        let is_words = matches!(spine.kind.as_str(), "lyrics" | "text");
1391        let is_marks = matches!(spine.kind.as_str(), "dynam" | "harm");
1392        if !is_marks && !is_words {
1393            continue;
1394        }
1395        for staff in staves {
1396            let Some((_, measures, _)) = parts
1397                .iter_mut()
1398                .find(|(_, _, loose)| staff_of(loose) == Some(staff))
1399            else {
1400                if is_marks {
1401                    return Err(humdrum_error(format!(
1402                        "no staff {staff} for the {} spine",
1403                        spine.kind
1404                    )));
1405                }
1406                continue;
1407            };
1408            for thing in &spine.stream {
1409                for measure in measures.iter_mut() {
1410                    let found = measure
1411                        .things
1412                        .iter()
1413                        .position(|held| held.priority == thing.line);
1414                    match (&thing.thing, found) {
1415                        (
1416                            Thing::Element(
1417                                mark @ (StreamElement::Dynamic(_) | StreamElement::Chord(_)),
1418                            ),
1419                            Some(found),
1420                        ) => {
1421                            let offset = measure.things[found].offset;
1422                            // music21's `HarmSpine` gives its numerals the
1423                            // line they were written on as their priority,
1424                            // which sorts them after the meter and the key
1425                            // standing at the same offset; its `DynamSpine`
1426                            // gives a dynamic none.
1427                            let priority = match mark {
1428                                StreamElement::Chord(_) => thing.line,
1429                                _ => 0,
1430                            };
1431                            measure.things.push(Placed {
1432                                offset,
1433                                line: thing.line,
1434                                priority,
1435                                voice: None,
1436                                thing: Thing::Element(mark.clone()),
1437                            });
1438                            break;
1439                        }
1440                        (Thing::Word(word), Some(_)) => {
1441                            for held in &mut measure.things {
1442                                if held.priority != thing.line {
1443                                    continue;
1444                                }
1445                                match &mut held.thing {
1446                                    Thing::Element(StreamElement::Note(note)) => {
1447                                        note.set_lyric(Some(word))?;
1448                                    }
1449                                    Thing::Element(StreamElement::Rest(rest)) => {
1450                                        rest.set_lyric(Some(word));
1451                                    }
1452                                    Thing::Element(StreamElement::Chord(chord)) => {
1453                                        if let Some(singer) = chord.notes_mut().first_mut() {
1454                                            singer.set_lyric(Some(word))?;
1455                                        }
1456                                    }
1457                                    _ => {}
1458                                }
1459                            }
1460                        }
1461                        _ => {}
1462                    }
1463                }
1464            }
1465        }
1466    }
1467
1468    // The parts come out in the order opposite to the file's, which is
1469    // written lowest part first.
1470    let mut events = Vec::new();
1471    for (spine, measures, loose) in parts.into_iter().rev() {
1472        // The instructions before the first barline stay in the part, ahead
1473        // of its measures, where music21 leaves them.
1474        let mut held: Vec<StreamEvent> = loose
1475            .into_iter()
1476            .filter(|thing| matches!(thing.thing, Thing::Tandem(_)))
1477            .filter_map(|thing| {
1478                let element = element_of(thing.thing, thing.priority)?;
1479                Some(placed_event(thing.offset, thing.priority, element))
1480            })
1481            .collect();
1482        let mut offset = 0.0;
1483        for (index, gathered) in measures.into_iter().enumerate() {
1484            let priority = gathered.priority;
1485            let mut measure = measure_stream(gathered);
1486            let length = measure.end_offset();
1487            if index == 0 {
1488                pad_pickup(&mut measure, length);
1489            }
1490            held.push(placed_event(offset, priority, measure.into()));
1491            offset = op_frac(offset + length);
1492        }
1493        let mut part = Stream::with_kind(StreamKind::Part).with_events(held);
1494        // music21 names a part by the spine it was read from.
1495        part.set_id(Some(format!("spine_{spine}")));
1496        events.push(StreamEvent::new(0.0, part));
1497    }
1498    place_comments(&mut events, &globals, &places);
1499    let mut score = Stream::with_kind(StreamKind::Score).with_events(events);
1500    score.set_metadata(Some(metadata));
1501    Ok(score)
1502}
1503
1504/// A `!!` line: its line number, how many global lines run into it, and
1505/// what it says.
1506struct GlobalLine {
1507    line: usize,
1508    run: usize,
1509    comment: String,
1510}
1511
1512/// music21's `GlobalCommentLine.value`: the line without its bangs and the
1513/// one space after them.
1514fn comment_text(line: &str) -> &str {
1515    let text = line.trim_start_matches('!');
1516    text.strip_prefix(char::is_whitespace).unwrap_or(text)
1517}
1518
1519/// music21's `getOffsetsAndPrioritiesByLineNumber`: the offset and priority
1520/// of the first thing each line holds, the top spines read in order and
1521/// their measures passed over.
1522fn line_places(spines: &[Spine]) -> BTreeMap<usize, (FloatType, usize)> {
1523    let mut places = BTreeMap::new();
1524    let tops = spines.iter().filter(|spine| spine.parent.is_none());
1525    for placed in tops.flat_map(|spine| &spine.stream) {
1526        if matches!(placed.thing, Thing::Measure(_)) {
1527            continue;
1528        }
1529        places
1530            .entry(placed.line)
1531            .or_insert((placed.offset, placed.priority));
1532    }
1533    places
1534}
1535
1536/// music21's `insertGlobalEvents`: each comment before the next line that
1537/// holds anything, 40 ahead of its priority and one back for each global
1538/// line running into it; a comment with no such line goes at the end.
1539///
1540/// ```text
1541/// !!a        -> offset of `4c`, priority 6 - 40 + 1
1542/// !!!COM: X     (counted in the run, but metadata)
1543/// !!b        -> offset of `4c`, priority 6 - 40 + 3
1544/// 4c            (line 6)
1545/// ```
1546fn place_comments(
1547    events: &mut Vec<StreamEvent>,
1548    globals: &[GlobalLine],
1549    places: &BTreeMap<usize, (FloatType, usize)>,
1550) {
1551    const AHEAD: i32 = 40;
1552    let key = |event: &StreamEvent| match event.element() {
1553        StreamElement::GlobalComment(said) => (event.offset(), said.priority()),
1554        _ => (event.offset(), 0),
1555    };
1556    let mut trailing = Vec::new();
1557    for global in globals {
1558        let Some((_, (offset, priority))) = places.range(global.line..).next() else {
1559            trailing.push(GlobalComment::new(global.comment.clone(), 0));
1560            continue;
1561        };
1562        let priority = *priority as i32 - AHEAD + global.run as i32;
1563        let comment = GlobalComment::new(global.comment.clone(), priority);
1564        let index = events
1565            .iter()
1566            .position(|event| {
1567                let (held, ahead) = key(event);
1568                held > *offset || (held == *offset && ahead > priority)
1569            })
1570            .unwrap_or(events.len());
1571        events.insert(index, StreamEvent::new(*offset, comment));
1572    }
1573    let end = events
1574        .iter()
1575        .map(StreamEvent::end_offset)
1576        .fold(0.0, FloatType::max);
1577    for comment in trailing {
1578        events.push(StreamEvent::new(end, comment));
1579    }
1580}
1581
1582/// Reads a Humdrum file into a score: music21's `converter.parse` of one.
1583///
1584/// Every `**kern` spine is a part, the last in the file first; its barlines
1585/// are the measures, its instructions the clefs, keys, meters, tempos and
1586/// instruments, and a spine split in two is two voices in the measures where
1587/// it is split. `**dynam` and `**text` spines put their dynamics and words
1588/// on the notes of the staff they name. A cell that cannot be read is passed
1589/// over, as music21 passes over it.
1590///
1591/// A file holding several tables, each closed before the next opens, comes
1592/// back as an opus of scores. `**harm` spines, and a voice that splits
1593/// again, are not read.
1594///
1595/// ```
1596/// use music21::humdrum::from_humdrum;
1597///
1598/// let score = from_humdrum("**kern\n*clefG2\n*M2/4\n=1\n4c\n4d\n=2\n2e\n==\n*-\n")?;
1599/// let part = score.parts()[0];
1600/// assert_eq!(part.measures().len(), 2);
1601/// assert_eq!(part.measures()[1].pitches()[0].name_with_octave(), "E4");
1602/// # Ok::<(), music21::Error>(())
1603/// ```
1604pub fn from_humdrum(text: &str) -> Result<Stream> {
1605    let lines: Vec<&str> = text.lines().collect();
1606    if lines.is_empty() {
1607        return Err(humdrum_error("Need a list of lines to parse!"));
1608    }
1609    // music21's `determineIfDataStreamIsOpus`: a line of nothing but `**`
1610    // headings opens a table, one of nothing but `*-` closes it.
1611    let all = |line: &str, test: fn(&str) -> bool| {
1612        let line = line.trim_end();
1613        !line.is_empty() && line.split('\t').all(test)
1614    };
1615    let heading: fn(&str) -> bool = |cell| {
1616        cell.strip_prefix("**").is_some_and(|name| {
1617            !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_')
1618        })
1619    };
1620    let starts: Vec<usize> = (0..lines.len())
1621        .filter(|index| all(lines[*index], heading))
1622        .collect();
1623    let mut ends: Vec<usize> = (0..lines.len())
1624        .filter(|index| all(lines[*index], |cell| cell == "*-"))
1625        .collect();
1626    if starts.len() < 2 {
1627        return score_of(&lines);
1628    }
1629    if ends.last().copied().unwrap_or(0) <= starts[starts.len() - 1] {
1630        ends.push(lines.len());
1631    }
1632    // Each piece, with how many lines of the document stand before it.
1633    let mut pieces: Vec<(usize, &[&str])> = Vec::new();
1634    for (index, end) in ends.iter().enumerate() {
1635        let start = if index == 0 { 0 } else { ends[index - 1] + 1 };
1636        let stop = if index + 1 == ends.len() {
1637            lines.len()
1638        } else {
1639            (*end + 1).min(lines.len())
1640        };
1641        pieces.push((start.min(stop), &lines[start.min(stop)..stop]));
1642    }
1643    if pieces.len() < 2 {
1644        return Err(humdrum_error(
1645            "Malformed Humdrum data: possibly multiple **tags without closing information.",
1646        ));
1647    }
1648    let mut events = Vec::new();
1649    let mut offset = 0.0;
1650    for (index, (before, piece)) in pieces.iter().enumerate() {
1651        let mut score = score_of(piece).map_err(|error| error.lines_down(*before))?;
1652        let mut metadata = score.metadata().cloned().unwrap_or_default();
1653        metadata.set(
1654            "number",
1655            vec![crate::metadata::MetadataValue::new((index + 1).to_string())],
1656        );
1657        score.set_metadata(Some(metadata));
1658        score.set_id(Some(format!("section_{}", index + 1)));
1659        let length = score.end_offset();
1660        events.push(StreamEvent::new(offset, score));
1661        offset = op_frac(offset + length);
1662    }
1663    // The opus says what its first score says, numbered `0`.
1664    let mut metadata = events
1665        .first()
1666        .and_then(|event| event.element().as_stream())
1667        .and_then(Stream::metadata)
1668        .cloned()
1669        .unwrap_or_default();
1670    metadata.set(
1671        "number",
1672        vec![crate::metadata::MetadataValue::new("0".to_string())],
1673    );
1674    let mut opus = Stream::with_kind(StreamKind::Opus).with_events(events);
1675    opus.set_metadata(Some(metadata));
1676    Ok(opus)
1677}
1678
1679#[cfg(test)]
1680mod tests {
1681    /// A `!!` line stands in the score at the offset of the next line
1682    /// holding anything, before what stands there, as music21's
1683    /// `insertGlobalEvents` puts it; one after the music goes at the end.
1684    #[test]
1685    fn global_comments_stand_in_the_score() {
1686        let text = "!!first\n**kern\n*M2/4\n=1\n!!mid\n4c\n!!second\n!!!COM: X\n\
1687                    !!third\n4d\n=2\n2e\n==\n*-\n!!last\n";
1688        let score = from_humdrum(text).unwrap();
1689        let seen: Vec<(FloatType, String, i32)> = score
1690            .events()
1691            .iter()
1692            .map(|event| match event.element() {
1693                StreamElement::GlobalComment(said) => {
1694                    (event.offset(), said.comment().to_string(), said.priority())
1695                }
1696                _ => (event.offset(), "part".to_string(), 0),
1697            })
1698            .collect();
1699        let expected = [
1700            (0.0, "first", -37),
1701            (0.0, "mid", -33),
1702            (0.0, "part", 0),
1703            (1.0, "second", -29),
1704            (1.0, "third", -27),
1705            (4.0, "last", 0),
1706        ];
1707        let expected: Vec<(FloatType, String, i32)> = expected
1708            .iter()
1709            .map(|(offset, said, priority)| (*offset, said.to_string(), *priority))
1710            .collect();
1711        assert_eq!(seen, expected);
1712    }
1713
1714    /// An instruction the reader has no object for stands where it was
1715    /// written as a `MiscTandem`, its line its priority, as music21 keeps
1716    /// it: those before the first barline in the part, the rest in their
1717    /// measure.
1718    #[test]
1719    fn tandems_stand_where_they_were_written() {
1720        let text = "**kern\t**dynam\n*staff1\t*staff1\n*clefG2\t*\n*Xtandy\t*\n\
1721                    *M2/4\t*\n=1\t=1\n4c\tp\n*foo\t*\n4d\t.\n==\t==\n*-\t*-\n";
1722        let score = from_humdrum(text).unwrap();
1723        let tandems = |stream: &Stream| -> Vec<(FloatType, String, i32)> {
1724            stream
1725                .events()
1726                .iter()
1727                .filter_map(|event| match event.element() {
1728                    StreamElement::MiscTandem(tandem) => Some((
1729                        event.offset(),
1730                        tandem.tandem().to_string(),
1731                        tandem.priority(),
1732                    )),
1733                    _ => None,
1734                })
1735                .collect()
1736        };
1737        let part = score.events()[0].element().as_stream().unwrap();
1738        let measure = part
1739            .events()
1740            .iter()
1741            .find_map(|event| event.element().as_stream())
1742            .unwrap();
1743        let owned = |held: &[(FloatType, &str, i32)]| -> Vec<(FloatType, String, i32)> {
1744            held.iter()
1745                .map(|(offset, said, priority)| (*offset, said.to_string(), *priority))
1746                .collect()
1747        };
1748        assert_eq!(
1749            tandems(part),
1750            owned(&[(0.0, "**kern", 1), (0.0, "*staff1", 2), (0.0, "*Xtandy", 4)])
1751        );
1752        assert_eq!(tandems(measure), owned(&[(1.0, "*foo", 8)]));
1753    }
1754
1755    /// An opus is named as music21 names it: its scores `section_1` on,
1756    /// and its own metadata a copy of the first score's, numbered `0`.
1757    #[test]
1758    fn an_opus_takes_its_first_scores_metadata() {
1759        let text = "!!!OTL: Piece\n**kern\n4c\n*-\n!!!OTL: Other\n**kern\n4d\n*-\n";
1760        let opus = from_humdrum(text).unwrap();
1761        let read = |stream: &Stream, name: &str| -> String {
1762            stream.metadata().unwrap().get(name)[0].text().to_string()
1763        };
1764        assert_eq!(read(&opus, "title"), "Piece");
1765        assert_eq!(read(&opus, "number"), "0");
1766        let ids: Vec<Option<&str>> = opus
1767            .events()
1768            .iter()
1769            .filter_map(|event| event.element().as_stream())
1770            .map(Stream::id)
1771            .collect();
1772        assert_eq!(ids, [Some("section_1"), Some("section_2")]);
1773    }
1774
1775    /// A first measure shorter than its bar is a pickup, padded on the left
1776    /// by what it lacks, as music21's `moveElementsIntoMeasures` pads it.
1777    #[test]
1778    fn a_short_first_measure_is_padded_as_a_pickup() {
1779        let padding = |body: &str| {
1780            let text = format!("**kern\n*M2/4\n{body}\n=1\n2d\n=2\n*-\n");
1781            let score = from_humdrum(&text).unwrap();
1782            let part = score.parts()[0].clone();
1783            part.measures()
1784                .iter()
1785                .map(|measure| measure.padding_left())
1786                .collect::<Vec<_>>()
1787        };
1788        assert_eq!(padding("4c"), [1.0, 0.0]);
1789        assert_eq!(
1790            padding("12c"),
1791            [crate::makenotation::op_frac(5.0 / 3.0), 0.0]
1792        );
1793        assert_eq!(padding("2c"), [0.0, 0.0]);
1794    }
1795
1796    use super::*;
1797
1798    #[test]
1799    fn a_length_is_a_number_and_its_dots() {
1800        assert_eq!(duration_of("4c").unwrap().quarter_length(), 1.0);
1801        assert_eq!(duration_of("8.d").unwrap().quarter_length(), 0.75);
1802        assert_eq!(duration_of("0r").unwrap().quarter_length(), 8.0);
1803        // Twelfths are eighths, three in the time of two.
1804        let triplet = duration_of("12e").unwrap();
1805        assert!((triplet.quarter_length() - 1.0 / 3.0).abs() < 1e-9);
1806        assert_eq!(triplet.tuplets()[0].actual(), 3);
1807        assert!(duration_of("qf").unwrap().is_grace());
1808        assert!(duration_of("c").is_err());
1809    }
1810
1811    #[test]
1812    fn a_refusal_names_its_line_in_the_whole_document() {
1813        let unpaired = "**kern\t**kern\n*x\t*\n4c\t4d\n*-\t*-\n";
1814        let error = from_humdrum(unpaired).unwrap_err();
1815        assert_eq!(
1816            error.to_string(),
1817            "Humdrum error: ProtoSpine found with unpaired exchange instruction at line 2"
1818        );
1819        assert_eq!(error.position(), Some(&Position::line(2)));
1820
1821        let opus = format!("**kern\n4c\n*-\n{unpaired}");
1822        let error = from_humdrum(&opus).unwrap_err();
1823        assert_eq!(error.position(), Some(&Position::line(5)));
1824    }
1825
1826    #[test]
1827    fn letters_say_the_octave() {
1828        let name = |token: &str| match note_of(token, None).unwrap() {
1829            StreamElement::Note(note) => note.pitch().name_with_octave(),
1830            _ => "rest".to_string(),
1831        };
1832        assert_eq!(name("4c"), "C4");
1833        assert_eq!(name("4cc#"), "C#5");
1834        assert_eq!(name("4C"), "C3");
1835        assert_eq!(name("4BB-"), "Bb2");
1836        assert_eq!(name("4r"), "rest");
1837    }
1838
1839    #[test]
1840    fn a_grace_note_leaves_a_tuplet_whole() {
1841        // music21's `testGraceNoteInsideTuplet`.
1842        let types = |cells: &[&str]| -> Vec<Option<&'static str>> {
1843            let events: Vec<(String, usize)> = cells
1844                .iter()
1845                .enumerate()
1846                .map(|(line, cell)| (cell.to_string(), line))
1847                .collect();
1848            parse_kern(&events)
1849                .iter()
1850                .filter_map(|placed| match &placed.thing {
1851                    Thing::Element(element) => Some(
1852                        element
1853                            .duration()?
1854                            .tuplets()
1855                            .first()
1856                            .and_then(|tuplet| tuplet.tuplet_type())
1857                            .map(TupletType::as_str),
1858                    ),
1859                    _ => None,
1860                })
1861                .collect()
1862        };
1863        assert_eq!(
1864            types(&["12c", "12qd", "12e", "12f", "4g"]),
1865            [Some("start"), None, None, Some("stop"), None]
1866        );
1867        assert_eq!(
1868            types(&["12c", "12e", "12qd", "12f", "4g"]),
1869            [Some("start"), None, None, Some("stop"), None]
1870        );
1871        // A grace note after an unfinished tuplet: it still stops on its last note.
1872        assert_eq!(
1873            types(&["12c", "12e", "16qd", "4g"]),
1874            [Some("start"), Some("stop"), None, None]
1875        );
1876    }
1877
1878    #[test]
1879    fn a_barline_says_how_the_measure_before_ends() {
1880        let (measure, right) = measure_of("=12a:|!", true);
1881        assert_eq!(measure.number, 12);
1882        assert_eq!(measure.suffix.as_deref(), Some("a"));
1883        assert_eq!(right.repeat_direction(), Some(RepeatDirection::End));
1884        let (_, double) = measure_of("==", true);
1885        assert_eq!(double.bar_type(), BarlineType::Double);
1886    }
1887
1888    #[test]
1889    fn spines_are_parts_and_the_last_comes_first() {
1890        let score = from_humdrum(
1891            "**kern\t**kern\n*clefF4\t*clefG2\n*M2/4\t*M2/4\n=1\t=1\n2C\t4c\n.\t4d\n=2\t=2\n\
1892             2D\t2e\n==\t==\n*-\t*-\n",
1893        )
1894        .unwrap();
1895        let parts = score.parts();
1896        assert_eq!(parts.len(), 2);
1897        assert_eq!(parts[0].measures()[0].pitches().len(), 2);
1898        assert_eq!(parts[1].measures()[0].pitches()[0].name_with_octave(), "C3");
1899    }
1900
1901    /// music21 gives each thing it reads the line it was written on as its
1902    /// priority, a measure that of its barline, and names a part by its
1903    /// spine.
1904    #[test]
1905    fn what_is_read_keeps_its_line_and_its_spine() {
1906        let score = from_humdrum(
1907            "**kern\t**kern\n*clefF4\t*clefG2\n*M2/4\t*M2/4\n=1\t=1\n2C\t4c\n.\t4d\n=2\t=2\n\
1908             2D\t2e\n==\t==\n*-\t*-\n",
1909        )
1910        .unwrap();
1911        let parts = score.parts();
1912        assert_eq!(parts[0].id(), Some("spine_1"));
1913        assert_eq!(parts[1].id(), Some("spine_0"));
1914        let measures: Vec<i32> = parts[0]
1915            .events()
1916            .iter()
1917            .filter(|event| event.element().as_stream().is_some())
1918            .map(StreamEvent::priority)
1919            .collect();
1920        assert_eq!(measures, [4, 7]);
1921        let first = parts[0].measures()[0];
1922        let lines: Vec<i32> = first.events().iter().map(StreamEvent::priority).collect();
1923        // The clef, the meter and the two notes.
1924        assert_eq!(lines, [2, 3, 5, 6]);
1925    }
1926
1927    /// A `!` comment in a `**kern` spine stands where it was written, as
1928    /// music21's `SpineComment`; an empty one is passed over.
1929    #[test]
1930    fn a_spine_comment_stands_where_it_was_written() {
1931        let score = from_humdrum("**kern\n*M2/4\n!Cantus\n!\n=1\n2c\n==\n*-\n").unwrap();
1932        let said: Vec<(&str, i32)> = score.parts()[0].measures()[0]
1933            .events()
1934            .iter()
1935            .filter_map(|event| match event.element() {
1936                StreamElement::SpineComment(said) => Some((said.comment(), event.priority())),
1937                _ => None,
1938            })
1939            .collect();
1940        assert_eq!(said, [("Cantus", 3)]);
1941    }
1942
1943    #[test]
1944    fn a_split_spine_is_two_voices() {
1945        let score = from_humdrum("**kern\n*M2/4\n=1\n*^\n4c\t4e\n4d\t4f\n*v\t*v\n=2\n2g\n==\n*-\n")
1946            .unwrap();
1947        let part = score.parts()[0];
1948        let measures = part.measures();
1949        assert_eq!(measures[0].voices().len(), 2);
1950        assert!(measures[1].voices().is_empty());
1951    }
1952}