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