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, FormatErrorKind, Position, Result};
26use crate::key::{Key, KeySignature, convert_key_string_to_music21_key_string};
27use crate::makenotation::{MAX_MEASURES, make_accidentals, make_beams, op_frac, sorted_events};
28use crate::metadata::{Metadata, MetadataValue};
29use crate::meter::TimeSignature;
30use crate::notation::{Tie, TieType};
31use crate::rest::Rest;
32use crate::roman::{Minor67Default, RomanNumeral};
33use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
34
35fn roman_text_error(message: impl Into<String>) -> Error {
36 Error::roman_text(message.into())
37}
38
39#[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 (index, line) in lines.iter().enumerate().skip(body) {
227 let line = line.trim();
228 if line.is_empty() {
229 continue;
230 }
231 let Some(end) = measure_tag_end(line) else {
232 tokens.push(tagged(line));
233 continue;
234 };
235 let (numbers, letters) = measure_numbers(line[..end].trim())
236 .map_err(|error| error.placed(|| Position::line(index + 1)))?;
237 let raw = line[end..].trim();
238 let variant = raw.strip_prefix("var").is_some_and(|rest| {
240 rest.starts_with(|c: char| c.is_ascii_digit() || c.is_ascii_uppercase())
241 });
242 tokens.push(Token::Measure {
243 numbers,
244 letters,
245 variant,
246 data: raw.to_string(),
247 atoms: atoms_of(raw),
248 });
249 }
250 Ok(tokens)
251}
252
253fn key_and_prefix(word: &str) -> Result<(Key, String)> {
256 let name = convert_key_string_to_music21_key_string(word.trim_end_matches([';', ':']));
257 let key = Key::from_tonic(&name)?;
258 let named = music21_key_name(&key);
261 Ok((key, format!("{named}: ")))
262}
263
264pub(super) fn music21_key_name(key: &Key) -> String {
267 let mut name = key.tonic_pitch_name_with_case();
268 let accidental = name.split_off(1);
270 name.push_str(&accidental.replace('b', "-"));
271 name
272}
273
274fn beat_of(word: &str) -> Result<FloatType> {
277 let text = word.replace('b', "");
278 let parts: Vec<&str> = text.split('.').collect();
279 if parts.len() > 4 {
280 return Err(roman_text_error(format!(
281 "cannot handle specification: {word}"
282 )));
283 }
284 let read = |text: &str| -> Result<FloatType> {
285 text.parse::<FloatType>()
286 .map_err(|_| roman_text_error(format!("cannot read the beat {word:?}")))
287 };
288 let main = read(parts[0])?;
289 let mut fraction = 0.0;
291 let mut denominator = 1.0;
292 if parts.len() > 1 {
293 fraction = read(&format!(".{}", parts[1]))?;
294 for (numerator, over) in [(1.0, 3.0), (2.0, 3.0), (1.0, 6.0), (5.0, 6.0)] {
295 if (fraction - numerator / over).abs() <= 1e-2 {
296 fraction = numerator / over;
297 }
298 }
299 denominator = crate::duration::limited_fraction(fraction, 1_000_000)
300 .map_or(1.0, |(_, denominator)| denominator as FloatType);
301 }
302 let further = if parts.len() > 2 {
303 op_frac(1.0 / (denominator / read(&format!(".{}", parts[2]))?))
304 } else {
305 0.0
306 };
307 Ok(op_frac(main + fraction + further))
308}
309
310#[allow(clippy::large_enum_variant)]
313#[derive(Clone, Debug)]
314enum Sound {
315 Numeral {
316 figure: String,
317 key: Key,
318 sixth: Minor67Default,
319 seventh: Minor67Default,
320 follows_key_change: bool,
321 pivot: Option<Key>,
323 },
324 Rest,
325}
326
327#[derive(Clone, Debug)]
330struct Sounding {
331 sound: Sound,
332 quarter_length: FloatType,
333 lyric: Option<String>,
335 tie: Option<TieType>,
336}
337
338impl Sounding {
339 fn is_numeral(&self) -> bool {
340 matches!(self.sound, Sound::Numeral { .. })
341 }
342
343 fn element(&self) -> Result<StreamElement> {
344 let duration = Duration::new(self.quarter_length)?;
345 match &self.sound {
346 Sound::Rest => {
347 let mut rest = Rest::new(duration);
348 if let Some(lyric) = &self.lyric {
349 rest.set_lyric(Some(lyric));
350 }
351 rest.set_tie(self.tie.map(Tie::new));
352 Ok(rest.into())
353 }
354 Sound::Numeral {
355 figure,
356 key,
357 sixth,
358 seventh,
359 ..
360 } => {
361 let numeral =
362 RomanNumeral::with_minor_defaults(figure, key.clone(), *sixth, *seventh)?;
363 let mut chord: Chord = numeral
364 .to_chord()?
365 .with_duration(duration)
366 .with_numeral(numeral);
367 if let (Some(lyric), Some(singer)) = (&self.lyric, chord.notes_mut().first_mut()) {
368 singer.set_lyric(Some(lyric))?;
369 }
370 chord.set_tie(self.tie.map(Tie::new));
371 Ok(chord.into())
372 }
373 }
374 }
375}
376
377#[allow(clippy::large_enum_variant)]
380#[derive(Clone, Debug)]
381enum Held {
382 Sounding(usize),
384 Element(StreamElement),
385}
386
387#[derive(Clone, Debug, Default)]
388struct Measure {
389 number: IntegerType,
390 suffix: Option<String>,
391 items: Vec<(FloatType, Held)>,
392 left: Option<Barline>,
393 right: Option<Barline>,
394 padding_left: FloatType,
397 padding_right: FloatType,
398}
399
400struct Translator {
402 metadata: Metadata,
403 measures: Vec<Measure>,
404 soundings: Vec<Sounding>,
405 meter: TimeSignature,
406 meter_at_last_chord: TimeSignature,
407 meter_set: bool,
408 last_measure_number: IntegerType,
409 last_letters: Vec<String>,
410 previous: Option<usize>,
411 signature: Option<KeySignature>,
412 signature_placed: bool,
413 found_signature: bool,
414 key: Key,
415 prefix: String,
416 sixth: Minor67Default,
417 seventh: Minor67Default,
418 endings: Vec<(u32, Vec<(IntegerType, usize)>)>,
421 previous_in_measure: Option<usize>,
423 pivot_possible: bool,
424 key_change: bool,
425 offset: FloatType,
426}
427
428impl Translator {
429 fn new() -> Result<Self> {
430 Ok(Self {
431 metadata: Metadata::new(),
432 measures: Vec::new(),
433 soundings: Vec::new(),
434 meter: TimeSignature::new(4, 4)?,
435 meter_at_last_chord: TimeSignature::new(4, 4)?,
436 meter_set: false,
437 last_measure_number: 0,
438 last_letters: Vec::new(),
439 previous: None,
440 signature: None,
441 signature_placed: true,
442 found_signature: false,
443 key: Key::from_tonic("C")?,
444 prefix: String::new(),
445 sixth: Minor67Default::Cautionary,
446 seventh: Minor67Default::Cautionary,
447 endings: Vec::new(),
448 previous_in_measure: None,
449 pivot_possible: false,
450 key_change: false,
451 offset: 0.0,
452 })
453 }
454
455 fn add(&mut self, sounding: Sounding) -> usize {
456 self.soundings.push(sounding);
457 self.soundings.len() - 1
458 }
459
460 fn continued(&mut self, quarter_length: FloatType) -> Option<usize> {
462 let previous = self.previous?;
463 let mut copy = self.soundings[previous].clone();
464 copy.lyric = Some(String::new());
465 copy.quarter_length = quarter_length;
466 copy.tie = Some(TieType::Stop);
467 let held = &mut self.soundings[previous];
468 held.tie = Some(if held.tie.is_none() {
469 TieType::Start
470 } else {
471 TieType::Continue
472 });
473 let made = self.add(copy);
474 self.previous = Some(made);
475 Some(made)
476 }
477
478 fn note_ending(
480 &mut self,
481 letters: &[String],
482 measure: &mut Measure,
483 place: usize,
484 ) -> Result<()> {
485 if letters.is_empty() {
486 return Ok(());
487 }
488 measure.suffix = Some(letters[0].clone()).filter(|letter| !letter.is_empty());
489 for letter in letters {
490 if letter.is_empty() {
491 continue;
492 }
493 let number = match letter.as_str() {
494 "a" => 1,
495 "b" => 2,
496 "c" => 3,
497 "d" => 4,
498 "e" => 5,
499 "f" => 6,
500 "g" => 7,
501 "h" => 8,
502 _ => {
503 return Err(roman_text_error(format!(
504 "Improper repeat letter: {letter}"
505 )));
506 }
507 };
508 let entry = (measure.number, place);
509 match self.endings.iter_mut().find(|(held, _)| *held == number) {
510 Some((_, measures)) => measures.push(entry),
511 None => self.endings.push((number, vec![entry])),
512 }
513 }
514 Ok(())
515 }
516
517 fn fill_from_previous(&mut self, measure: &mut Measure) {
519 let length = self.meter_at_last_chord.bar_quarter_length();
520 if let Some(made) = self.continued(length) {
521 let at = measure
522 .items
523 .iter()
524 .map(|(offset, held)| match held {
525 Held::Sounding(index) => offset + self.soundings[*index].quarter_length,
526 Held::Element(_) => *offset,
527 })
528 .fold(0.0, FloatType::max);
529 measure.items.push((at, Held::Sounding(made)));
530 }
531 }
532
533 fn measure_length(&self, measure: &Measure) -> FloatType {
534 measure
535 .items
536 .iter()
537 .map(|(offset, held)| match held {
538 Held::Sounding(index) => offset + self.soundings[*index].quarter_length,
539 Held::Element(_) => *offset,
540 })
541 .fold(0.0, FloatType::max)
542 }
543
544 fn copied(&mut self, source: usize, number: IntegerType) -> Measure {
547 let mut measure = self.measures[source].clone();
548 measure.number = number;
549 for (_, held) in &mut measure.items {
550 let Held::Sounding(index) = held else {
551 continue;
552 };
553 let mut copy = self.soundings[*index].clone();
554 if let Sound::Numeral {
555 figure,
556 key,
557 sixth,
558 seventh,
559 follows_key_change,
560 pivot,
561 } = &mut copy.sound
562 {
563 if *follows_key_change {
564 self.key = key.clone();
565 } else if let Some(pivot) = pivot {
566 self.key = pivot.clone();
567 } else if key.tonic().name() != self.key.tonic().name()
568 || key.mode() != self.key.mode()
569 {
570 *key = self.key.clone();
573 copy.tie = None;
574 }
575 if figure.contains('/')
578 && RomanNumeral::new(figure.as_str(), key.clone())
579 .is_ok_and(|numeral| numeral.secondary().is_some())
580 {
581 *key = self.key.clone();
582 *sixth = Minor67Default::default();
583 *seventh = Minor67Default::default();
584 *follows_key_change = false;
585 *pivot = None;
586 copy.tie = None;
587 }
588 }
589 self.soundings.push(copy);
590 *index = self.soundings.len() - 1;
591 }
592 measure
593 }
594
595 fn last_numeral(&self, measure: &Measure) -> Option<usize> {
596 let mut items: Vec<&(FloatType, Held)> = measure.items.iter().collect();
597 items.sort_by(|left, right| left.0.total_cmp(&right.0));
598 items.into_iter().rev().find_map(|(_, held)| match held {
599 Held::Sounding(index) if self.soundings[*index].is_numeral() => Some(*index),
600 _ => None,
601 })
602 }
603
604 fn measure(
606 &mut self,
607 numbers: &[IntegerType],
608 letters: &[String],
609 variant: bool,
610 data: &str,
611 atoms: &[Atom],
612 ) -> Result<()> {
613 if variant {
614 return Ok(());
615 }
616 let is_copy = data.starts_with('=');
617 let next = i64::from(self.last_measure_number) + 1;
618 if i64::from(numbers[0]) > next && self.previous.is_some() {
619 let left_out = i64::from(numbers[0]) - next;
620 if left_out + self.measures.len() as i64 > MAX_MEASURES as i64 {
621 return Err(roman_text_error(format!(
622 "measure {} would make more than {MAX_MEASURES} measures",
623 numbers[0]
624 ))
625 .of_kind(FormatErrorKind::TooLarge)
626 .placed(|| Position::measure(numbers[0].to_string())));
627 }
628 for number in self.last_measure_number + 1..numbers[0] {
630 let mut fill = Measure {
631 number,
632 ..Measure::default()
633 };
634 self.fill_from_previous(&mut fill);
635 let letters = self.last_letters.clone();
636 let place = self.measures.len();
637 self.note_ending(&letters, &mut fill, place)?;
638 fill.number = number;
639 self.measures.push(fill);
640 }
641 self.last_measure_number = numbers[0] - 1;
642 self.last_letters = letters.to_vec();
643 }
644
645 if numbers.len() == 1 && is_copy {
646 let (targets, _) = measure_numbers(data.replace('=', "").trim())?;
647 if targets.len() > 1 {
648 return Err(roman_text_error(
649 "a single measure cannot define a copy operation for multiple measures",
650 ));
651 }
652 let source = self
653 .measures
654 .iter()
655 .position(|measure| measure.number == targets[0])
656 .ok_or_else(|| {
657 roman_text_error(format!(
658 "Could not find measure {} to copy from",
659 targets[0]
660 ))
661 })?;
662 let measure = self.copied(source, numbers[0]);
663 if let Some(last) = self.last_numeral(&measure) {
664 self.previous = Some(last);
665 }
666 self.last_measure_number = measure.number;
667 self.last_letters = letters.to_vec();
668 self.measures.push(measure);
669 } else if numbers.len() > 1 {
670 let (targets, _) = measure_numbers(data.replace('=', "").trim())?;
671 if targets.len() == 1 {
672 return Err(roman_text_error(
673 "a multiple measure range cannot copy a single measure",
674 ));
675 }
676 let (start, end) = (targets[0], targets[1]);
677 if numbers[1] - numbers[0] != end - start {
678 return Err(roman_text_error(
679 "both the source and destination sections need to have the same number of \
680 measures",
681 ));
682 }
683 if numbers[0] < end {
684 return Err(roman_text_error(
685 "the source section cannot overlap with the destination section",
686 ));
687 }
688 let mut made: Vec<Measure> = Vec::new();
689 for source in 0..self.measures.len() {
690 let number = self.measures[source].number;
691 if (start..=end).contains(&number) {
692 made.push(self.copied(source, numbers[0] + number - start));
693 }
694 if number == end {
695 break;
696 }
697 }
698 let Some(last) = made.last() else {
699 return Err(roman_text_error(format!(
700 "Could not find measures {start}-{end} to copy from"
701 )));
702 };
703 if let Some(last) = self.last_numeral(last) {
704 self.previous = Some(last);
705 }
706 self.last_measure_number = last.number;
707 self.last_letters = letters.to_vec();
708 self.measures.extend(made);
709 } else {
710 let mut measure = self.single(numbers[0], letters, atoms)?;
711 if self.measure_length(&measure) == 0.0 {
712 self.fill_from_previous(&mut measure);
713 }
714 self.measures.push(measure);
715 }
716 Ok(())
717 }
718
719 fn single(
721 &mut self,
722 number: IntegerType,
723 letters: &[String],
724 atoms: &[Atom],
725 ) -> Result<Measure> {
726 let mut measure = Measure {
727 number,
728 ..Measure::default()
729 };
730 let place = self.measures.len();
731 self.note_ending(letters, &mut measure, place)?;
732 self.last_measure_number = number;
733 self.last_letters = letters.to_vec();
734 if !self.meter_set {
735 measure
736 .items
737 .push((0.0, Held::Element(self.meter.clone().into())));
738 self.meter_set = true;
739 }
740 if !self.signature_placed
741 && let Some(signature) = &self.signature
742 {
743 measure
744 .items
745 .push((0.0, Held::Element(signature.clone().into())));
746 self.signature_placed = true;
747 }
748 self.offset = 0.0;
749 self.previous_in_measure = None;
750 self.pivot_possible = false;
751 self.key_change = false;
752 for (index, atom) in atoms.iter().enumerate() {
753 self.atom(atom, &mut measure, index + 1 == atoms.len())?;
754 }
755 if let Some(previous) = self.previous {
758 self.soundings[previous].quarter_length = self.meter.bar_quarter_length() - self.offset;
759 }
760 Ok(measure)
761 }
762
763 fn end_previous(&mut self, measure: &Measure) -> Result<()> {
766 let Some(previous) = self.previous_in_measure else {
767 return Ok(());
768 };
769 let start = measure
770 .items
771 .iter()
772 .find_map(|(offset, held)| match held {
773 Held::Sounding(index) if *index == previous => Some(*offset),
774 _ => None,
775 })
776 .unwrap_or(0.0);
777 let length = self.offset - start;
778 if length <= 0.0 {
779 return Err(roman_text_error("too many notes in this measure"));
780 }
781 self.soundings[previous].quarter_length = length;
782 Ok(())
783 }
784
785 fn atom(&mut self, atom: &Atom, measure: &mut Measure, is_last: bool) -> Result<()> {
787 let at_start = if measure.number <= 1 {
788 0.0
789 } else {
790 self.offset
791 };
792 match atom {
793 Atom::Key(word) => {
794 self.set_key(word)?;
795 measure.items.push((
796 at_start,
797 Held::Element(StreamElement::Key(self.key.clone())),
798 ));
799 self.found_signature = true;
800 }
801 Atom::AnalyticKey(word) if !self.found_signature => {
802 self.set_key(word)?;
803 measure.items.push((
804 at_start,
805 Held::Element(StreamElement::Key(self.key.clone())),
806 ));
807 self.found_signature = true;
808 }
809 Atom::AnalyticKey(word) => self.set_key(word)?,
810 Atom::KeySignature(word) => {
811 let sharps = word[2..].parse::<IntegerType>().map_err(|_| {
812 roman_text_error(format!("cannot get a key signature from {word}"))
813 })?;
814 measure
815 .items
816 .push((at_start, Held::Element(KeySignature::new(sharps).into())));
817 self.found_signature = true;
818 }
819 Atom::Beat(word) => {
820 let offset = self.meter.offset_from_beat(beat_of(word)?).unwrap_or(0.0);
821 if self.previous_in_measure.is_none()
824 && offset > 0.0
825 && let Some(made) = self.continued(offset)
826 {
827 self.previous_in_measure = Some(made);
828 measure.items.push((0.0, Held::Sounding(made)));
829 }
830 self.pivot_possible = false;
831 self.offset = offset;
832 }
833 Atom::NoChord => {
834 self.meter_at_last_chord = self.meter.clone();
835 measure.items.push((
836 self.offset,
837 Held::Element(ChordSymbol::no_chord(None).into()),
838 ));
839 if !self.pivot_possible {
840 self.end_previous(measure)?;
841 self.prefix.clear();
842 let made = self.add(Sounding {
843 sound: Sound::Rest,
844 quarter_length: 1.0,
845 lyric: None,
846 tie: None,
847 });
848 measure.items.push((self.offset, Held::Sounding(made)));
849 self.previous_in_measure = Some(made);
850 self.previous = Some(made);
851 }
852 }
853 Atom::Chord(figure) => {
854 self.meter_at_last_chord = self.meter.clone();
855 let _ = RomanNumeral::with_minor_defaults(
858 figure.as_str(),
859 self.key.clone(),
860 self.sixth,
861 self.seventh,
862 )?;
863 let follows_key_change = std::mem::take(&mut self.key_change);
864 if self.pivot_possible {
865 if let Some(previous) = self.previous_in_measure {
868 let prefix = std::mem::take(&mut self.prefix);
869 let held = &mut self.soundings[previous];
870 held.lyric = Some(format!(
871 "{}//{prefix}{figure}",
872 held.lyric.clone().unwrap_or_default()
873 ));
874 if let Sound::Numeral { pivot, .. } = &mut held.sound {
875 *pivot = Some(self.key.clone());
876 }
877 }
878 self.pivot_possible = false;
879 } else {
880 self.end_previous(measure)?;
881 let prefix = std::mem::take(&mut self.prefix);
882 let made = self.add(Sounding {
883 sound: Sound::Numeral {
884 figure: figure.clone(),
885 key: self.key.clone(),
886 sixth: self.sixth,
887 seventh: self.seventh,
888 follows_key_change,
889 pivot: None,
890 },
891 quarter_length: 1.0,
892 lyric: Some(format!("{prefix}{figure}")),
893 tie: None,
894 });
895 measure.items.push((self.offset, Held::Sounding(made)));
896 self.previous_in_measure = Some(made);
897 self.previous = Some(made);
898 self.pivot_possible = true;
899 }
900 }
901 Atom::RepeatStart | Atom::RepeatStop => {
902 let stop = *atom == Atom::RepeatStop;
903 if (stop && is_last)
904 || (stop
905 && self.offset != 0.0
906 && self.meter.bar_quarter_length() == self.offset)
907 {
908 measure.right = Some(Barline::repeat(RepeatDirection::End, None));
909 } else if self.offset == 0.0 && !stop {
910 measure.left = Some(Barline::repeat(RepeatDirection::Start, None));
911 }
912 }
913 Atom::Other => {}
914 }
915 Ok(())
916 }
917
918 fn set_key(&mut self, word: &str) -> Result<()> {
919 let (key, prefix) = key_and_prefix(word)?;
920 self.key = key;
921 self.prefix.push_str(&prefix);
922 self.key_change = true;
923 Ok(())
924 }
925
926 fn token(&mut self, token: &Token) -> Result<()> {
928 let (tag, data) = match token {
929 Token::Measure {
930 numbers,
931 letters,
932 variant,
933 data,
934 atoms,
935 } => return self.measure(numbers, letters, *variant, data, atoms),
936 Token::Tagged { tag, data } => (tag.to_lowercase(), data),
937 };
938 let text = |metadata: &mut Metadata, name: &str| {
939 metadata.add(name, MetadataValue::new(data.clone()));
940 };
941 match tag.as_str() {
942 "title" => text(&mut self.metadata, "title"),
943 "work" | "madrigal" | "piece" => text(&mut self.metadata, "alternativeTitle"),
944 "composer" => self.metadata.add_contributor("composer", data.clone()),
945 "movement" => text(&mut self.metadata, "movementNumber"),
946 "analyst" => self.metadata.add_contributor("analyst", data.clone()),
947 "proofreader" | "proof reader" => {
948 self.metadata.add_contributor("proofreader", data.clone());
949 }
950 "timesignature" | "time signature" => {
951 let meter = match data.as_str() {
952 "C" | "c" => Some(TimeSignature::common()),
953 "C|" | "Cut" | "cut" => Some(TimeSignature::cut()),
954 other => TimeSignature::from_ratio_string(other).ok(),
955 };
956 if let Some(meter) = meter {
958 self.meter = meter;
959 self.meter_set = false;
960 }
961 }
962 "keysignature" | "key signature" => {
963 let sharps = match data.as_str() {
964 "" => 0,
965 "Bb" => -1,
966 other => other.parse::<IntegerType>().map_err(|_| {
967 roman_text_error(format!("Cannot parse key signature: {other:?}"))
968 })?,
969 };
970 self.signature = Some(KeySignature::new(sharps));
971 self.signature_placed = false;
972 self.found_signature = true;
973 }
974 "sixthminor" | "sixth minor" | "seventhminor" | "seventh minor" => {
975 let sixth = tag.starts_with("sixth");
976 let reading = match data.to_lowercase().as_str() {
977 "flat" => Minor67Default::Flat,
978 "sharp" => Minor67Default::Sharp,
979 "quality" => Minor67Default::Quality,
980 "courtesy" | "cautionary" => Minor67Default::Cautionary,
981 "harmonic" if sixth => Minor67Default::Flat,
982 "harmonic" => Minor67Default::Sharp,
983 other => {
984 return Err(roman_text_error(format!(
985 "Cannot parse setting vi or vii parsing: {other:?}"
986 )));
987 }
988 };
989 if sixth {
990 self.sixth = reading;
991 } else {
992 self.seventh = reading;
993 }
994 }
995 _ => {}
996 }
997 Ok(())
998 }
999
1000 fn fix_pickup(&mut self) {
1004 let Some(first) = self.measures.iter().position(|measure| measure.number == 0) else {
1005 return;
1006 };
1007 let is_chord = |held: &Held, soundings: &[Sounding]| match held {
1008 Held::Sounding(index) => soundings[*index].is_numeral(),
1009 Held::Element(element) => matches!(element, StreamElement::ChordSymbol(_)),
1010 };
1011 let mut chords: Vec<FloatType> = self.measures[first]
1012 .items
1013 .iter()
1014 .filter(|(_, held)| is_chord(held, &self.soundings))
1015 .map(|(offset, _)| *offset)
1016 .collect();
1017 chords.sort_by(FloatType::total_cmp);
1018 let Some(padding) = chords.first().copied().filter(|offset| *offset != 0.0) else {
1019 return;
1020 };
1021 for (offset, _) in &mut self.measures[first].items {
1022 if *offset >= padding {
1023 *offset -= padding;
1024 }
1025 }
1026 self.measures[first].padding_left = padding;
1027 let last = self.measures.len() - 1;
1028 if last == first {
1029 return;
1030 }
1031 let mut sorted: Vec<&(FloatType, Held)> = self.measures[last].items.iter().collect();
1032 sorted.sort_by(|left, right| left.0.total_cmp(&right.0));
1033 let closing = sorted.into_iter().rev().find_map(|(_, held)| match held {
1034 Held::Sounding(index) if self.soundings[*index].is_numeral() => Some(Some(*index)),
1035 Held::Element(StreamElement::ChordSymbol(_)) => Some(None),
1036 _ => None,
1037 });
1038 if let Some(Some(index)) = closing
1039 && self.soundings[index].quarter_length > padding
1040 {
1041 let length = self.measure_length(&self.measures[last]);
1042 self.soundings[index].quarter_length -= length - padding;
1043 self.measures[last].padding_right = length - padding;
1044 }
1045 }
1046
1047 fn part(mut self) -> Result<(Stream, Metadata)> {
1049 self.fix_pickup();
1050
1051 let mut brackets: Vec<(Vec<usize>, u32)> = Vec::new();
1054 for (number, measures) in &self.endings {
1055 let mut run: Vec<usize> = Vec::new();
1056 let mut last: Option<IntegerType> = None;
1057 for (measure_number, place) in measures {
1058 if last.is_some_and(|last| *measure_number > last + 1) {
1059 brackets.push((std::mem::take(&mut run), *number));
1060 }
1061 run.push(*place);
1062 last = Some(*measure_number);
1063 }
1064 if !run.is_empty() {
1065 brackets.push((run, *number));
1066 }
1067 }
1068 let mut endings: Vec<Option<Ending>> = vec![None; self.measures.len()];
1069 for (places, number) in &brackets {
1070 for (index, place) in places.iter().enumerate() {
1071 if let Some(slot) = endings.get_mut(*place) {
1072 *slot = Some(Ending::new(
1073 vec![*number],
1074 index == 0,
1075 index + 1 == places.len(),
1076 ));
1077 }
1078 }
1079 if *number == 1
1080 && let Some(measure) = places
1081 .last()
1082 .and_then(|place| self.measures.get_mut(*place))
1083 && measure.right.is_none()
1084 {
1085 measure.right = Some(Barline::repeat(RepeatDirection::End, None));
1086 }
1087 }
1088
1089 let mut events = Vec::new();
1090 let mut offset = 0.0;
1091 for (measure, ending) in self.measures.iter().zip(endings) {
1092 let mut held = Vec::new();
1093 for (at, item) in &measure.items {
1094 let element = match item {
1095 Held::Sounding(index) => self.soundings[*index].element()?,
1096 Held::Element(element) => element.clone(),
1097 };
1098 held.push(StreamEvent::new(*at, element));
1099 }
1100 let mut stream =
1101 Stream::with_kind(StreamKind::Measure).with_events(sorted_events(held));
1102 stream.set_number(measure.number);
1103 stream.set_number_suffix(measure.suffix.clone());
1104 stream.set_padding_left(measure.padding_left);
1105 stream.set_padding_right(measure.padding_right);
1106 stream.set_left_barline(measure.left.clone());
1107 stream.set_right_barline(measure.right.clone());
1108 stream.set_ending(ending);
1109 let length = stream.end_offset();
1110 events.push(StreamEvent::new(offset, stream));
1111 offset = op_frac(offset + length);
1112 }
1113 let mut part = Stream::with_kind(StreamKind::Part).with_events(events);
1114 let _ = make_beams(&mut part);
1116 make_accidentals(&mut part);
1117 Ok((part, self.metadata))
1118 }
1119}
1120
1121fn score_of(tokens: &[Token]) -> Result<Stream> {
1122 let mut translator = Translator::new()?;
1123 for token in tokens {
1124 let measure = match token {
1126 Token::Measure { numbers, .. } => numbers.first().copied(),
1127 _ => None,
1128 };
1129 translator.token(token).map_err(|error| match measure {
1130 Some(number) => error.placed(|| Position::measure(number.to_string())),
1131 None => error,
1132 })?;
1133 }
1134 let (part, metadata) = translator.part()?;
1135 let mut score =
1136 Stream::with_kind(StreamKind::Score).with_events(vec![StreamEvent::new(0.0, part)]);
1137 score.set_metadata(Some(metadata));
1138 Ok(score)
1139}
1140
1141pub fn from_roman_text(text: &str) -> Result<Stream> {
1170 let tokens = tokenize(text)?;
1171 let movements = tokens.iter().filter(|token| token.is_movement()).count();
1172 if movements < 2 {
1173 return score_of(&tokens);
1174 }
1175 let mut pieces: Vec<Vec<Token>> = Vec::new();
1177 let mut current: Vec<Token> = Vec::new();
1178 for token in tokens {
1179 if token.is_movement() {
1180 pieces.push(std::mem::take(&mut current));
1181 }
1182 current.push(token);
1183 }
1184 if !current.is_empty() {
1185 pieces.push(current);
1186 }
1187 let head = pieces.remove(0);
1188 let mut events = Vec::new();
1189 let mut offset = 0.0;
1190 for piece in pieces {
1191 let tokens: Vec<Token> = head.iter().cloned().chain(piece).collect();
1192 let score = score_of(&tokens)?;
1193 let length = score.end_offset();
1194 events.push(StreamEvent::new(offset, score));
1195 offset = op_frac(offset + length);
1196 }
1197 Ok(Stream::with_kind(StreamKind::Opus).with_events(events))
1198}
1199
1200pub fn to_roman_text(stream: &Stream) -> Result<String> {
1226 let mut lines = Vec::new();
1227 write_lines(stream, &mut lines)?;
1228 Ok(lines.iter().map(|line| format!("{line}\n")).collect())
1229}
1230
1231fn write_lines(stream: &Stream, lines: &mut Vec<String>) -> Result<()> {
1233 if stream.kind() == StreamKind::Opus {
1234 for event in stream.events() {
1235 if let StreamElement::Stream(score) = event.element() {
1236 write_lines(score, lines)?;
1237 lines.push("\n".to_string());
1238 }
1239 }
1240 return Ok(());
1241 }
1242 let wrapped;
1243 let container: &Stream = match stream.kind() {
1244 StreamKind::Score => stream.parts().first().copied().unwrap_or(stream),
1245 StreamKind::Part | StreamKind::PartStaff => stream,
1246 StreamKind::Measure => {
1247 let mut part = Stream::with_kind(StreamKind::Part);
1248 part.insert(0.0, stream.clone());
1249 wrapped = part;
1250 &wrapped
1251 }
1252 _ => {
1253 let mut measure = Stream::with_kind(StreamKind::Measure);
1255 for event in stream.events() {
1256 measure.push(event.element().clone());
1257 }
1258 let mut part = Stream::with_kind(StreamKind::Part);
1259 part.insert(0.0, measure);
1260 wrapped = part;
1261 &wrapped
1262 }
1263 };
1264
1265 let mut composer = "Composer unknown".to_string();
1266 let mut title = "Title unknown".to_string();
1267 let mut analyst = String::new();
1268 let mut proofreader = String::new();
1269 if let Some(metadata) = stream.metadata() {
1270 if let Some(prepared) = written_title(metadata) {
1271 title = prepared;
1272 }
1273 if let Some(name) = contributor(metadata, "composer") {
1274 composer = name;
1275 }
1276 if let Some(name) = contributor(metadata, "analyst") {
1277 analyst = name;
1278 }
1279 if let Some(name) = contributor(metadata, "proofreader") {
1280 proofreader = name;
1281 }
1282 }
1283 lines.push(format!("Composer: {composer}"));
1284 lines.push(format!("Title: {title}"));
1285 lines.push(format!("Analyst: {analyst}"));
1286 lines.push(format!("Proofreader: {proofreader}"));
1287 lines.push(String::new());
1288
1289 let mut meter = container
1292 .events()
1293 .iter()
1294 .find_map(|event| match event.element() {
1295 StreamElement::TimeSignature(meter) => Some(meter.clone()),
1296 _ => None,
1297 });
1298 let any_meter = container
1299 .leaves()
1300 .iter()
1301 .any(|(_, element)| matches!(element, StreamElement::TimeSignature(_)));
1302 if !any_meter {
1303 meter = Some(TimeSignature::new(4, 4)?);
1304 }
1305
1306 let mut key_string = String::new();
1307 for measure in container.measures() {
1308 let meters: Vec<(FloatType, &TimeSignature)> = measure
1309 .events()
1310 .iter()
1311 .filter_map(|event| match event.element() {
1312 StreamElement::TimeSignature(meter) => Some((event.offset(), meter)),
1313 _ => None,
1314 })
1315 .collect();
1316 if let Some((_, first)) = meters.first() {
1317 lines.push(format!("Time Signature: {}", first.ratio_string()));
1318 if meters.len() > 1 {
1319 let rest: Vec<String> = meters[1..]
1320 .iter()
1321 .map(|(_, meter)| format!("'{}'", meter.ratio_string()))
1322 .collect();
1323 lines.push(format!(
1324 "Note: further time signature change(s) unprocessed: [{}]",
1325 rest.join(", ")
1326 ));
1327 }
1328 }
1329 let number = format!(
1330 "{}{}",
1331 measure.number(),
1332 measure.number_suffix().unwrap_or("")
1333 );
1334 let mut line = String::new();
1335 if measure
1336 .left_barline()
1337 .is_some_and(|barline| barline.repeat_direction() == Some(RepeatDirection::Start))
1338 {
1339 push_chord_string(&mut line, &number, 1.0, "||:");
1340 }
1341 let mut last_beat = None;
1342 for event in measure.events() {
1343 let StreamElement::Chord(chord) = event.element() else {
1344 continue;
1345 };
1346 let Some(numeral) = chord.numeral() else {
1347 continue;
1348 };
1349 let at = event.offset();
1350 let in_force = meters
1353 .iter()
1354 .rev()
1355 .find(|(offset, _)| *offset <= at)
1356 .map(|(_, meter)| (*meter).clone())
1357 .or_else(|| meter.clone())
1358 .ok_or_else(|| roman_text_error("a roman numeral with no meter in force"))?;
1359 let beat = in_force.beat_proportion(op_frac(at + measure.padding_left()))?;
1360 last_beat = Some(beat);
1361 if chord
1362 .tie()
1363 .is_some_and(|tie| tie.tie_type() != TieType::Start)
1364 {
1365 continue;
1366 }
1367 let key = numeral.key().tonic_pitch_name_with_case().replace('-', "b");
1368 let written = if key == key_string {
1369 numeral.figure().to_string()
1370 } else {
1371 key_string = key.clone();
1372 format!("{key}: {}", numeral.figure())
1373 };
1374 push_chord_string(&mut line, &number, beat, &written);
1375 }
1376 if measure
1377 .right_barline()
1378 .is_some_and(|barline| barline.repeat_direction() == Some(RepeatDirection::End))
1379 {
1380 push_chord_string(&mut line, &number, last_beat.unwrap_or(1.0), ":||");
1381 }
1382 if !line.is_empty() {
1383 lines.push(line);
1384 }
1385 if let Some((_, last)) = meters.last() {
1386 meter = Some((*last).clone());
1387 }
1388 }
1389 Ok(())
1390}
1391
1392fn push_chord_string(line: &mut String, number: &str, beat: FloatType, chord: &str) {
1395 if line.is_empty() {
1396 line.push('m');
1397 line.push_str(number);
1398 }
1399 let beat = int_beat(beat);
1400 if beat == "1" {
1401 line.push_str(&format!(" {chord}"));
1402 } else {
1403 line.push_str(&format!(" b{beat} {chord}"));
1404 }
1405}
1406
1407fn int_beat(beat: FloatType) -> String {
1410 if beat.fract() == 0.0 {
1411 return format!("{}", beat as i64);
1412 }
1413 let rounded = (beat * 100.0).round() / 100.0;
1414 let written = format!("{rounded}");
1415 if written.contains('.') {
1416 written
1417 } else {
1418 format!("{written}.0")
1419 }
1420}
1421
1422fn written_title(metadata: &Metadata) -> Option<String> {
1425 let mut parts = Vec::new();
1426 if let Some(title) = ["title", "popularTitle", "alternativeTitle", "movementName"]
1427 .iter()
1428 .find_map(|name| metadata.first_text(name))
1429 {
1430 parts.push(title.to_string());
1431 }
1432 if let Some(number) = metadata.first_text("movementNumber") {
1433 parts.push(format!("- No.{number}:"));
1434 }
1435 if let Some(name) = metadata.first_text("movementName")
1436 && metadata.first_text("title") != Some(name)
1437 {
1438 parts.push(name.to_string());
1439 }
1440 (!parts.is_empty()).then(|| parts.join(" "))
1441}
1442
1443fn contributor(metadata: &Metadata, role: &str) -> Option<String> {
1446 metadata
1447 .first_text(role)
1448 .or_else(|| {
1449 metadata
1450 .get("otherContributor")
1451 .iter()
1452 .find(|value| value.role() == Some(role))
1453 .map(MetadataValue::text)
1454 })
1455 .filter(|name| !name.is_empty())
1456 .map(str::to_string)
1457}
1458
1459#[cfg(test)]
1460mod tests {
1461 use super::*;
1462
1463 fn lengths(measure: &Stream) -> Vec<(FloatType, FloatType)> {
1464 measure
1465 .events()
1466 .iter()
1467 .filter(|event| event.element().is_note_or_chord())
1468 .map(|event| (event.offset(), event.element().quarter_length()))
1469 .collect()
1470 }
1471
1472 #[test]
1473 fn words_of_a_measure_are_told_apart() {
1474 assert_eq!(
1475 atoms_of("C: I b2.5 V6 || Bb;: KS-2 NC ||: :|| ?(d: ii"),
1476 [
1477 Atom::AnalyticKey("C:".to_string()),
1478 Atom::Chord("I".to_string()),
1479 Atom::Beat("b2.5".to_string()),
1480 Atom::Chord("V6".to_string()),
1481 Atom::Other,
1482 Atom::Key("Bb;:".to_string()),
1483 Atom::KeySignature("KS-2".to_string()),
1484 Atom::NoChord,
1485 Atom::RepeatStart,
1486 Atom::RepeatStop,
1487 Atom::Other,
1488 Atom::Chord("ii".to_string()),
1489 ]
1490 );
1491 }
1492
1493 #[test]
1494 fn a_beat_may_be_a_third_written_to_two_places() {
1495 assert_eq!(beat_of("b2").unwrap(), 2.0);
1496 assert_eq!(beat_of("b2.5").unwrap(), 2.5);
1497 assert!((beat_of("b1.66").unwrap() - 5.0 / 3.0).abs() < 1e-9);
1498 assert!((beat_of("b1.66.5").unwrap() - 11.0 / 6.0).abs() < 1e-9);
1499 }
1500
1501 #[test]
1502 fn a_chord_lasts_until_the_next_or_the_barline() {
1503 let score = from_roman_text("Time Signature: 4/4\nm1 C: I b3 V\nm2 I\n").unwrap();
1504 let part = score.parts()[0];
1505 let measures = part.measures();
1506 assert_eq!(lengths(measures[0]), [(0.0, 2.0), (2.0, 2.0)]);
1507 assert_eq!(lengths(measures[1]), [(0.0, 4.0)]);
1508 }
1509
1510 #[test]
1511 fn a_measure_opening_late_holds_the_last_chord_tied() {
1512 let score = from_roman_text("m1 C: I\nm2 b3 V\n").unwrap();
1513 let part = score.parts()[0];
1514 let second = part.measures()[1];
1515 assert_eq!(lengths(second), [(0.0, 2.0), (2.0, 2.0)]);
1516 let StreamElement::Chord(held) = second.events()[0].element() else {
1517 panic!("the measure opens with the chord held over");
1518 };
1519 assert_eq!(held.tie().map(Tie::tie_type), Some(TieType::Stop));
1520 }
1521
1522 #[test]
1523 fn a_measure_may_copy_another() {
1524 let score = from_roman_text("m1 C: I b3 V\nm2 IV\nm3 = m1\n").unwrap();
1525 let part = score.parts()[0];
1526 let measures = part.measures();
1527 assert_eq!(measures.len(), 3);
1528 assert_eq!(measures[2].number(), 3);
1529 assert_eq!(measures[2].pitches(), measures[0].pitches());
1530 }
1531
1532 #[test]
1533 fn text_with_no_measure_is_refused() {
1534 assert!(from_roman_text("Composer: nobody\n").is_err());
1535 }
1536
1537 #[test]
1538 fn a_measure_after_the_largest_number_is_read() {
1539 assert!(from_roman_text("m2147483647 C: I\nm1 I\n").is_ok());
1540 }
1541
1542 #[test]
1543 fn a_refusal_says_its_measure_or_line() {
1544 let error = from_roman_text("m1 C: I\nm2 V\nm3 KS1x I\n").unwrap_err();
1545 assert_eq!(error.position(), Some(&Position::measure("3")));
1546
1547 let error = from_roman_text("Title: x\nm1 C: I\n\nm99999999999 V\n").unwrap_err();
1548 assert_eq!(
1549 error.to_string(),
1550 "RomanText error: cannot read the measure number in \"m99999999999\""
1551 );
1552 assert_eq!(error.position(), Some(&Position::line(4)));
1553 }
1554
1555 #[test]
1556 fn a_gap_of_more_measures_than_a_stream_takes_is_refused() {
1557 let gap = crate::makenotation::MAX_MEASURES + 2;
1558 let refused = from_roman_text(&format!("m1 C: I\nm{gap} V\n")).unwrap_err();
1559 let said = refused.format_error().unwrap();
1560 assert_eq!(said.kind, FormatErrorKind::TooLarge);
1561 assert_eq!(said.position, Some(Position::measure(gap.to_string())));
1562 assert!(from_roman_text("m1 C: I\nm1000 V\n").is_ok());
1563 }
1564}