Skip to main content

music21_rs/romantext/
clercq_temperley.rs

1//! The de Clercq-Temperley analyses of the rock corpus: music21's
2//! `romanText.clercqTemperley`.
3//!
4//! A song is a handful of rules, each a line of its own. `Vr: I | IV | I |
5//! V |` names the verse's chords a measure at a time, and the last rule,
6//! `S:`, plays the song through by naming the others -- `S: [G] $In $Vr
7//! $Vr $Ch`. A rule may expand another (`$VP*2`), repeat a measure of its
8//! own (`|*4`), say the key or the meter in brackets (`[G]`, `[3/4]`),
9//! repeat the chord before it with a dot, rest a measure with an `R`, and
10//! carry a comment after a `%`. The first line is the title, also written
11//! after a `%`.
12//!
13//! [`from_clercq_temperley`] reads one into a part of
14//! [`RomanNumeral`](crate::roman::RomanNumeral)s, which is music21's
15//! `CTSong.toPart`: a measure for each the rules name, its chords sharing
16//! the bar equally, the figure of each written under it as a lyric and the
17//! name of the rule that brought it under the first chord of its section.
18//! A chord repeated across a barline is tied to the one before it rather
19//! than struck again.
20//!
21//! The format is the rock corpus's, documented at
22//! <https://rockcorpus.midside.com>. The library reads text, not files, so
23//! a `.clt` file is the caller's to read.
24
25use crate::chord::Chord;
26use crate::defaults::{FloatType, IntegerType};
27use crate::duration::Duration;
28use crate::error::{Error, Result};
29use crate::key::{Key, convert_key_string_to_music21_key_string};
30use crate::makenotation::op_frac;
31use crate::metadata::{Metadata, MetadataValue};
32use crate::meter::TimeSignature;
33use crate::notation::Tie;
34use crate::rest::Rest;
35use crate::roman::RomanNumeral;
36use crate::stream::{Stream, StreamElement, StreamKind};
37
38/// What is written under the chords as a song is read: music21's
39/// `labelRomanNumerals` and `labelSubsectionsOnScore`, both on.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct CtLabels {
43    /// Whether each chord carries its figure as a lyric.
44    pub roman_numerals: bool,
45    /// Whether the first chord of a section carries the name of the rule
46    /// that brought it.
47    pub subsections: bool,
48}
49
50impl Default for CtLabels {
51    fn default() -> Self {
52        Self {
53            roman_numerals: true,
54            subsections: true,
55        }
56    }
57}
58
59/// A song read into a part: music21's `CTSong(text).toPart()`.
60///
61/// ```
62/// use music21_rs::romantext::clercq_temperley::from_clercq_temperley;
63///
64/// let song = "% Simple Gifts\nVr: I | I |\nS: [A] $Vr\n";
65/// let part = from_clercq_temperley(song)?;
66/// assert_eq!(part.measures().len(), 2);
67/// # Ok::<(), music21_rs::Error>(())
68/// ```
69///
70/// # Errors
71///
72/// A song with no `S:` rule, a rule naming one the song has not got, a dot
73/// with no chord before it, or a key, a meter or a figure the crate cannot
74/// read.
75pub fn from_clercq_temperley(text: &str) -> Result<Stream> {
76    CtSong::parse(text).to_part()
77}
78
79/// The same, saying what to write under the chords.
80///
81/// # Errors
82///
83/// As [`from_clercq_temperley`].
84pub fn from_clercq_temperley_with(text: &str, labels: CtLabels) -> Result<Stream> {
85    CtSong::parse(text).to_part_with(labels)
86}
87
88/// A song as its text says: music21's `CTSong`.
89#[derive(Clone, Debug, Default, PartialEq, Eq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91pub struct CtSong {
92    text: String,
93    lines: Vec<String>,
94    rules: Vec<CtRule>,
95}
96
97impl CtSong {
98    /// A song read from its text: music21's `parse`, which keeps the lines
99    /// that carry anything, each trimmed.
100    ///
101    /// music21 takes a path as readily as a text, reading the file where
102    /// the string holds no `|` and no `S:`; the library reads no files, so
103    /// this takes the text alone.
104    pub fn parse(text: &str) -> Self {
105        let lines: Vec<String> = text
106            .split('\n')
107            .filter(|line| !line.is_empty())
108            .map(|line| line.trim().to_string())
109            .collect();
110        let rules = lines
111            .iter()
112            .filter(|line| {
113                line.split_whitespace()
114                    .next()
115                    .is_some_and(|word| word.ends_with(':'))
116            })
117            .map(|line| CtRule::new(line))
118            .collect();
119        Self {
120            text: lines.join("\n"),
121            lines,
122            rules,
123        }
124    }
125
126    /// The text as it was read, its blank lines gone.
127    pub fn text(&self) -> &str {
128        &self.text
129    }
130
131    /// The lines the text was read as.
132    pub fn lines(&self) -> &[String] {
133        &self.lines
134    }
135
136    /// The title, which is the first line with its `%` taken off.
137    ///
138    /// music21 takes *every* `%` out of that line rather than the one in
139    /// front of it, so a title with a per cent in it loses the sign.
140    pub fn title(&self) -> String {
141        self.lines
142            .first()
143            .map(|line| line.replace('%', "").trim().to_string())
144            .unwrap_or_default()
145    }
146
147    /// Every comment of the song but the title, each as music21 lists it:
148    /// a comment on a rule's line is that rule's name and the comment, and
149    /// one on a line of its own is the comment alone.
150    pub fn comments(&self) -> Vec<Vec<String>> {
151        let mut comments = Vec::new();
152        for line in self.lines.iter().skip(1) {
153            let Some(at) = line.find('%') else { continue };
154            let said = line[at + 1..].trim().to_string();
155            match line.split_whitespace().next() {
156                Some(word) if word.ends_with(':') => comments.push(vec![word.to_string(), said]),
157                _ => comments.push(vec![said]),
158            }
159        }
160        comments
161    }
162
163    /// The rules of the song, in the order the text names them, the `S:`
164    /// rule among them.
165    pub fn rules(&self) -> &[CtRule] {
166        &self.rules
167    }
168
169    /// The rule of a name, as music21 keys its rules by the left-hand side.
170    pub fn rule(&self, name: &str) -> Option<&CtRule> {
171        self.rules.iter().find(|rule| rule.left_hand_side() == name)
172    }
173
174    /// The meter the song opens in: music21's `homeTimeSig`, which reads
175    /// the first two atoms of the `S:` rule -- four-four where the first
176    /// says no meter and no key, the meter where one says a meter, and
177    /// nothing at all where both say a key.
178    ///
179    /// # Errors
180    ///
181    /// A meter the crate cannot read.
182    pub fn home_time_signature(&self) -> Result<Option<TimeSignature>> {
183        for atom in self.song_head() {
184            if !atom.contains('[') {
185                return TimeSignature::from_ratio_string("4/4").map(Some);
186            }
187            if atom.contains('/') {
188                return TimeSignature::from_ratio_string(bracketed(atom)).map(Some);
189            }
190        }
191        Ok(None)
192    }
193
194    /// The key the song opens in: music21's `homeKey`, read the same way --
195    /// C major where the first atom says neither, the key where one says a
196    /// key, and nothing where both say a meter.
197    ///
198    /// # Errors
199    ///
200    /// A key the crate cannot read.
201    pub fn home_key(&self) -> Result<Option<Key>> {
202        for atom in self.song_head() {
203            if !atom.contains('[') {
204                return Key::from_tonic("C").map(Some);
205            }
206            if !atom.contains('/') {
207                let named = convert_key_string_to_music21_key_string(bracketed(atom));
208                return Key::from_tonic(&named).map(Some);
209            }
210        }
211        Ok(None)
212    }
213
214    /// The first two atoms of the `S:` rule, which is where the song says
215    /// its key and its meter.
216    fn song_head(&self) -> Vec<&str> {
217        self.lines
218            .iter()
219            .find(|line| line.starts_with("S:"))
220            .map(|line| line.split_whitespace().skip(1).take(2).collect())
221            .unwrap_or_default()
222    }
223
224    /// The song as a part: music21's `toPart`, which expands the `S:` rule,
225    /// numbers the measures from one and signs the part with the title.
226    ///
227    /// # Errors
228    ///
229    /// As [`from_clercq_temperley`].
230    pub fn to_part(&self) -> Result<Stream> {
231        self.to_part_with(CtLabels::default())
232    }
233
234    /// The same, saying what to write under the chords.
235    ///
236    /// # Errors
237    ///
238    /// As [`from_clercq_temperley`].
239    pub fn to_part_with(&self, labels: CtLabels) -> Result<Stream> {
240        let mut expansion = Expansion::of(self, labels)?;
241        let song = self
242            .rules
243            .iter()
244            .position(|rule| rule.left_hand_side() == "S")
245            .ok_or_else(|| ct_error("a song with no S: rule to play it through"))?;
246        let places = expansion.expand(song, None, None)?;
247        let mut part = Stream::with_kind(StreamKind::Part);
248        let mut at = 0.0;
249        for (index, place) in places.iter().enumerate() {
250            let measure = expansion.built[*place]
251                .as_measure(IntegerType::try_from(index + 1).unwrap_or(IntegerType::MAX))?;
252            let length = measure.end_offset();
253            part.insert(at, measure);
254            at = op_frac(at + length);
255        }
256        let mut metadata = Metadata::new();
257        metadata.add("title", MetadataValue::new(self.title()));
258        part.set_metadata(Some(metadata));
259        Ok(part)
260    }
261}
262
263/// One rule of a song: music21's `CTRule`, a line of the text.
264#[derive(Clone, Debug, Default, PartialEq, Eq)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
266pub struct CtRule {
267    text: String,
268    left_hand_side: String,
269    music_text: String,
270}
271
272impl CtRule {
273    /// A rule read from its line.
274    pub fn new(text: &str) -> Self {
275        let left_hand_side = match text.split_whitespace().next() {
276            Some(word) if word.ends_with(':') => text
277                .split_once(':')
278                .map_or(String::new(), |(before, _)| before.trim().to_string()),
279            _ => String::new(),
280        };
281        // music21 reads the music from one character past the name,
282        // whatever that character is -- so a line with no name at all
283        // loses its first letter rather than keeping it.
284        let mut music_text = String::new();
285        if !text.is_empty() {
286            let after = text.get(left_hand_side.len() + 1..).unwrap_or_default();
287            let said = after.split('%').next().unwrap_or(after);
288            music_text = said.trim().to_string();
289        }
290        Self {
291            text: text.to_string(),
292            left_hand_side,
293            music_text,
294        }
295    }
296
297    /// The line as it was written, its name and comment included.
298    pub fn text(&self) -> &str {
299        &self.text
300    }
301
302    /// The name in front of the colon: music21's `LHS`.
303    pub fn left_hand_side(&self) -> &str {
304        &self.left_hand_side
305    }
306
307    /// The chords alone, without the name or the comment: music21's
308    /// `musicText`.
309    pub fn music_text(&self) -> &str {
310        &self.music_text
311    }
312
313    /// What was written after the `%`, if anything.
314    pub fn comment(&self) -> Option<&str> {
315        self.text.find('%').map(|at| self.text[at + 1..].trim())
316    }
317
318    /// The section the name stands for, as music21 spells the six it knows:
319    /// `In` is an introduction, `Br` a bridge, `Vr` a verse, `Ch` a chorus,
320    /// `Tg` a tag, `S` the song and `Fadeout` itself, with whatever follows
321    /// the abbreviation kept.
322    pub fn section_name(&self) -> String {
323        let name = &self.left_hand_side;
324        let rest = |at: usize| name.get(at..).unwrap_or_default();
325        for (abbreviation, said) in [
326            ("In", "Introduction"),
327            ("Br", "Bridge"),
328            ("Vr", "Verse"),
329            ("Ch", "Chorus"),
330            ("Tg", "Tag"),
331        ] {
332            if name.contains(abbreviation) {
333                return format!("{said}{}", rest(2));
334            }
335        }
336        match name.as_str() {
337            "S" => "Song".to_string(),
338            "Fadeout" => "Fadeout".to_string(),
339            other => other.to_string(),
340        }
341    }
342
343    /// The rule's text as the groups it is read in: music21's
344    /// `_measureGroups`, where each group is a measure or the expansion of
345    /// another rule, with how many times it is played.
346    pub fn measure_groups(&self) -> Vec<MeasureGroup> {
347        // First pass: split at every barline, keeping the count a `|*4`
348        // says beside it.
349        let split = split_measures(&self.music_text);
350        let mut barred: Vec<(String, String)> = Vec::new();
351        let mut index = 0;
352        while index < split.len() {
353            let content = split[index].trim().to_string();
354            let separator = split.get(index + 1).cloned().unwrap_or_default();
355            if !content.is_empty() || !separator.is_empty() {
356                barred.push((content, separator));
357            }
358            index += 2;
359        }
360        // Second pass: take the expansions out, each as a group of its own,
361        // and read the counts.
362        let mut groups: Vec<MeasureGroup> = Vec::new();
363        for (content, separator) in barred {
364            let mut kept: Vec<&str> = Vec::new();
365            for atom in content.split_whitespace() {
366                if let Some(named) = atom.strip_prefix('$') {
367                    if !kept.is_empty() {
368                        groups.push(MeasureGroup {
369                            content: kept.join(" "),
370                            kind: GroupKind::Continued,
371                            repeats: 1,
372                        });
373                        kept.clear();
374                    }
375                    let (repeats, named) = repetition(named);
376                    groups.push(MeasureGroup {
377                        content: named,
378                        kind: GroupKind::Expansion,
379                        repeats,
380                    });
381                } else {
382                    kept.push(atom);
383                }
384            }
385            let (repeats, separator) = repetition(&separator);
386            if !kept.is_empty() || separator == "|" {
387                groups.push(MeasureGroup {
388                    content: kept.join(" "),
389                    kind: if separator == "|" {
390                        GroupKind::Measure
391                    } else {
392                        GroupKind::Loose
393                    },
394                    repeats,
395                });
396            }
397        }
398        // Third pass: a measure saying nothing but its key or its meter
399        // holds the chord before it, and content with no barline after it
400        // carries on into the measure that follows.
401        for group in &mut groups {
402            let atoms: Vec<&str> = group.content.split_whitespace().collect();
403            if group.kind == GroupKind::Measure && atoms.iter().all(|atom| atom.starts_with('[')) {
404                group.content = atoms.join(" ");
405                if !group.content.is_empty() {
406                    group.content.push(' ');
407                }
408                group.content.push('.');
409            } else if group.kind == GroupKind::Continued {
410                group.kind = GroupKind::Measure;
411            }
412        }
413        groups
414    }
415
416    /// The measures the rule expands to on its own, in four-four and C
417    /// major unless it says otherwise: music21's `expand` on a rule with no
418    /// song behind it, which writes the key and the meter into every
419    /// measure it makes.
420    ///
421    /// # Errors
422    ///
423    /// A rule expanding another, which only a song can answer, a dot with
424    /// no chord before it, or a key, a meter or a figure the crate cannot
425    /// read.
426    pub fn expand(&self) -> Result<Vec<Stream>> {
427        self.expand_in(None, None)
428    }
429
430    /// The same, in a key and a meter of the caller's.
431    ///
432    /// # Errors
433    ///
434    /// As [`CtRule::expand`].
435    pub fn expand_in(&self, meter: Option<TimeSignature>, key: Option<Key>) -> Result<Vec<Stream>> {
436        let alone = [self.clone()];
437        let mut expansion = Expansion {
438            rules: &alone,
439            labels: CtLabels::default(),
440            has_song: false,
441            states: vec![RuleState {
442                meter: match meter {
443                    Some(meter) => meter,
444                    None => TimeSignature::from_ratio_string("4/4")?,
445                },
446                key: match key {
447                    Some(key) => key,
448                    None => Key::from_tonic("C")?,
449                },
450                last_regular_atom: String::new(),
451                last_chord: None,
452            }],
453            built: Vec::new(),
454            meters_said: Vec::new(),
455            keys_said: Vec::new(),
456        };
457        let places = expansion.expand(0, None, None)?;
458        places
459            .iter()
460            .enumerate()
461            .map(|(index, place)| {
462                expansion.built[*place]
463                    .as_measure(IntegerType::try_from(index + 1).unwrap_or(IntegerType::MAX))
464            })
465            .collect()
466    }
467}
468
469/// What a group of a rule's text stands for.
470#[derive(Clone, Copy, Debug, PartialEq, Eq)]
471#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
472pub enum GroupKind {
473    /// A measure, closed by a barline.
474    Measure,
475    /// Another rule, played through as many times as the `*` says.
476    Expansion,
477    /// Chords with no barline after them, which music21 warns about and
478    /// leaves out.
479    Loose,
480    /// Chords an expansion interrupted, which carry on into the measure
481    /// that follows: music21's `?`, which its second pass writes and its
482    /// third turns into a measure.
483    Continued,
484}
485
486/// One group of a rule's text: music21's `(content, sep, numReps)`.
487#[derive(Clone, Debug, PartialEq, Eq)]
488#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
489pub struct MeasureGroup {
490    /// The atoms of the group, as they were written.
491    pub content: String,
492    /// What the group stands for.
493    pub kind: GroupKind,
494    /// How many times it is played.
495    pub repeats: usize,
496}
497
498/// A figure as music21 reads one: music21's `fixupChordAtom`, which reads
499/// the lab's `x` as a diminished chord, its `h` as a half-diminished one
500/// and, on a minor degree, its `a` as an augmented one.
501///
502/// ```
503/// use music21_rs::romantext::clercq_temperley::fixup_chord_atom;
504///
505/// assert_eq!(fixup_chord_atom("iix"), "iio");
506/// assert_eq!(fixup_chord_atom("viih7"), "vii/o7");
507/// assert_eq!(fixup_chord_atom("iia"), "ii+");
508/// ```
509pub fn fixup_chord_atom(atom: &str) -> String {
510    let mut atom = atom.to_string();
511    if atom.contains('x') {
512        atom = atom.replace('x', "o");
513    }
514    if atom.contains('h') {
515        atom = atom.replace('h', "/o");
516    }
517    if atom.chars().next().is_some_and(char::is_lowercase) && atom.contains('a') {
518        atom = atom.replace('a', "+");
519    }
520    atom
521}
522
523/// Whether two chords sound the same notes, which is what decides a tie:
524/// music21's `isSame`, comparing the pitches by name and octave.
525pub fn is_same(chord: &Chord, last: Option<&Chord>) -> bool {
526    let Some(last) = last else { return false };
527    let names = |chord: &Chord| -> Vec<String> {
528        chord
529            .pitches()
530            .iter()
531            .map(crate::pitch::Pitch::name_with_octave)
532            .collect()
533    };
534    names(chord) == names(last)
535}
536
537/// Whether two keys are the one key, as music21's `==` reads them: the
538/// same tonic, spelled the same, in the same mode. The crate's [`Key`] is
539/// not `PartialEq`, and what music21 compares here is the key music21
540/// writes into a measure.
541fn same_key(one: &Key, other: &Key) -> bool {
542    one.tonic_pitch_name_with_case() == other.tonic_pitch_name_with_case()
543        && one.mode() == other.mode()
544        && one.sharps() == other.sharps()
545}
546
547/// What is inside a bracketed atom, `[3/4]` being `3/4`.
548fn bracketed(atom: &str) -> &str {
549    atom.trim_start_matches('[').trim_end_matches(']')
550}
551
552/// An error in a song, which music21 raises as a `CTSongException` or a
553/// `CTRuleException`.
554fn ct_error(said: impl Into<String>) -> Error {
555    Error::RomanText(said.into())
556}
557
558/// The rule's text split at every barline, the barlines kept: music21's
559/// `SPLITMEASURES`, a `|` with the `*4` that may follow it.
560fn split_measures(text: &str) -> Vec<String> {
561    let mut parts = Vec::new();
562    let mut rest = text;
563    while let Some(at) = rest.find('|') {
564        parts.push(rest[..at].to_string());
565        let after = &rest[at + 1..];
566        let bytes = after.as_bytes();
567        let mut walked = 0;
568        let mut separator = String::from("|");
569        if bytes.first() == Some(&b'*') {
570            separator.push('*');
571            walked += 1;
572        }
573        while bytes.get(walked).is_some_and(u8::is_ascii_digit) {
574            separator.push(char::from(bytes[walked]));
575            walked += 1;
576        }
577        parts.push(separator);
578        rest = &after[walked..];
579    }
580    parts.push(rest.to_string());
581    parts
582}
583
584/// The count a `*4` says and the text without it: music21's `REPETITION`,
585/// which reads the first `*` with digits after it.
586fn repetition(text: &str) -> (usize, String) {
587    let Some(at) = text.find('*') else {
588        return (1, text.to_string());
589    };
590    let digits: String = text[at + 1..]
591        .chars()
592        .take_while(char::is_ascii_digit)
593        .collect();
594    if digits.is_empty() {
595        return (1, text.to_string());
596    }
597    let mut kept = text[..at].to_string();
598    kept.push_str(&text[at + 1 + digits.len()..]);
599    (digits.parse().unwrap_or(1), kept)
600}
601
602/// A measure being built: the key and the meter it states, if it states
603/// them, and the chords and rests in the order they were read.
604#[derive(Clone, Debug, Default)]
605struct Built {
606    meter: Option<TimeSignature>,
607    key: Option<Key>,
608    items: Vec<StreamElement>,
609}
610
611impl Built {
612    /// The measure as a stream: the key and the meter at its start, and
613    /// each chord after the one before it.
614    fn as_measure(&self, number: IntegerType) -> Result<Stream> {
615        let mut measure = Stream::with_kind(StreamKind::Measure);
616        measure.set_number(number);
617        if let Some(key) = &self.key {
618            measure.insert(0.0, key.clone());
619        }
620        if let Some(meter) = &self.meter {
621            measure.insert(0.0, meter.clone());
622        }
623        let mut at = 0.0;
624        for item in &self.items {
625            let length = item.quarter_length();
626            measure.insert(at, item.clone());
627            at = op_frac(at + length);
628        }
629        Ok(measure)
630    }
631
632    /// The chord standing at a place in the measure, where one does.
633    fn chord(&self, item: usize) -> Option<&Chord> {
634        match self.items.get(item) {
635            Some(StreamElement::Chord(chord)) => Some(chord),
636            _ => None,
637        }
638    }
639}
640
641/// Where a chord stands while a song is being read.
642#[derive(Clone, Copy, Debug)]
643struct Place {
644    measure: usize,
645    item: usize,
646}
647
648/// What a rule keeps while it is expanded, which music21 keeps on the rule
649/// itself -- so the chord before is the one that rule last read, however
650/// many measures of other rules stand between.
651#[derive(Clone, Debug)]
652struct RuleState {
653    meter: TimeSignature,
654    key: Key,
655    last_regular_atom: String,
656    last_chord: Option<Place>,
657}
658
659/// A song being expanded: every measure made so far, in the order they were
660/// made, and what each rule is keeping.
661struct Expansion<'a> {
662    rules: &'a [CtRule],
663    labels: CtLabels,
664    /// Whether a song stands behind the rules, which decides whether a
665    /// measure restating the key or the meter says so: music21 writes both
666    /// into every measure of a rule it expands on its own, and otherwise
667    /// only where they have changed.
668    has_song: bool,
669    states: Vec<RuleState>,
670    built: Vec<Built>,
671    meters_said: Vec<TimeSignature>,
672    keys_said: Vec<Key>,
673}
674
675impl<'a> Expansion<'a> {
676    /// A song's rules, each starting in the key and the meter the song
677    /// opens in.
678    fn of(song: &'a CtSong, labels: CtLabels) -> Result<Self> {
679        let meter = match song.home_time_signature()? {
680            Some(meter) => meter,
681            None => TimeSignature::from_ratio_string("4/4")?,
682        };
683        let key = match song.home_key()? {
684            Some(key) => key,
685            None => Key::from_tonic("C")?,
686        };
687        let state = RuleState {
688            meter,
689            key,
690            last_regular_atom: String::new(),
691            last_chord: None,
692        };
693        Ok(Self {
694            rules: song.rules(),
695            labels,
696            has_song: true,
697            states: vec![state; song.rules().len()],
698            built: Vec::new(),
699            meters_said: Vec::new(),
700            keys_said: Vec::new(),
701        })
702    }
703
704    /// One rule expanded whole: music21's `expand`, which reads its groups
705    /// in the key and the meter it is given and puts its own back
706    /// afterwards.
707    fn expand(
708        &mut self,
709        rule: usize,
710        meter: Option<TimeSignature>,
711        key: Option<Key>,
712    ) -> Result<Vec<usize>> {
713        let kept = (
714            self.states[rule].meter.clone(),
715            self.states[rule].key.clone(),
716        );
717        if let Some(meter) = meter {
718            self.states[rule].meter = meter;
719        }
720        if let Some(key) = key {
721            self.states[rule].key = key;
722        }
723        let mut places = Vec::new();
724        for group in self.rules[rule].measure_groups() {
725            match group.kind {
726                GroupKind::Expansion => {
727                    self.expand_expansion(rule, &group.content, group.repeats, &mut places)?;
728                }
729                GroupKind::Measure => {
730                    self.expand_simple(rule, &group.content, group.repeats, &mut places)?;
731                }
732                // music21 warns and reads nothing where a group has
733                // neither a barline nor a name.
734                GroupKind::Loose | GroupKind::Continued => {}
735            }
736        }
737        self.label_section(rule, &places)?;
738        self.states[rule].meter = kept.0;
739        self.states[rule].key = kept.1;
740        Ok(places)
741    }
742
743    /// Another rule played through: music21's `expandExpansionContent`.
744    fn expand_expansion(
745        &mut self,
746        rule: usize,
747        content: &str,
748        repeats: usize,
749        places: &mut Vec<usize>,
750    ) -> Result<()> {
751        let named = self
752            .rules
753            .iter()
754            .position(|known| known.left_hand_side() == content)
755            .filter(|_| self.has_song)
756            .ok_or_else(|| {
757                ct_error(format!(
758                    "cannot expand rule {content} in {}",
759                    self.rules[rule].text()
760                ))
761            })?;
762        for _ in 0..repeats {
763            let meter = self.states[rule].meter.clone();
764            let key = self.states[rule].key.clone();
765            let returned = self.expand(named, Some(meter.clone()), Some(key.clone()))?;
766            let Some(first) = returned.first() else {
767                return Err(ct_error(format!(
768                    "rule {content} expands to no measure at all"
769                )));
770            };
771            self.insert_key_and_meter(*first, &meter, &key);
772            places.extend(returned);
773        }
774        Ok(())
775    }
776
777    /// A measure of the rule's own chords: music21's `expandSimpleContent`,
778    /// which shares the bar out equally among them.
779    fn expand_simple(
780        &mut self,
781        rule: usize,
782        content: &str,
783        repeats: usize,
784        places: &mut Vec<usize>,
785    ) -> Result<()> {
786        // The key and the meter first, since they say how long the chords
787        // of this very measure are.
788        let mut regular: Vec<String> = Vec::new();
789        for atom in content.split_whitespace() {
790            if atom.starts_with('[') {
791                let inside = bracketed(atom);
792                if inside == "0" {
793                    // A meter music21 cannot write; it reads the measure in
794                    // four-four instead.
795                    self.states[rule].meter = TimeSignature::from_ratio_string("4/4")?;
796                } else if inside.contains('/') {
797                    self.states[rule].meter = TimeSignature::from_ratio_string(inside)?;
798                } else {
799                    let named = convert_key_string_to_music21_key_string(inside);
800                    self.states[rule].key = Key::from_tonic(&named)?;
801                }
802            } else if atom == "." {
803                if self.states[rule].last_regular_atom.is_empty() {
804                    return Err(ct_error(format!(
805                        ". with no chord before it: {}",
806                        self.rules[rule].text()
807                    )));
808                }
809                regular.push(self.states[rule].last_regular_atom.clone());
810            } else {
811                regular.push(atom.to_string());
812                self.states[rule].last_regular_atom = atom.to_string();
813            }
814        }
815        if regular.is_empty() {
816            // The group said nothing but a key or a meter.
817            return Ok(());
818        }
819        let measure = self.built.len();
820        self.built.push(Built::default());
821        let meter = self.states[rule].meter.clone();
822        let key = self.states[rule].key.clone();
823        self.insert_key_and_meter(measure, &meter, &key);
824        let length = op_frac(meter.bar_duration().quarter_length() / regular.len() as FloatType);
825        let mut last_is_in_this_measure = false;
826        for atom in regular {
827            if atom == "R" {
828                self.built[measure]
829                    .items
830                    .push(Rest::new(Duration::new(length)?).into());
831                self.states[rule].last_chord = None;
832                last_is_in_this_measure = false;
833                continue;
834            }
835            let numeral = RomanNumeral::new(fixup_chord_atom(&atom), key.clone())?;
836            let mut chord = numeral.to_chord()?.with_numeral(numeral);
837            let last = self.states[rule]
838                .last_chord
839                .and_then(|place| self.built[place.measure].chord(place.item))
840                .cloned();
841            if is_same(&chord, last.as_ref()) && last_is_in_this_measure {
842                // The same chord twice in one measure is one chord as long
843                // as the two.
844                if let Some(place) = self.states[rule].last_chord
845                    && let Some(StreamElement::Chord(held)) =
846                        self.built[place.measure].items.get_mut(place.item)
847                {
848                    let held_length = held
849                        .duration()
850                        .map_or(0.0, crate::duration::Duration::quarter_length);
851                    held.set_duration(Duration::new(op_frac(held_length + length))?);
852                }
853                continue;
854            }
855            chord.set_duration(Duration::new(length)?);
856            self.tie_and_label(rule, &mut chord, last.as_ref())?;
857            let item = self.built[measure].items.len();
858            self.built[measure].items.push(chord.into());
859            self.states[rule].last_chord = Some(Place { measure, item });
860            last_is_in_this_measure = true;
861        }
862        places.push(measure);
863        // A measure played more than once is copied, the copies saying
864        // nothing about the key or the meter.
865        for _ in 1..repeats {
866            let mut copy = self.built[measure].clone();
867            copy.meter = None;
868            copy.key = None;
869            places.push(self.built.len());
870            self.built.push(copy);
871        }
872        Ok(())
873    }
874
875    /// The tie a chord repeated across a barline carries, and the figure
876    /// written under a chord that is not: music21's
877    /// `addOptionalTieAndLyrics`, which writes the tie onto the chord
878    /// before as well.
879    fn tie_and_label(
880        &mut self,
881        rule: usize,
882        chord: &mut Chord,
883        last: Option<&Chord>,
884    ) -> Result<()> {
885        let same = is_same(chord, last);
886        let before = self.states[rule].last_chord;
887        let before_tie = last.and_then(|chord| chord.tie().cloned());
888        if !same {
889            if let Some(mut tie) = before_tie {
890                tie.set_tie_type(crate::notation::TieType::Stop);
891                self.write_tie(before, Some(tie));
892            }
893            if self.labels.roman_numerals {
894                let figure = chord
895                    .numeral()
896                    .map(|numeral| numeral.figure().to_string())
897                    .unwrap_or_default();
898                chord.add_lyric(&figure, Some(1), false)?;
899            }
900            return Ok(());
901        }
902        match before_tie {
903            None => {
904                self.write_tie(before, Some(Tie::new(crate::notation::TieType::Start)));
905            }
906            Some(mut tie) => {
907                tie.set_tie_type(crate::notation::TieType::Continue);
908                self.write_tie(before, Some(tie));
909            }
910        }
911        chord.set_tie(Some(Tie::new(crate::notation::TieType::Stop)));
912        Ok(())
913    }
914
915    /// Writes a tie onto a chord already built.
916    fn write_tie(&mut self, place: Option<Place>, tie: Option<Tie>) {
917        if let Some(place) = place
918            && let Some(StreamElement::Chord(held)) =
919                self.built[place.measure].items.get_mut(place.item)
920        {
921            held.set_tie(tie);
922        }
923    }
924
925    /// The key and the meter written into a measure where they have not
926    /// been said yet: music21's `insertKsTs`.
927    fn insert_key_and_meter(&mut self, measure: usize, meter: &TimeSignature, key: &Key) {
928        if !self.has_song {
929            // A rule expanded on its own says both in every measure.
930            self.built[measure].meter = Some(meter.clone());
931            self.built[measure].key = Some(key.clone());
932            return;
933        }
934        if !self.meters_said.contains(meter) {
935            self.built[measure].meter = Some(meter.clone());
936            self.meters_said.push(meter.clone());
937        }
938        if !self.keys_said.iter().any(|said| same_key(said, key)) {
939            self.built[measure].key = Some(key.clone());
940            self.keys_said.push(key.clone());
941        }
942    }
943
944    /// The name of the rule written under the first chord it brought: the
945    /// end of music21's `expand`, which writes it on the first measure of
946    /// the rule holding a note at all and on no other.
947    fn label_section(&mut self, rule: usize, places: &[usize]) -> Result<()> {
948        if !self.labels.subsections || self.rules[rule].left_hand_side() == "S" {
949            return Ok(());
950        }
951        let name = self.rules[rule].left_hand_side().to_string();
952        for place in places {
953            let Some(item) = self.built[*place]
954                .items
955                .iter()
956                .position(|item| matches!(item, StreamElement::Chord(_)))
957            else {
958                // A measure of rests alone carries no name.
959                continue;
960            };
961            if let Some(StreamElement::Chord(chord)) = self.built[*place].items.get_mut(item) {
962                let number = IntegerType::try_from(chord.lyrics().len() + 1).unwrap_or(1);
963                chord.add_lyric(&name, Some(number), false)?;
964            }
965            break;
966        }
967        Ok(())
968    }
969}
970
971#[cfg(test)]
972mod tests {
973    use super::*;
974
975    /// music21's own `_measureGroups` docstring, group for group: a rule
976    /// expanding others, measures of its own, an empty measure repeating
977    /// the chord before it, and the counts a `|*4` says.
978    #[test]
979    fn a_rule_is_read_as_the_groups_music21_reads() {
980        let rule = CtRule::new(
981            "In: [A] [4/4] $Vr $BP*3 I IV | I | $BP*3 I IV | I | | R |*4 I |*4 % A comment",
982        );
983        let said: Vec<(String, GroupKind, usize)> = rule
984            .measure_groups()
985            .into_iter()
986            .map(|group| (group.content, group.kind, group.repeats))
987            .collect();
988        let expected: Vec<(String, GroupKind, usize)> = vec![
989            ("[A] [4/4]", GroupKind::Measure, 1),
990            ("Vr", GroupKind::Expansion, 1),
991            ("BP", GroupKind::Expansion, 3),
992            ("I IV", GroupKind::Measure, 1),
993            ("I", GroupKind::Measure, 1),
994            ("BP", GroupKind::Expansion, 3),
995            ("I IV", GroupKind::Measure, 1),
996            ("I", GroupKind::Measure, 1),
997            (".", GroupKind::Measure, 1),
998            ("R", GroupKind::Measure, 4),
999            ("I", GroupKind::Measure, 4),
1000        ]
1001        .into_iter()
1002        .map(|(content, kind, repeats)| (content.to_string(), kind, repeats))
1003        .collect();
1004        assert_eq!(said, expected);
1005        assert_eq!(rule.left_hand_side(), "In");
1006        assert_eq!(rule.comment(), Some("A comment"));
1007        assert_eq!(
1008            rule.music_text(),
1009            "[A] [4/4] $Vr $BP*3 I IV | I | $BP*3 I IV | I | | R |*4 I |*4"
1010        );
1011    }
1012
1013    /// An empty measure holds the chord before it and a measure saying a
1014    /// meter holds it too, which is music21's `_measureGroups` docstring
1015    /// again -- the third measure is a four-four `IV` and the fourth the
1016    /// same chord in two-four, lasting half as long.
1017    #[test]
1018    fn an_empty_measure_holds_the_chord_before_it() {
1019        let rule = CtRule::new("Vr: [4/4] bVII | IV | | [2/4] |");
1020        let measures = rule.expand().expect("the rule expands");
1021        assert_eq!(measures.len(), 4);
1022        let described = |measure: &Stream| -> Vec<String> {
1023            measure
1024                .leaves()
1025                .iter()
1026                .map(|(offset, element)| match element {
1027                    StreamElement::Chord(chord) => format!(
1028                        "{offset} {} {}",
1029                        chord.numeral().map_or("", RomanNumeral::figure),
1030                        element.quarter_length()
1031                    ),
1032                    StreamElement::TimeSignature(meter) => meter.ratio_string(),
1033                    StreamElement::Key(key) => key.tonic_pitch_name_with_case(),
1034                    other => format!("{other:?}"),
1035                })
1036                .collect()
1037        };
1038        assert_eq!(described(&measures[2]), ["C", "4/4", "0 IV 4"]);
1039        assert_eq!(described(&measures[3]), ["C", "2/4", "0 IV 2"]);
1040    }
1041
1042    /// The song music21 measures in its own `toPart` docstring.
1043    #[test]
1044    fn blitzkrieg_bop_is_as_long_as_music21_makes_it() {
1045        let song = "% Blitzkrieg Bop\n\
1046                    BP: I | IV V | %THIS IS A COMMENT\n\
1047                    In: $BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4\n\
1048                    Vr: $BP*3 I IV | I |\n\
1049                    Br: IV | | I | IV I | IV | | ii | IV V |\n\
1050                    Co: R |*4 I |*4\n\
1051                    S: [A] $In $Vr $Vr $Br $Vr $Vr $Br $Vr $Vr $Co\n";
1052        let part = from_clercq_temperley(song).expect("the song reads");
1053        assert_eq!(part.measures().len(), 96);
1054        assert_eq!(
1055            part.measures().last().map(|measure| measure.number()),
1056            Some(96)
1057        );
1058        // music21's `partObj.highestOffset`, which is where the last
1059        // measure stands.
1060        assert_eq!(part.end_offset() - 4.0, 380.0);
1061    }
1062
1063    /// The title and the comments of music21's smallest example, as its own
1064    /// docstrings read them.
1065    #[test]
1066    fn a_song_says_its_title_and_its_comments() {
1067        let song = CtSong::parse(
1068            "\n% Simple Gifts\n% A wonderful shaker melody\nVr: I | I | %incomplete verse\n\
1069             S: [A] $Vr % Not quite finished!",
1070        );
1071        assert_eq!(song.title(), "Simple Gifts");
1072        assert_eq!(
1073            song.comments(),
1074            vec![
1075                vec!["A wonderful shaker melody".to_string()],
1076                vec!["Vr:".to_string(), "incomplete verse".to_string()],
1077                vec!["S:".to_string(), "Not quite finished!".to_string()],
1078            ]
1079        );
1080        assert_eq!(song.rules().len(), 2);
1081        assert_eq!(
1082            song.home_key()
1083                .expect("a key")
1084                .map(|key| key.tonic_pitch_name_with_case()),
1085            Some("A".to_string())
1086        );
1087        assert_eq!(
1088            song.home_time_signature()
1089                .expect("a meter")
1090                .map(|meter| meter.ratio_string()),
1091            Some("4/4".to_string())
1092        );
1093    }
1094
1095    /// What music21 reads off a line with no name on it, and a title with a
1096    /// per cent in it: both are what its own slicing gives rather than what
1097    /// a reader would guess, and both were read off music21.
1098    #[test]
1099    fn a_line_is_read_as_music21_slices_it() {
1100        // One character past the name, whatever that character is.
1101        assert_eq!(CtRule::new("I | IV |").music_text(), "| IV |");
1102        assert_eq!(CtRule::new("I | IV |").left_hand_side(), "");
1103        assert_eq!(CtRule::new("In: $BP*3 I | % c").music_text(), "$BP*3 I |");
1104        // Every per cent comes out of the title, not only the one in front.
1105        let song = CtSong::parse("% 50% More\nVr: I |\nS: [C] $Vr\n");
1106        assert_eq!(song.title(), "50 More");
1107    }
1108
1109    /// The sections music21 spells out, its own docstring's among them.
1110    #[test]
1111    fn a_rule_names_the_section_it_stands_for() {
1112        let named = |text: &str| CtRule::new(text).section_name();
1113        assert_eq!(named("Vr2: I |"), "Verse2");
1114        assert_eq!(named("In: I |"), "Introduction");
1115        assert_eq!(named("Br: I |"), "Bridge");
1116        assert_eq!(named("Ch2: I |"), "Chorus2");
1117        assert_eq!(named("Tg: I |"), "Tag");
1118        assert_eq!(named("S: $Vr"), "Song");
1119        assert_eq!(named("Fadeout: I |"), "Fadeout");
1120        assert_eq!(named("Xx: I |"), "Xx");
1121    }
1122}