1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct CtLabels {
43 pub roman_numerals: bool,
45 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
59pub fn from_clercq_temperley(text: &str) -> Result<Stream> {
77 song_text(text)?;
78 CtSong::parse(text).to_part()
79}
80
81pub 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
91fn 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#[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 pub fn parse(text: &str) -> Self {
120 Self::from_lines(text.split('\n'))
121 }
122
123 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 pub fn text(&self) -> &str {
160 &self.text
161 }
162
163 pub fn lines(&self) -> &[String] {
165 &self.lines
166 }
167
168 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 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 pub fn rules(&self) -> &[CtRule] {
198 &self.rules
199 }
200
201 pub fn rule(&self, name: &str) -> Option<&CtRule> {
203 self.rules.iter().find(|rule| rule.left_hand_side() == name)
204 }
205
206 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 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 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 pub fn to_part(&self) -> Result<Stream> {
263 self.to_part_with(CtLabels::default())
264 }
265
266 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 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#[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 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 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 pub fn text(&self) -> &str {
334 &self.text
335 }
336
337 pub fn left_hand_side(&self) -> &str {
339 &self.left_hand_side
340 }
341
342 pub fn music_text(&self) -> &str {
345 &self.music_text
346 }
347
348 pub fn comment(&self) -> Option<&str> {
350 self.text.find('%').map(|at| self.text[at + 1..].trim())
351 }
352
353 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 pub fn measure_groups(&self) -> Vec<MeasureGroup> {
382 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 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 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 pub fn expand(&self) -> Result<Vec<Stream>> {
462 self.expand_in(None, None)
463 }
464
465 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
507#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
508pub enum GroupKind {
509 Measure,
511 Expansion,
513 Loose,
516 Continued,
520}
521
522#[derive(Clone, Debug, PartialEq, Eq)]
524#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
525pub struct MeasureGroup {
526 pub content: String,
528 pub kind: GroupKind,
530 pub repeats: usize,
532}
533
534pub 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
559pub 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
573fn 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
583fn bracketed(atom: &str) -> &str {
585 atom.trim_start_matches('[').trim_end_matches(']')
586}
587
588fn ct_error(said: impl Into<String>) -> Error {
591 Error::roman_text(said.into())
592}
593
594fn 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
620fn 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#[derive(Clone, Debug, Default)]
641struct Built {
642 meter: Option<TimeSignature>,
643 key: Option<Key>,
644 items: Vec<StreamElement>,
645}
646
647impl Built {
648 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 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#[derive(Clone, Copy, Debug)]
679struct Place {
680 measure: usize,
681 item: usize,
682}
683
684#[derive(Clone, Debug)]
688struct RuleState {
689 meter: TimeSignature,
690 key: Key,
691 last_regular_atom: String,
692 last_chord: Option<Place>,
693}
694
695const MAX_DEPTH: usize = 64;
699
700struct Expansion<'a> {
703 rules: &'a [CtRule],
704 labels: CtLabels,
705 has_song: bool,
710 states: Vec<RuleState>,
711 built: Vec<Built>,
712 meters_said: Vec<TimeSignature>,
713 keys_said: Vec<Key>,
714 expanding: Vec<usize>,
716}
717
718impl<'a> Expansion<'a> {
719 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 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 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 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 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 fn expand_simple(
858 &mut self,
859 rule: usize,
860 content: &str,
861 repeats: usize,
862 places: &mut Vec<usize>,
863 ) -> Result<()> {
864 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 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 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 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 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 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 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 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 fn insert_key_and_meter(&mut self, measure: usize, meter: &TimeSignature, key: &Key) {
1020 if !self.has_song {
1021 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 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 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 #[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 #[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 #[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 assert_eq!(part.end_offset() - 4.0, 380.0);
1166 }
1167
1168 #[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 #[test]
1204 fn a_line_is_read_as_music21_slices_it() {
1205 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 let song = CtSong::parse("% 50% More\nVr: I |\nS: [C] $Vr\n");
1211 assert_eq!(song.title(), "50 More");
1212 }
1213
1214 #[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 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 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 #[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}