Skip to main content

music21/romantext/
mod.rs

1//! RomanText: music21's `romanText`, a harmonic analysis written measure by
2//! measure as roman numerals.
3//!
4//! ```text
5//! Composer: J. S. Bach
6//! Time Signature: 4/4
7//! m1 C: I b3 V6
8//! m2 I b2 ii6/5 b3 V7 b4 vi
9//! m3 = m1
10//! ```
11//!
12//! A line starting `m` and a number is a measure; in it a key is a note name
13//! and a colon, `b` and a number is the beat the next chord stands on, and
14//! anything else is a roman numeral. The other lines are a tag, a colon and
15//! what it says.
16
17pub mod clercq_temperley;
18pub mod tsv;
19
20use crate::bar::{Barline, Ending, RepeatDirection};
21use crate::chord::Chord;
22use crate::chordsymbol::ChordSymbol;
23use crate::defaults::{FloatType, IntegerType};
24use crate::duration::Duration;
25use crate::error::{Error, FormatErrorKind, Position, Result};
26use crate::key::{Key, KeySignature, convert_key_string_to_music21_key_string};
27use crate::makenotation::{MAX_MEASURES, make_accidentals, make_beams, op_frac, sorted_events};
28use crate::metadata::{Metadata, MetadataValue};
29use crate::meter::TimeSignature;
30use crate::notation::{Tie, TieType};
31use crate::rest::Rest;
32use crate::roman::{Minor67Default, RomanNumeral};
33use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
34
35fn roman_text_error(message: impl Into<String>) -> Error {
36    Error::roman_text(message.into())
37}
38
39/// One word of a measure line: music21's `RTAtom` and its kinds.
40#[derive(Clone, Debug, PartialEq)]
41enum Atom {
42    /// `b2.5`: the beat what follows stands on.
43    Beat(String),
44    /// `G;:` sets the key and its signature.
45    Key(String),
46    /// `G:` sets the key chords are read in.
47    AnalyticKey(String),
48    /// `KS-2`: a key signature of so many sharps.
49    KeySignature(String),
50    RepeatStart,
51    RepeatStop,
52    NoChord,
53    Chord(String),
54    /// A phrase mark, a parenthesis or an optional key, none of which says
55    /// anything a score holds.
56    Other,
57}
58
59/// One line: music21's `RTTagged` and `RTMeasure`.
60#[derive(Clone, Debug)]
61enum Token {
62    Tagged {
63        tag: String,
64        data: String,
65    },
66    Measure {
67        numbers: Vec<IntegerType>,
68        letters: Vec<String>,
69        variant: bool,
70        data: String,
71        atoms: Vec<Atom>,
72    },
73}
74
75impl Token {
76    fn is_movement(&self) -> bool {
77        matches!(self, Self::Tagged { tag, .. } if tag.eq_ignore_ascii_case("movement"))
78    }
79}
80
81/// How far a measure tag reaches into a line: `m`, digits, letters `a` to
82/// `h`, hyphens, digits and letters again.
83fn measure_tag_end(line: &str) -> Option<usize> {
84    let bytes = line.as_bytes();
85    if bytes.first() != Some(&b'm') {
86        return None;
87    }
88    let mut end = 1;
89    let run = |from: usize, test: fn(&u8) -> bool| -> usize {
90        from + bytes[from..].iter().take_while(|byte| test(byte)).count()
91    };
92    let digits = run(end, u8::is_ascii_digit);
93    if digits == end {
94        return None;
95    }
96    end = digits;
97    let letter: fn(&u8) -> bool = |byte| (b'a'..=b'h').contains(byte);
98    end = run(end, letter);
99    end = run(end, |byte| *byte == b'-');
100    end = run(end, u8::is_ascii_digit);
101    end = run(end, letter);
102    Some(end)
103}
104
105/// A measure number and its repeat letter, or two of each for a range:
106/// music21's `_getMeasureNumberData`.
107fn measure_numbers(text: &str) -> Result<(Vec<IntegerType>, Vec<String>)> {
108    let pieces: Vec<&str> = if text.contains('-') {
109        let pieces: Vec<&str> = text.split('-').collect();
110        if pieces.len() != 2 {
111            return Err(roman_text_error(format!(
112                "cannot read the measure numbers {text:?}"
113            )));
114        }
115        pieces
116    } else {
117        vec![text]
118    };
119    let mut numbers = Vec::new();
120    let mut letters = Vec::new();
121    for piece in pieces {
122        let digits: String = piece.chars().filter(char::is_ascii_digit).collect();
123        let other: String = piece.chars().filter(|c| !c.is_ascii_digit()).collect();
124        numbers.push(digits.parse::<IntegerType>().map_err(|_| {
125            roman_text_error(format!("cannot read the measure number in {piece:?}"))
126        })?);
127        letters.push(other.replace('m', ""));
128    }
129    Ok((numbers, letters))
130}
131
132/// Whether a word starts with a key written as music21's patterns have it:
133/// note letters, flats and sharps, then the ending asked for.
134fn starts_with_key(word: &str, ending: &str) -> bool {
135    let letters = word
136        .chars()
137        .take_while(|c| matches!(c, 'A'..='G' | 'a'..='g'))
138        .count();
139    if letters == 0 {
140        return false;
141    }
142    let rest = &word[letters..];
143    let signs = rest.chars().take_while(|c| matches!(c, 'b' | '#')).count();
144    rest[signs..].starts_with(ending)
145}
146
147/// music21's `tokenizeAtoms`.
148fn atoms_of(data: &str) -> Vec<Atom> {
149    let mut atoms = Vec::new();
150    for word in data.split(' ') {
151        let word = word.trim();
152        let chars: Vec<char> = word.chars().collect();
153        if word.is_empty() {
154            continue;
155        }
156        if word == "=" {
157            break;
158        }
159        let no_chord = word.starts_with("NC")
160            || word.starts_with("nc")
161            || (chars.len() >= 4 && chars[0] == 'N' && chars[2] == 'C');
162        let atom = if matches!(word, "||" | "(" | ")") {
163            Atom::Other
164        } else if chars[0] == 'b' && chars.get(1).is_some_and(|c| matches!(c, '1'..='9' | '.')) {
165            Atom::Beat(word.to_string())
166        } else if word
167            .strip_prefix("?(")
168            .or_else(|| word.strip_prefix("?)"))
169            .is_some_and(|rest| starts_with_key(rest, ""))
170            && (word.starts_with("?)") || starts_with_key(&word[2..], ":"))
171        {
172            Atom::Other
173        } else if starts_with_key(word, ";:") {
174            Atom::Key(word.to_string())
175        } else if starts_with_key(word, ":") {
176            Atom::AnalyticKey(word.to_string())
177        } else if word.strip_prefix("KS").is_some_and(|rest| {
178            rest.strip_prefix('-')
179                .unwrap_or(rest)
180                .starts_with(|c: char| ('0'..='7').contains(&c))
181        }) {
182            Atom::KeySignature(word.to_string())
183        } else if word.starts_with("||:") {
184            Atom::RepeatStart
185        } else if word.starts_with(":||") {
186            Atom::RepeatStop
187        } else if no_chord {
188            Atom::NoChord
189        } else {
190            Atom::Chord(word.to_string())
191        };
192        atoms.push(atom);
193    }
194    atoms
195}
196
197fn tagged(line: &str) -> Token {
198    match line.split_once(':') {
199        Some((tag, data)) => Token::Tagged {
200            tag: tag.trim().to_string(),
201            data: data.trim().to_string(),
202        },
203        None => Token::Tagged {
204            tag: String::new(),
205            data: line.to_string(),
206        },
207    }
208}
209
210/// music21's `RTHandler.tokenize`: the header's lines, then the body's.
211fn tokenize(text: &str) -> Result<Vec<Token>> {
212    let lines: Vec<&str> = text.split('\n').collect();
213    let body = lines
214        .iter()
215        .position(|line| measure_tag_end(line.trim()).is_some())
216        .ok_or_else(|| {
217            roman_text_error("Cannot find the first measure definition in this file.")
218        })?;
219    let mut tokens = Vec::new();
220    for line in &lines[..body] {
221        let line = line.trim();
222        if !line.is_empty() {
223            tokens.push(tagged(line));
224        }
225    }
226    for (index, line) in lines.iter().enumerate().skip(body) {
227        let line = line.trim();
228        if line.is_empty() {
229            continue;
230        }
231        let Some(end) = measure_tag_end(line) else {
232            tokens.push(tagged(line));
233            continue;
234        };
235        let (numbers, letters) = measure_numbers(line[..end].trim())
236            .map_err(|error| error.placed(|| Position::line(index + 1)))?;
237        let raw = line[end..].trim();
238        // `var1` and `varA` mark a variant reading, which is passed over.
239        let variant = raw.strip_prefix("var").is_some_and(|rest| {
240            rest.starts_with(|c: char| c.is_ascii_digit() || c.is_ascii_uppercase())
241        });
242        tokens.push(Token::Measure {
243            numbers,
244            letters,
245            variant,
246            data: raw.to_string(),
247            atoms: atoms_of(raw),
248        });
249    }
250    Ok(tokens)
251}
252
253/// The key a word names, and the words it puts in front of the next chord:
254/// music21's `_getKeyAndPrefix`.
255fn key_and_prefix(word: &str) -> Result<(Key, String)> {
256    let name = convert_key_string_to_music21_key_string(word.trim_end_matches([';', ':']));
257    let key = Key::from_tonic(&name)?;
258    // The lyric is music21's `tonicPitchNameWithCase`: the text a score
259    // carries is music21's, not a name of the crate's.
260    let named = music21_key_name(&key);
261    Ok((key, format!("{named}: ")))
262}
263
264/// A key as music21 names it: its `tonicPitchNameWithCase`, which spells a
265/// flat `-` where the crate spells one `b`.
266pub(super) fn music21_key_name(key: &Key) -> String {
267    let mut name = key.tonic_pitch_name_with_case();
268    // The step itself may be a `B`, so only what follows it is a flat.
269    let accidental = name.split_off(1);
270    name.push_str(&accidental.replace('b', "-"));
271    name
272}
273
274/// music21's `getBeatFloatOrFrac`: `b2.5` is beat two and a half, `b1.66`
275/// beat one and two thirds, and `b1.66.5` half a third further on.
276fn beat_of(word: &str) -> Result<FloatType> {
277    let text = word.replace('b', "");
278    let parts: Vec<&str> = text.split('.').collect();
279    if parts.len() > 4 {
280        return Err(roman_text_error(format!(
281            "cannot handle specification: {word}"
282        )));
283    }
284    let read = |text: &str| -> Result<FloatType> {
285        text.parse::<FloatType>()
286            .map_err(|_| roman_text_error(format!("cannot read the beat {word:?}")))
287    };
288    let main = read(parts[0])?;
289    // A third or a sixth written to two places is the fraction itself.
290    let mut fraction = 0.0;
291    let mut denominator = 1.0;
292    if parts.len() > 1 {
293        fraction = read(&format!(".{}", parts[1]))?;
294        for (numerator, over) in [(1.0, 3.0), (2.0, 3.0), (1.0, 6.0), (5.0, 6.0)] {
295            if (fraction - numerator / over).abs() <= 1e-2 {
296                fraction = numerator / over;
297            }
298        }
299        denominator = crate::duration::limited_fraction(fraction, 1_000_000)
300            .map_or(1.0, |(_, denominator)| denominator as FloatType);
301    }
302    let further = if parts.len() > 2 {
303        op_frac(1.0 / (denominator / read(&format!(".{}", parts[2]))?))
304    } else {
305        0.0
306    };
307    Ok(op_frac(main + fraction + further))
308}
309
310/// What a sounding element is.
311// Small and short-lived: boxing the larger variant would buy nothing.
312#[allow(clippy::large_enum_variant)]
313#[derive(Clone, Debug)]
314enum Sound {
315    Numeral {
316        figure: String,
317        key: Key,
318        sixth: Minor67Default,
319        seventh: Minor67Default,
320        follows_key_change: bool,
321        /// The key of the chord this one pivots to.
322        pivot: Option<Key>,
323    },
324    Rest,
325}
326
327/// A chord or a rest of the analysis, kept apart from the measures so that
328/// a later line can lengthen or tie one in an earlier measure.
329#[derive(Clone, Debug)]
330struct Sounding {
331    sound: Sound,
332    quarter_length: FloatType,
333    /// The verses, a line each; none where there are no lyrics at all.
334    lyric: Option<String>,
335    tie: Option<TieType>,
336}
337
338impl Sounding {
339    fn is_numeral(&self) -> bool {
340        matches!(self.sound, Sound::Numeral { .. })
341    }
342
343    fn element(&self) -> Result<StreamElement> {
344        let duration = Duration::new(self.quarter_length)?;
345        match &self.sound {
346            Sound::Rest => {
347                let mut rest = Rest::new(duration);
348                if let Some(lyric) = &self.lyric {
349                    rest.set_lyric(Some(lyric));
350                }
351                rest.set_tie(self.tie.map(Tie::new));
352                Ok(rest.into())
353            }
354            Sound::Numeral {
355                figure,
356                key,
357                sixth,
358                seventh,
359                ..
360            } => {
361                let numeral =
362                    RomanNumeral::with_minor_defaults(figure, key.clone(), *sixth, *seventh)?;
363                let mut chord: Chord = numeral
364                    .to_chord()?
365                    .with_duration(duration)
366                    .with_numeral(numeral);
367                if let (Some(lyric), Some(singer)) = (&self.lyric, chord.notes_mut().first_mut()) {
368                    singer.set_lyric(Some(lyric))?;
369                }
370                chord.set_tie(self.tie.map(Tie::new));
371                Ok(chord.into())
372            }
373        }
374    }
375}
376
377/// What a measure holds at an offset.
378// Small and short-lived: boxing the larger variant would buy nothing.
379#[allow(clippy::large_enum_variant)]
380#[derive(Clone, Debug)]
381enum Held {
382    /// A chord or rest, by its place among the soundings.
383    Sounding(usize),
384    Element(StreamElement),
385}
386
387#[derive(Clone, Debug, Default)]
388struct Measure {
389    number: IntegerType,
390    suffix: Option<String>,
391    items: Vec<(FloatType, Held)>,
392    left: Option<Barline>,
393    right: Option<Barline>,
394    /// What a pickup leaves out before its first chord, and the last
395    /// measure after its last.
396    padding_left: FloatType,
397    padding_right: FloatType,
398}
399
400/// music21's `PartTranslator`.
401struct Translator {
402    metadata: Metadata,
403    measures: Vec<Measure>,
404    soundings: Vec<Sounding>,
405    meter: TimeSignature,
406    meter_at_last_chord: TimeSignature,
407    meter_set: bool,
408    last_measure_number: IntegerType,
409    last_letters: Vec<String>,
410    previous: Option<usize>,
411    signature: Option<KeySignature>,
412    signature_placed: bool,
413    found_signature: bool,
414    key: Key,
415    prefix: String,
416    sixth: Minor67Default,
417    seventh: Minor67Default,
418    /// The measures under each ending, by the number music21 counts them
419    /// under, in the order they were met.
420    endings: Vec<(u32, Vec<(IntegerType, usize)>)>,
421    // What is true while one measure is read.
422    previous_in_measure: Option<usize>,
423    pivot_possible: bool,
424    key_change: bool,
425    offset: FloatType,
426}
427
428impl Translator {
429    fn new() -> Result<Self> {
430        Ok(Self {
431            metadata: Metadata::new(),
432            measures: Vec::new(),
433            soundings: Vec::new(),
434            meter: TimeSignature::new(4, 4)?,
435            meter_at_last_chord: TimeSignature::new(4, 4)?,
436            meter_set: false,
437            last_measure_number: 0,
438            last_letters: Vec::new(),
439            previous: None,
440            signature: None,
441            signature_placed: true,
442            found_signature: false,
443            key: Key::from_tonic("C")?,
444            prefix: String::new(),
445            sixth: Minor67Default::Cautionary,
446            seventh: Minor67Default::Cautionary,
447            endings: Vec::new(),
448            previous_in_measure: None,
449            pivot_possible: false,
450            key_change: false,
451            offset: 0.0,
452        })
453    }
454
455    fn add(&mut self, sounding: Sounding) -> usize {
456        self.soundings.push(sounding);
457        self.soundings.len() - 1
458    }
459
460    /// Ties the last chord on to a copy of it, which is handed back.
461    fn continued(&mut self, quarter_length: FloatType) -> Option<usize> {
462        let previous = self.previous?;
463        let mut copy = self.soundings[previous].clone();
464        copy.lyric = Some(String::new());
465        copy.quarter_length = quarter_length;
466        copy.tie = Some(TieType::Stop);
467        let held = &mut self.soundings[previous];
468        held.tie = Some(if held.tie.is_none() {
469            TieType::Start
470        } else {
471            TieType::Continue
472        });
473        let made = self.add(copy);
474        self.previous = Some(made);
475        Some(made)
476    }
477
478    /// music21's `appendMeasureToRepeatEndingsDict`.
479    fn note_ending(
480        &mut self,
481        letters: &[String],
482        measure: &mut Measure,
483        place: usize,
484    ) -> Result<()> {
485        if letters.is_empty() {
486            return Ok(());
487        }
488        measure.suffix = Some(letters[0].clone()).filter(|letter| !letter.is_empty());
489        for letter in letters {
490            if letter.is_empty() {
491                continue;
492            }
493            let number = match letter.as_str() {
494                "a" => 1,
495                "b" => 2,
496                "c" => 3,
497                "d" => 4,
498                "e" => 5,
499                "f" => 6,
500                "g" => 7,
501                "h" => 8,
502                _ => {
503                    return Err(roman_text_error(format!(
504                        "Improper repeat letter: {letter}"
505                    )));
506                }
507            };
508            let entry = (measure.number, place);
509            match self.endings.iter_mut().find(|(held, _)| *held == number) {
510                Some((_, measures)) => measures.push(entry),
511                None => self.endings.push((number, vec![entry])),
512            }
513        }
514        Ok(())
515    }
516
517    /// music21's `fillMeasureFromPreviousRn`.
518    fn fill_from_previous(&mut self, measure: &mut Measure) {
519        let length = self.meter_at_last_chord.bar_quarter_length();
520        if let Some(made) = self.continued(length) {
521            let at = measure
522                .items
523                .iter()
524                .map(|(offset, held)| match held {
525                    Held::Sounding(index) => offset + self.soundings[*index].quarter_length,
526                    Held::Element(_) => *offset,
527                })
528                .fold(0.0, FloatType::max);
529            measure.items.push((at, Held::Sounding(made)));
530        }
531    }
532
533    fn measure_length(&self, measure: &Measure) -> FloatType {
534        measure
535            .items
536            .iter()
537            .map(|(offset, held)| match held {
538                Held::Sounding(index) => offset + self.soundings[*index].quarter_length,
539                Held::Element(_) => *offset,
540            })
541            .fold(0.0, FloatType::max)
542    }
543
544    /// A copy of a measure already read, its chords read again in the key
545    /// in force: music21's `_copySingleMeasure`.
546    fn copied(&mut self, source: usize, number: IntegerType) -> Measure {
547        let mut measure = self.measures[source].clone();
548        measure.number = number;
549        for (_, held) in &mut measure.items {
550            let Held::Sounding(index) = held else {
551                continue;
552            };
553            let mut copy = self.soundings[*index].clone();
554            if let Sound::Numeral {
555                figure,
556                key,
557                sixth,
558                seventh,
559                follows_key_change,
560                pivot,
561            } = &mut copy.sound
562            {
563                if *follows_key_change {
564                    self.key = key.clone();
565                } else if let Some(pivot) = pivot {
566                    self.key = pivot.clone();
567                } else if key.tonic().name() != self.key.tonic().name()
568                    || key.mode() != self.key.mode()
569                {
570                    // Read in another key the chord is built afresh, and
571                    // the ties its notes carried are gone.
572                    *key = self.key.clone();
573                    copy.tie = None;
574                }
575                // A chord of another chord is read afresh, with nothing of
576                // what the first reading was told.
577                if figure.contains('/')
578                    && RomanNumeral::new(figure.as_str(), key.clone())
579                        .is_ok_and(|numeral| numeral.secondary().is_some())
580                {
581                    *key = self.key.clone();
582                    *sixth = Minor67Default::default();
583                    *seventh = Minor67Default::default();
584                    *follows_key_change = false;
585                    *pivot = None;
586                    copy.tie = None;
587                }
588            }
589            self.soundings.push(copy);
590            *index = self.soundings.len() - 1;
591        }
592        measure
593    }
594
595    fn last_numeral(&self, measure: &Measure) -> Option<usize> {
596        let mut items: Vec<&(FloatType, Held)> = measure.items.iter().collect();
597        items.sort_by(|left, right| left.0.total_cmp(&right.0));
598        items.into_iter().rev().find_map(|(_, held)| match held {
599            Held::Sounding(index) if self.soundings[*index].is_numeral() => Some(*index),
600            _ => None,
601        })
602    }
603
604    /// music21's `translateMeasureLineToken`.
605    fn measure(
606        &mut self,
607        numbers: &[IntegerType],
608        letters: &[String],
609        variant: bool,
610        data: &str,
611        atoms: &[Atom],
612    ) -> Result<()> {
613        if variant {
614            return Ok(());
615        }
616        let is_copy = data.starts_with('=');
617        let next = i64::from(self.last_measure_number) + 1;
618        if i64::from(numbers[0]) > next && self.previous.is_some() {
619            let left_out = i64::from(numbers[0]) - next;
620            if left_out + self.measures.len() as i64 > MAX_MEASURES as i64 {
621                return Err(roman_text_error(format!(
622                    "measure {} would make more than {MAX_MEASURES} measures",
623                    numbers[0]
624                ))
625                .of_kind(FormatErrorKind::TooLarge)
626                .placed(|| Position::measure(numbers[0].to_string())));
627            }
628            // Measures the file leaves out hold the last chord still.
629            for number in self.last_measure_number + 1..numbers[0] {
630                let mut fill = Measure {
631                    number,
632                    ..Measure::default()
633                };
634                self.fill_from_previous(&mut fill);
635                let letters = self.last_letters.clone();
636                let place = self.measures.len();
637                self.note_ending(&letters, &mut fill, place)?;
638                fill.number = number;
639                self.measures.push(fill);
640            }
641            self.last_measure_number = numbers[0] - 1;
642            self.last_letters = letters.to_vec();
643        }
644
645        if numbers.len() == 1 && is_copy {
646            let (targets, _) = measure_numbers(data.replace('=', "").trim())?;
647            if targets.len() > 1 {
648                return Err(roman_text_error(
649                    "a single measure cannot define a copy operation for multiple measures",
650                ));
651            }
652            let source = self
653                .measures
654                .iter()
655                .position(|measure| measure.number == targets[0])
656                .ok_or_else(|| {
657                    roman_text_error(format!(
658                        "Could not find measure {} to copy from",
659                        targets[0]
660                    ))
661                })?;
662            let measure = self.copied(source, numbers[0]);
663            if let Some(last) = self.last_numeral(&measure) {
664                self.previous = Some(last);
665            }
666            self.last_measure_number = measure.number;
667            self.last_letters = letters.to_vec();
668            self.measures.push(measure);
669        } else if numbers.len() > 1 {
670            let (targets, _) = measure_numbers(data.replace('=', "").trim())?;
671            if targets.len() == 1 {
672                return Err(roman_text_error(
673                    "a multiple measure range cannot copy a single measure",
674                ));
675            }
676            let (start, end) = (targets[0], targets[1]);
677            if numbers[1] - numbers[0] != end - start {
678                return Err(roman_text_error(
679                    "both the source and destination sections need to have the same number of \
680                     measures",
681                ));
682            }
683            if numbers[0] < end {
684                return Err(roman_text_error(
685                    "the source section cannot overlap with the destination section",
686                ));
687            }
688            let mut made: Vec<Measure> = Vec::new();
689            for source in 0..self.measures.len() {
690                let number = self.measures[source].number;
691                if (start..=end).contains(&number) {
692                    made.push(self.copied(source, numbers[0] + number - start));
693                }
694                if number == end {
695                    break;
696                }
697            }
698            let Some(last) = made.last() else {
699                return Err(roman_text_error(format!(
700                    "Could not find measures {start}-{end} to copy from"
701                )));
702            };
703            if let Some(last) = self.last_numeral(last) {
704                self.previous = Some(last);
705            }
706            self.last_measure_number = last.number;
707            self.last_letters = letters.to_vec();
708            self.measures.extend(made);
709        } else {
710            let mut measure = self.single(numbers[0], letters, atoms)?;
711            if self.measure_length(&measure) == 0.0 {
712                self.fill_from_previous(&mut measure);
713            }
714            self.measures.push(measure);
715        }
716        Ok(())
717    }
718
719    /// music21's `translateSingleMeasure`.
720    fn single(
721        &mut self,
722        number: IntegerType,
723        letters: &[String],
724        atoms: &[Atom],
725    ) -> Result<Measure> {
726        let mut measure = Measure {
727            number,
728            ..Measure::default()
729        };
730        let place = self.measures.len();
731        self.note_ending(letters, &mut measure, place)?;
732        self.last_measure_number = number;
733        self.last_letters = letters.to_vec();
734        if !self.meter_set {
735            measure
736                .items
737                .push((0.0, Held::Element(self.meter.clone().into())));
738            self.meter_set = true;
739        }
740        if !self.signature_placed
741            && let Some(signature) = &self.signature
742        {
743            measure
744                .items
745                .push((0.0, Held::Element(signature.clone().into())));
746            self.signature_placed = true;
747        }
748        self.offset = 0.0;
749        self.previous_in_measure = None;
750        self.pivot_possible = false;
751        self.key_change = false;
752        for (index, atom) in atoms.iter().enumerate() {
753            self.atom(atom, &mut measure, index + 1 == atoms.len())?;
754        }
755        // The last chord read, in whatever measure it stands, lasts what is
756        // left of this bar.
757        if let Some(previous) = self.previous {
758            self.soundings[previous].quarter_length = self.meter.bar_quarter_length() - self.offset;
759        }
760        Ok(measure)
761    }
762
763    /// Gives the chord before this one in the measure the length up to
764    /// here.
765    fn end_previous(&mut self, measure: &Measure) -> Result<()> {
766        let Some(previous) = self.previous_in_measure else {
767            return Ok(());
768        };
769        let start = measure
770            .items
771            .iter()
772            .find_map(|(offset, held)| match held {
773                Held::Sounding(index) if *index == previous => Some(*offset),
774                _ => None,
775            })
776            .unwrap_or(0.0);
777        let length = self.offset - start;
778        if length <= 0.0 {
779            return Err(roman_text_error("too many notes in this measure"));
780        }
781        self.soundings[previous].quarter_length = length;
782        Ok(())
783    }
784
785    /// music21's `translateSingleMeasureAtom`.
786    fn atom(&mut self, atom: &Atom, measure: &mut Measure, is_last: bool) -> Result<()> {
787        let at_start = if measure.number <= 1 {
788            0.0
789        } else {
790            self.offset
791        };
792        match atom {
793            Atom::Key(word) => {
794                self.set_key(word)?;
795                measure.items.push((
796                    at_start,
797                    Held::Element(StreamElement::Key(self.key.clone())),
798                ));
799                self.found_signature = true;
800            }
801            Atom::AnalyticKey(word) if !self.found_signature => {
802                self.set_key(word)?;
803                measure.items.push((
804                    at_start,
805                    Held::Element(StreamElement::Key(self.key.clone())),
806                ));
807                self.found_signature = true;
808            }
809            Atom::AnalyticKey(word) => self.set_key(word)?,
810            Atom::KeySignature(word) => {
811                let sharps = word[2..].parse::<IntegerType>().map_err(|_| {
812                    roman_text_error(format!("cannot get a key signature from {word}"))
813                })?;
814                measure
815                    .items
816                    .push((at_start, Held::Element(KeySignature::new(sharps).into())));
817                self.found_signature = true;
818            }
819            Atom::Beat(word) => {
820                let offset = self.meter.offset_from_beat(beat_of(word)?).unwrap_or(0.0);
821                // A measure that opens after its first beat holds the last
822                // chord until then.
823                if self.previous_in_measure.is_none()
824                    && offset > 0.0
825                    && let Some(made) = self.continued(offset)
826                {
827                    self.previous_in_measure = Some(made);
828                    measure.items.push((0.0, Held::Sounding(made)));
829                }
830                self.pivot_possible = false;
831                self.offset = offset;
832            }
833            Atom::NoChord => {
834                self.meter_at_last_chord = self.meter.clone();
835                measure.items.push((
836                    self.offset,
837                    Held::Element(ChordSymbol::no_chord(None).into()),
838                ));
839                if !self.pivot_possible {
840                    self.end_previous(measure)?;
841                    self.prefix.clear();
842                    let made = self.add(Sounding {
843                        sound: Sound::Rest,
844                        quarter_length: 1.0,
845                        lyric: None,
846                        tie: None,
847                    });
848                    measure.items.push((self.offset, Held::Sounding(made)));
849                    self.previous_in_measure = Some(made);
850                    self.previous = Some(made);
851                }
852            }
853            Atom::Chord(figure) => {
854                self.meter_at_last_chord = self.meter.clone();
855                // Read now, so that a figure that cannot be read is refused
856                // where it stands.
857                let _ = RomanNumeral::with_minor_defaults(
858                    figure.as_str(),
859                    self.key.clone(),
860                    self.sixth,
861                    self.seventh,
862                )?;
863                let follows_key_change = std::mem::take(&mut self.key_change);
864                if self.pivot_possible {
865                    // Two chords with no beat between are one chord read in
866                    // two keys.
867                    if let Some(previous) = self.previous_in_measure {
868                        let prefix = std::mem::take(&mut self.prefix);
869                        let held = &mut self.soundings[previous];
870                        held.lyric = Some(format!(
871                            "{}//{prefix}{figure}",
872                            held.lyric.clone().unwrap_or_default()
873                        ));
874                        if let Sound::Numeral { pivot, .. } = &mut held.sound {
875                            *pivot = Some(self.key.clone());
876                        }
877                    }
878                    self.pivot_possible = false;
879                } else {
880                    self.end_previous(measure)?;
881                    let prefix = std::mem::take(&mut self.prefix);
882                    let made = self.add(Sounding {
883                        sound: Sound::Numeral {
884                            figure: figure.clone(),
885                            key: self.key.clone(),
886                            sixth: self.sixth,
887                            seventh: self.seventh,
888                            follows_key_change,
889                            pivot: None,
890                        },
891                        quarter_length: 1.0,
892                        lyric: Some(format!("{prefix}{figure}")),
893                        tie: None,
894                    });
895                    measure.items.push((self.offset, Held::Sounding(made)));
896                    self.previous_in_measure = Some(made);
897                    self.previous = Some(made);
898                    self.pivot_possible = true;
899                }
900            }
901            Atom::RepeatStart | Atom::RepeatStop => {
902                let stop = *atom == Atom::RepeatStop;
903                if (stop && is_last)
904                    || (stop
905                        && self.offset != 0.0
906                        && self.meter.bar_quarter_length() == self.offset)
907                {
908                    measure.right = Some(Barline::repeat(RepeatDirection::End, None));
909                } else if self.offset == 0.0 && !stop {
910                    measure.left = Some(Barline::repeat(RepeatDirection::Start, None));
911                }
912            }
913            Atom::Other => {}
914        }
915        Ok(())
916    }
917
918    fn set_key(&mut self, word: &str) -> Result<()> {
919        let (key, prefix) = key_and_prefix(word)?;
920        self.key = key;
921        self.prefix.push_str(&prefix);
922        self.key_change = true;
923        Ok(())
924    }
925
926    /// music21's `translateOneLineToken`.
927    fn token(&mut self, token: &Token) -> Result<()> {
928        let (tag, data) = match token {
929            Token::Measure {
930                numbers,
931                letters,
932                variant,
933                data,
934                atoms,
935            } => return self.measure(numbers, letters, *variant, data, atoms),
936            Token::Tagged { tag, data } => (tag.to_lowercase(), data),
937        };
938        let text = |metadata: &mut Metadata, name: &str| {
939            metadata.add(name, MetadataValue::new(data.clone()));
940        };
941        match tag.as_str() {
942            "title" => text(&mut self.metadata, "title"),
943            "work" | "madrigal" | "piece" => text(&mut self.metadata, "alternativeTitle"),
944            "composer" => self.metadata.add_contributor("composer", data.clone()),
945            "movement" => text(&mut self.metadata, "movementNumber"),
946            "analyst" => self.metadata.add_contributor("analyst", data.clone()),
947            "proofreader" | "proof reader" => {
948                self.metadata.add_contributor("proofreader", data.clone());
949            }
950            "timesignature" | "time signature" => {
951                let meter = match data.as_str() {
952                    "C" | "c" => Some(TimeSignature::common()),
953                    "C|" | "Cut" | "cut" => Some(TimeSignature::cut()),
954                    other => TimeSignature::from_ratio_string(other).ok(),
955                };
956                // A meter that cannot be read leaves the one in force.
957                if let Some(meter) = meter {
958                    self.meter = meter;
959                    self.meter_set = false;
960                }
961            }
962            "keysignature" | "key signature" => {
963                let sharps = match data.as_str() {
964                    "" => 0,
965                    "Bb" => -1,
966                    other => other.parse::<IntegerType>().map_err(|_| {
967                        roman_text_error(format!("Cannot parse key signature: {other:?}"))
968                    })?,
969                };
970                self.signature = Some(KeySignature::new(sharps));
971                self.signature_placed = false;
972                self.found_signature = true;
973            }
974            "sixthminor" | "sixth minor" | "seventhminor" | "seventh minor" => {
975                let sixth = tag.starts_with("sixth");
976                let reading = match data.to_lowercase().as_str() {
977                    "flat" => Minor67Default::Flat,
978                    "sharp" => Minor67Default::Sharp,
979                    "quality" => Minor67Default::Quality,
980                    "courtesy" | "cautionary" => Minor67Default::Cautionary,
981                    "harmonic" if sixth => Minor67Default::Flat,
982                    "harmonic" => Minor67Default::Sharp,
983                    other => {
984                        return Err(roman_text_error(format!(
985                            "Cannot parse setting vi or vii parsing: {other:?}"
986                        )));
987                    }
988                };
989                if sixth {
990                    self.sixth = reading;
991                } else {
992                    self.seventh = reading;
993                }
994            }
995            _ => {}
996        }
997        Ok(())
998    }
999
1000    /// music21's `fixPickupMeasure`: a measure numbered nought that opens
1001    /// after its first beat starts where its first chord does, and the last
1002    /// measure is cut short by as much.
1003    fn fix_pickup(&mut self) {
1004        let Some(first) = self.measures.iter().position(|measure| measure.number == 0) else {
1005            return;
1006        };
1007        let is_chord = |held: &Held, soundings: &[Sounding]| match held {
1008            Held::Sounding(index) => soundings[*index].is_numeral(),
1009            Held::Element(element) => matches!(element, StreamElement::ChordSymbol(_)),
1010        };
1011        let mut chords: Vec<FloatType> = self.measures[first]
1012            .items
1013            .iter()
1014            .filter(|(_, held)| is_chord(held, &self.soundings))
1015            .map(|(offset, _)| *offset)
1016            .collect();
1017        chords.sort_by(FloatType::total_cmp);
1018        let Some(padding) = chords.first().copied().filter(|offset| *offset != 0.0) else {
1019            return;
1020        };
1021        for (offset, _) in &mut self.measures[first].items {
1022            if *offset >= padding {
1023                *offset -= padding;
1024            }
1025        }
1026        self.measures[first].padding_left = padding;
1027        let last = self.measures.len() - 1;
1028        if last == first {
1029            return;
1030        }
1031        let mut sorted: Vec<&(FloatType, Held)> = self.measures[last].items.iter().collect();
1032        sorted.sort_by(|left, right| left.0.total_cmp(&right.0));
1033        let closing = sorted.into_iter().rev().find_map(|(_, held)| match held {
1034            Held::Sounding(index) if self.soundings[*index].is_numeral() => Some(Some(*index)),
1035            Held::Element(StreamElement::ChordSymbol(_)) => Some(None),
1036            _ => None,
1037        });
1038        if let Some(Some(index)) = closing
1039            && self.soundings[index].quarter_length > padding
1040        {
1041            let length = self.measure_length(&self.measures[last]);
1042            self.soundings[index].quarter_length -= length - padding;
1043            self.measures[last].padding_right = length - padding;
1044        }
1045    }
1046
1047    /// The part, once every line is read: music21's `translateTokens`.
1048    fn part(mut self) -> Result<(Stream, Metadata)> {
1049        self.fix_pickup();
1050
1051        // music21's `_consolidateRepeatEndings`: each run of measures in a
1052        // row under one ending is a bracket.
1053        let mut brackets: Vec<(Vec<usize>, u32)> = Vec::new();
1054        for (number, measures) in &self.endings {
1055            let mut run: Vec<usize> = Vec::new();
1056            let mut last: Option<IntegerType> = None;
1057            for (measure_number, place) in measures {
1058                if last.is_some_and(|last| *measure_number > last + 1) {
1059                    brackets.push((std::mem::take(&mut run), *number));
1060                }
1061                run.push(*place);
1062                last = Some(*measure_number);
1063            }
1064            if !run.is_empty() {
1065                brackets.push((run, *number));
1066            }
1067        }
1068        let mut endings: Vec<Option<Ending>> = vec![None; self.measures.len()];
1069        for (places, number) in &brackets {
1070            for (index, place) in places.iter().enumerate() {
1071                if let Some(slot) = endings.get_mut(*place) {
1072                    *slot = Some(Ending::new(
1073                        vec![*number],
1074                        index == 0,
1075                        index + 1 == places.len(),
1076                    ));
1077                }
1078            }
1079            if *number == 1
1080                && let Some(measure) = places
1081                    .last()
1082                    .and_then(|place| self.measures.get_mut(*place))
1083                && measure.right.is_none()
1084            {
1085                measure.right = Some(Barline::repeat(RepeatDirection::End, None));
1086            }
1087        }
1088
1089        let mut events = Vec::new();
1090        let mut offset = 0.0;
1091        for (measure, ending) in self.measures.iter().zip(endings) {
1092            let mut held = Vec::new();
1093            for (at, item) in &measure.items {
1094                let element = match item {
1095                    Held::Sounding(index) => self.soundings[*index].element()?,
1096                    Held::Element(element) => element.clone(),
1097                };
1098                held.push(StreamEvent::new(*at, element));
1099            }
1100            let mut stream =
1101                Stream::with_kind(StreamKind::Measure).with_events(sorted_events(held));
1102            stream.set_number(measure.number);
1103            stream.set_number_suffix(measure.suffix.clone());
1104            stream.set_padding_left(measure.padding_left);
1105            stream.set_padding_right(measure.padding_right);
1106            stream.set_left_barline(measure.left.clone());
1107            stream.set_right_barline(measure.right.clone());
1108            stream.set_ending(ending);
1109            let length = stream.end_offset();
1110            events.push(StreamEvent::new(offset, stream));
1111            offset = op_frac(offset + length);
1112        }
1113        let mut part = Stream::with_kind(StreamKind::Part).with_events(events);
1114        // music21 goes on where a measure cannot be beamed.
1115        let _ = make_beams(&mut part);
1116        make_accidentals(&mut part);
1117        Ok((part, self.metadata))
1118    }
1119}
1120
1121fn score_of(tokens: &[Token]) -> Result<Stream> {
1122    let mut translator = Translator::new()?;
1123    for token in tokens {
1124        // What goes wrong in a measure says which, by its first number.
1125        let measure = match token {
1126            Token::Measure { numbers, .. } => numbers.first().copied(),
1127            _ => None,
1128        };
1129        translator.token(token).map_err(|error| match measure {
1130            Some(number) => error.placed(|| Position::measure(number.to_string())),
1131            None => error,
1132        })?;
1133    }
1134    let (part, metadata) = translator.part()?;
1135    let mut score =
1136        Stream::with_kind(StreamKind::Score).with_events(vec![StreamEvent::new(0.0, part)]);
1137    score.set_metadata(Some(metadata));
1138    Ok(score)
1139}
1140
1141/// Reads a RomanText analysis into a score: music21's
1142/// `converter.parse` of one.
1143///
1144/// The score holds one part of measures, and in each the chords the
1145/// numerals stand for, in the key they were written in, each carrying its
1146/// figure as its lyric -- the key too where it has just changed, as
1147/// `G: V7`. A chord lasts until the next, or to the end of its bar; one
1148/// held into a measure that opens after its first beat is tied there. `NC`
1149/// is a rest under a no-chord symbol. A measure written `m5 = m1` is a copy
1150/// of another, its chords read in the key now in force.
1151///
1152/// A file of several `Movement:` lines comes back as an opus of scores, the
1153/// lines before the first heading each of them.
1154///
1155/// ```
1156/// use music21::romantext::from_roman_text;
1157///
1158/// let score = from_roman_text("Time Signature: 3/4\nm1 G: I b3 V6\nm2 I\n")?;
1159/// let part = score.parts()[0];
1160/// assert_eq!(part.measures().len(), 2);
1161/// let names: Vec<String> = part.measures()[0]
1162///     .pitches()
1163///     .iter()
1164///     .map(|pitch| pitch.name())
1165///     .collect();
1166/// assert_eq!(names, ["G", "B", "D", "F#", "A", "D"]);
1167/// # Ok::<(), music21::Error>(())
1168/// ```
1169pub fn from_roman_text(text: &str) -> Result<Stream> {
1170    let tokens = tokenize(text)?;
1171    let movements = tokens.iter().filter(|token| token.is_movement()).count();
1172    if movements < 2 {
1173        return score_of(&tokens);
1174    }
1175    // Each movement takes the lines before the first heading as its own.
1176    let mut pieces: Vec<Vec<Token>> = Vec::new();
1177    let mut current: Vec<Token> = Vec::new();
1178    for token in tokens {
1179        if token.is_movement() {
1180            pieces.push(std::mem::take(&mut current));
1181        }
1182        current.push(token);
1183    }
1184    if !current.is_empty() {
1185        pieces.push(current);
1186    }
1187    let head = pieces.remove(0);
1188    let mut events = Vec::new();
1189    let mut offset = 0.0;
1190    for piece in pieces {
1191        let tokens: Vec<Token> = head.iter().cloned().chain(piece).collect();
1192        let score = score_of(&tokens)?;
1193        let length = score.end_offset();
1194        events.push(StreamEvent::new(offset, score));
1195        offset = op_frac(offset + length);
1196    }
1197    Ok(Stream::with_kind(StreamKind::Opus).with_events(events))
1198}
1199
1200/// Writes a score's roman numerals as a RomanText analysis, as music21's
1201/// `romanText.writeRoman.RnWriter` writes one, a line after each line.
1202///
1203/// The header names the composer, the title (with the movement's number and
1204/// name), the analyst and the proofreader, as the score's metadata gives
1205/// them. Then measure by measure: a `Time Signature:` line where a meter
1206/// stands, and a line of the measure's numerals -- each chord standing for
1207/// one ([`Chord::numeral`]) at its beat, `b2.5`, beat one left unsaid, the
1208/// key written before a numeral where it is not the key of the one before,
1209/// and a chord tied from the one before passed over -- with `||:` and
1210/// `:||` where a repeat starts and ends. A score is written from its first
1211/// part, an opus score by score.
1212///
1213/// ```
1214/// use music21::romantext::{from_roman_text, to_roman_text};
1215///
1216/// let score = from_roman_text("Time Signature: 3/4\nm1 G: I b3 V6\nm2 I\n")?;
1217/// let written = to_roman_text(&score)?;
1218/// assert!(written.ends_with("Time Signature: 3/4\nm1 G: I b3 V6\nm2 I\n"));
1219/// # Ok::<(), music21::Error>(())
1220/// ```
1221///
1222/// # Errors
1223///
1224/// A numeral standing where a meter has no beat.
1225pub fn to_roman_text(stream: &Stream) -> Result<String> {
1226    let mut lines = Vec::new();
1227    write_lines(stream, &mut lines)?;
1228    Ok(lines.iter().map(|line| format!("{line}\n")).collect())
1229}
1230
1231/// `RnWriter`'s `combinedList` for one stream.
1232fn write_lines(stream: &Stream, lines: &mut Vec<String>) -> Result<()> {
1233    if stream.kind() == StreamKind::Opus {
1234        for event in stream.events() {
1235            if let StreamElement::Stream(score) = event.element() {
1236                write_lines(score, lines)?;
1237                lines.push("\n".to_string());
1238            }
1239        }
1240        return Ok(());
1241    }
1242    let wrapped;
1243    let container: &Stream = match stream.kind() {
1244        StreamKind::Score => stream.parts().first().copied().unwrap_or(stream),
1245        StreamKind::Part | StreamKind::PartStaff => stream,
1246        StreamKind::Measure => {
1247            let mut part = Stream::with_kind(StreamKind::Part);
1248            part.insert(0.0, stream.clone());
1249            wrapped = part;
1250            &wrapped
1251        }
1252        _ => {
1253            // music21 appends everything the stream holds to one measure.
1254            let mut measure = Stream::with_kind(StreamKind::Measure);
1255            for event in stream.events() {
1256                measure.push(event.element().clone());
1257            }
1258            let mut part = Stream::with_kind(StreamKind::Part);
1259            part.insert(0.0, measure);
1260            wrapped = part;
1261            &wrapped
1262        }
1263    };
1264
1265    let mut composer = "Composer unknown".to_string();
1266    let mut title = "Title unknown".to_string();
1267    let mut analyst = String::new();
1268    let mut proofreader = String::new();
1269    if let Some(metadata) = stream.metadata() {
1270        if let Some(prepared) = written_title(metadata) {
1271            title = prepared;
1272        }
1273        if let Some(name) = contributor(metadata, "composer") {
1274            composer = name;
1275        }
1276        if let Some(name) = contributor(metadata, "analyst") {
1277            analyst = name;
1278        }
1279        if let Some(name) = contributor(metadata, "proofreader") {
1280            proofreader = name;
1281        }
1282    }
1283    lines.push(format!("Composer: {composer}"));
1284    lines.push(format!("Title: {title}"));
1285    lines.push(format!("Analyst: {analyst}"));
1286    lines.push(format!("Proofreader: {proofreader}"));
1287    lines.push(String::new());
1288
1289    // A meter standing in the part itself is in force from its start; with
1290    // none anywhere, music21 puts a 4/4 there.
1291    let mut meter = container
1292        .events()
1293        .iter()
1294        .find_map(|event| match event.element() {
1295            StreamElement::TimeSignature(meter) => Some(meter.clone()),
1296            _ => None,
1297        });
1298    let any_meter = container
1299        .leaves()
1300        .iter()
1301        .any(|(_, element)| matches!(element, StreamElement::TimeSignature(_)));
1302    if !any_meter {
1303        meter = Some(TimeSignature::new(4, 4)?);
1304    }
1305
1306    let mut key_string = String::new();
1307    for measure in container.measures() {
1308        let meters: Vec<(FloatType, &TimeSignature)> = measure
1309            .events()
1310            .iter()
1311            .filter_map(|event| match event.element() {
1312                StreamElement::TimeSignature(meter) => Some((event.offset(), meter)),
1313                _ => None,
1314            })
1315            .collect();
1316        if let Some((_, first)) = meters.first() {
1317            lines.push(format!("Time Signature: {}", first.ratio_string()));
1318            if meters.len() > 1 {
1319                let rest: Vec<String> = meters[1..]
1320                    .iter()
1321                    .map(|(_, meter)| format!("'{}'", meter.ratio_string()))
1322                    .collect();
1323                lines.push(format!(
1324                    "Note: further time signature change(s) unprocessed: [{}]",
1325                    rest.join(", ")
1326                ));
1327            }
1328        }
1329        let number = format!(
1330            "{}{}",
1331            measure.number(),
1332            measure.number_suffix().unwrap_or("")
1333        );
1334        let mut line = String::new();
1335        if measure
1336            .left_barline()
1337            .is_some_and(|barline| barline.repeat_direction() == Some(RepeatDirection::Start))
1338        {
1339            push_chord_string(&mut line, &number, 1.0, "||:");
1340        }
1341        let mut last_beat = None;
1342        for event in measure.events() {
1343            let StreamElement::Chord(chord) = event.element() else {
1344                continue;
1345            };
1346            let Some(numeral) = chord.numeral() else {
1347                continue;
1348            };
1349            let at = event.offset();
1350            // The meter in force: the last standing at or before the
1351            // numeral in this measure, else the one carried in.
1352            let in_force = meters
1353                .iter()
1354                .rev()
1355                .find(|(offset, _)| *offset <= at)
1356                .map(|(_, meter)| (*meter).clone())
1357                .or_else(|| meter.clone())
1358                .ok_or_else(|| roman_text_error("a roman numeral with no meter in force"))?;
1359            let beat = in_force.beat_proportion(op_frac(at + measure.padding_left()))?;
1360            last_beat = Some(beat);
1361            if chord
1362                .tie()
1363                .is_some_and(|tie| tie.tie_type() != TieType::Start)
1364            {
1365                continue;
1366            }
1367            let key = numeral.key().tonic_pitch_name_with_case().replace('-', "b");
1368            let written = if key == key_string {
1369                numeral.figure().to_string()
1370            } else {
1371                key_string = key.clone();
1372                format!("{key}: {}", numeral.figure())
1373            };
1374            push_chord_string(&mut line, &number, beat, &written);
1375        }
1376        if measure
1377            .right_barline()
1378            .is_some_and(|barline| barline.repeat_direction() == Some(RepeatDirection::End))
1379        {
1380            push_chord_string(&mut line, &number, last_beat.unwrap_or(1.0), ":||");
1381        }
1382        if !line.is_empty() {
1383            lines.push(line);
1384        }
1385        if let Some((_, last)) = meters.last() {
1386            meter = Some((*last).clone());
1387        }
1388    }
1389    Ok(())
1390}
1391
1392/// `rnString`: a chord string added to a measure's line, its beat said
1393/// where it is not the first.
1394fn push_chord_string(line: &mut String, number: &str, beat: FloatType, chord: &str) {
1395    if line.is_empty() {
1396        line.push('m');
1397        line.push_str(number);
1398    }
1399    let beat = int_beat(beat);
1400    if beat == "1" {
1401        line.push_str(&format!(" {chord}"));
1402    } else {
1403        line.push_str(&format!(" b{beat} {chord}"));
1404    }
1405}
1406
1407/// `intBeat`: a beat as a whole number where it is one, else rounded to two
1408/// places, written as Python writes a float.
1409fn int_beat(beat: FloatType) -> String {
1410    if beat.fract() == 0.0 {
1411        return format!("{}", beat as i64);
1412    }
1413    let rounded = (beat * 100.0).round() / 100.0;
1414    let written = format!("{rounded}");
1415    if written.contains('.') {
1416        written
1417    } else {
1418        format!("{written}.0")
1419    }
1420}
1421
1422/// `prepTitle`: the best title, the movement's number and its name where
1423/// that is not the title.
1424fn written_title(metadata: &Metadata) -> Option<String> {
1425    let mut parts = Vec::new();
1426    if let Some(title) = ["title", "popularTitle", "alternativeTitle", "movementName"]
1427        .iter()
1428        .find_map(|name| metadata.first_text(name))
1429    {
1430        parts.push(title.to_string());
1431    }
1432    if let Some(number) = metadata.first_text("movementNumber") {
1433        parts.push(format!("- No.{number}:"));
1434    }
1435    if let Some(name) = metadata.first_text("movementName")
1436        && metadata.first_text("title") != Some(name)
1437    {
1438        parts.push(name.to_string());
1439    }
1440    (!parts.is_empty()).then(|| parts.join(" "))
1441}
1442
1443/// The first contributor in a role, filed under the role or among the
1444/// other contributors.
1445fn contributor(metadata: &Metadata, role: &str) -> Option<String> {
1446    metadata
1447        .first_text(role)
1448        .or_else(|| {
1449            metadata
1450                .get("otherContributor")
1451                .iter()
1452                .find(|value| value.role() == Some(role))
1453                .map(MetadataValue::text)
1454        })
1455        .filter(|name| !name.is_empty())
1456        .map(str::to_string)
1457}
1458
1459#[cfg(test)]
1460mod tests {
1461    use super::*;
1462
1463    fn lengths(measure: &Stream) -> Vec<(FloatType, FloatType)> {
1464        measure
1465            .events()
1466            .iter()
1467            .filter(|event| event.element().is_note_or_chord())
1468            .map(|event| (event.offset(), event.element().quarter_length()))
1469            .collect()
1470    }
1471
1472    #[test]
1473    fn words_of_a_measure_are_told_apart() {
1474        assert_eq!(
1475            atoms_of("C: I b2.5 V6 || Bb;: KS-2 NC ||: :|| ?(d: ii"),
1476            [
1477                Atom::AnalyticKey("C:".to_string()),
1478                Atom::Chord("I".to_string()),
1479                Atom::Beat("b2.5".to_string()),
1480                Atom::Chord("V6".to_string()),
1481                Atom::Other,
1482                Atom::Key("Bb;:".to_string()),
1483                Atom::KeySignature("KS-2".to_string()),
1484                Atom::NoChord,
1485                Atom::RepeatStart,
1486                Atom::RepeatStop,
1487                Atom::Other,
1488                Atom::Chord("ii".to_string()),
1489            ]
1490        );
1491    }
1492
1493    #[test]
1494    fn a_beat_may_be_a_third_written_to_two_places() {
1495        assert_eq!(beat_of("b2").unwrap(), 2.0);
1496        assert_eq!(beat_of("b2.5").unwrap(), 2.5);
1497        assert!((beat_of("b1.66").unwrap() - 5.0 / 3.0).abs() < 1e-9);
1498        assert!((beat_of("b1.66.5").unwrap() - 11.0 / 6.0).abs() < 1e-9);
1499    }
1500
1501    #[test]
1502    fn a_chord_lasts_until_the_next_or_the_barline() {
1503        let score = from_roman_text("Time Signature: 4/4\nm1 C: I b3 V\nm2 I\n").unwrap();
1504        let part = score.parts()[0];
1505        let measures = part.measures();
1506        assert_eq!(lengths(measures[0]), [(0.0, 2.0), (2.0, 2.0)]);
1507        assert_eq!(lengths(measures[1]), [(0.0, 4.0)]);
1508    }
1509
1510    #[test]
1511    fn a_measure_opening_late_holds_the_last_chord_tied() {
1512        let score = from_roman_text("m1 C: I\nm2 b3 V\n").unwrap();
1513        let part = score.parts()[0];
1514        let second = part.measures()[1];
1515        assert_eq!(lengths(second), [(0.0, 2.0), (2.0, 2.0)]);
1516        let StreamElement::Chord(held) = second.events()[0].element() else {
1517            panic!("the measure opens with the chord held over");
1518        };
1519        assert_eq!(held.tie().map(Tie::tie_type), Some(TieType::Stop));
1520    }
1521
1522    #[test]
1523    fn a_measure_may_copy_another() {
1524        let score = from_roman_text("m1 C: I b3 V\nm2 IV\nm3 = m1\n").unwrap();
1525        let part = score.parts()[0];
1526        let measures = part.measures();
1527        assert_eq!(measures.len(), 3);
1528        assert_eq!(measures[2].number(), 3);
1529        assert_eq!(measures[2].pitches(), measures[0].pitches());
1530    }
1531
1532    #[test]
1533    fn text_with_no_measure_is_refused() {
1534        assert!(from_roman_text("Composer: nobody\n").is_err());
1535    }
1536
1537    #[test]
1538    fn a_measure_after_the_largest_number_is_read() {
1539        assert!(from_roman_text("m2147483647 C: I\nm1 I\n").is_ok());
1540    }
1541
1542    #[test]
1543    fn a_refusal_says_its_measure_or_line() {
1544        let error = from_roman_text("m1 C: I\nm2 V\nm3 KS1x I\n").unwrap_err();
1545        assert_eq!(error.position(), Some(&Position::measure("3")));
1546
1547        let error = from_roman_text("Title: x\nm1 C: I\n\nm99999999999 V\n").unwrap_err();
1548        assert_eq!(
1549            error.to_string(),
1550            "RomanText error: cannot read the measure number in \"m99999999999\""
1551        );
1552        assert_eq!(error.position(), Some(&Position::line(4)));
1553    }
1554
1555    #[test]
1556    fn a_gap_of_more_measures_than_a_stream_takes_is_refused() {
1557        let gap = crate::makenotation::MAX_MEASURES + 2;
1558        let refused = from_roman_text(&format!("m1 C: I\nm{gap} V\n")).unwrap_err();
1559        let said = refused.format_error().unwrap();
1560        assert_eq!(said.kind, FormatErrorKind::TooLarge);
1561        assert_eq!(said.position, Some(Position::measure(gap.to_string())));
1562        assert!(from_roman_text("m1 C: I\nm1000 V\n").is_ok());
1563    }
1564}