Skip to main content

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