1use crate::articulations::{Articulation, ArticulationKind};
11use crate::bar::{Barline, BarlineType, RepeatDirection};
12use crate::chord::Chord;
13use crate::clef::{Clef, ClefKind};
14use crate::defaults::{FloatType, IntegerType};
15use crate::duration::Duration;
16use crate::dynamics::Dynamic;
17use crate::error::{Error, Result};
18use crate::expressions::{Expression, Fermata, Ornament, OrnamentKind};
19use crate::interval::Interval;
20use crate::key::KeySignature;
21use crate::makenotation::{make_accidentals, make_beams, op_frac};
22use crate::metadata::Metadata;
23use crate::meter::TimeSignature;
24use crate::notation::{BeamDirection, BeamType, Beams, Tie, TieType};
25use crate::note::Note;
26use crate::pitch::Pitch;
27use crate::pitch::accidental::Accidental;
28use crate::rest::Rest;
29use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
30use crate::tempo::TempoText;
31
32fn error(message: impl Into<String>) -> Error {
33 Error::MuseData(message.into())
34}
35
36pub fn from_musedata(files: &[&str]) -> Result<Stream> {
75 let mut parts = Vec::new();
76 for text in files {
77 parts.extend(split_parts(text)?);
78 }
79 let first = parts
80 .first()
81 .ok_or_else(|| error("a MuseData work with no parts"))?;
82 let mut metadata = Metadata::new();
83 if let Some(title) = first.work_title() {
84 metadata.add_text("title", title);
85 }
86 if let Some(number) = first.movement_number() {
87 metadata.add_text("movementNumber", number);
88 }
89 if let Some(name) = first.movement_title() {
90 metadata.add_text("movementName", name);
91 }
92 if let Some(number) = first.work_number() {
93 metadata.add_text("number", number);
94 }
95 let mut score = Stream::with_kind(StreamKind::Score);
96 score.set_metadata(Some(metadata));
97 for part in &parts {
98 score.insert(0.0, part.to_part()?);
99 }
100 Ok(score)
101}
102
103fn split_parts(text: &str) -> Result<Vec<Part>> {
106 let mut parts = Vec::new();
107 let mut lines: Vec<String> = Vec::new();
108 let mut comment = false;
109 for line in text.split('\n') {
110 if line.starts_with('&') {
111 comment = !comment;
112 continue;
113 }
114 if comment || line.starts_with('@') {
115 continue;
116 }
117 if line.starts_with("/END") || line.starts_with("END") {
118 if lines.len() <= 1 {
119 lines.clear();
120 continue;
121 }
122 parts.push(Part::new(std::mem::take(&mut lines))?);
123 } else if !line.starts_with("/eof") {
124 lines.push(line.to_string());
125 }
126 }
127 Ok(parts)
128}
129
130fn digits(text: &str) -> String {
132 text.chars().filter(char::is_ascii_digit).collect()
133}
134
135struct Part {
137 lines: Vec<String>,
138 stage: u8,
139}
140
141impl Part {
142 fn new(lines: Vec<String>) -> Result<Self> {
145 if lines.iter().any(|line| line.starts_with('$')) {
146 return Ok(Self { lines, stage: 2 });
147 }
148 if lines.len() <= 1 {
149 return Err(error("cannot scrub empty source"));
150 }
151 let lines = lines
152 .into_iter()
153 .skip_while(|line| line.trim().is_empty())
154 .collect();
155 Ok(Self { lines, stage: 1 })
156 }
157
158 fn line(&self, index: usize) -> Result<&str> {
159 self.lines
160 .get(index)
161 .map(String::as_str)
162 .ok_or_else(|| error("a header shorter than MuseData's"))
163 }
164
165 fn digits_after(line: &str, tag: &str) -> String {
168 let Some(at) = line.find(tag) else {
169 return String::new();
170 };
171 let mut out = String::new();
172 for c in line[at + tag.len()..].chars() {
173 if c.is_ascii_digit() || c == '-' || c == '/' {
174 out.push(c);
175 } else if !c.is_whitespace() {
176 break;
177 }
178 }
179 out
180 }
181
182 fn words_after(line: &str, tag: &str) -> String {
185 let Some(at) = line.find(tag) else {
186 return String::new();
187 };
188 line[at + tag.len()..]
189 .chars()
190 .take_while(|c| c.is_alphabetic() || c.is_whitespace() || *c == ',')
191 .collect()
192 }
193
194 fn work_number(&self) -> Option<String> {
195 if self.stage == 1 {
196 let data: String = self.lines.get(1)?.chars().take(6).collect();
197 let data = data.trim();
198 Some(if data.contains(',') {
199 data.split(',').nth(1).unwrap_or("").to_string()
200 } else {
201 data.to_string()
202 })
203 } else {
204 Some(Self::digits_after(self.lines.get(4)?, "WK#:"))
205 }
206 }
207
208 fn movement_number(&self) -> Option<String> {
209 if self.stage == 1 {
210 Some(
211 self.lines
212 .get(1)?
213 .chars()
214 .skip(6)
215 .collect::<String>()
216 .trim()
217 .to_string(),
218 )
219 } else {
220 Some(Self::digits_after(self.lines.get(4)?, "MV#:"))
221 }
222 }
223
224 fn work_title(&self) -> Option<String> {
225 if self.stage == 1 {
226 Some(self.lines.first()?.trim().to_string())
227 } else {
228 self.lines.get(6).cloned()
229 }
230 }
231
232 fn movement_title(&self) -> Option<String> {
233 if self.stage == 1 {
234 None
235 } else {
236 self.lines.get(7).cloned()
237 }
238 }
239
240 fn part_name(&self) -> Option<String> {
241 if self.stage == 1 {
242 None
243 } else {
244 self.lines.get(8).map(|name| name.trim().to_string())
245 }
246 }
247
248 fn attributes(&self) -> Result<String> {
250 if self.stage == 1 {
251 return Ok(format!("{} {}", self.line(6)?.trim(), self.line(7)?.trim()));
252 }
253 self.lines
254 .iter()
255 .skip(11)
256 .find(|line| line.starts_with('$'))
257 .cloned()
258 .ok_or_else(|| error("a part with no attribute record"))
259 }
260
261 fn stage_one_field(&self, index: usize) -> Result<String> {
263 self.attributes()?
264 .split(' ')
265 .nth(index)
266 .map(str::to_string)
267 .ok_or_else(|| error("a stage-1 attribute record too short"))
268 }
269
270 fn integer(text: &str) -> Result<IntegerType> {
271 text.trim()
272 .parse()
273 .map_err(|_| error(format!("{text:?} is not a number")))
274 }
275
276 fn key_signature(&self) -> Result<KeySignature> {
277 let sharps = if self.stage == 1 {
278 Self::integer(&self.stage_one_field(1)?)?
279 } else {
280 Self::integer(&Self::digits_after(&self.attributes()?, "K:"))?
281 };
282 Ok(KeySignature::new(sharps))
283 }
284
285 fn time_signature(&self) -> Result<TimeSignature> {
286 let (numerator, denominator) = if self.stage == 1 {
287 (
288 Self::integer(&self.stage_one_field(4)?)?,
289 Self::integer(&self.stage_one_field(5)?)?,
290 )
291 } else {
292 let written = Self::digits_after(&self.attributes()?, "T:");
293 let (numerator, denominator) = written
294 .split_once('/')
295 .filter(|(_, denominator)| !denominator.contains('/'))
296 .ok_or_else(|| error(format!("no meter in {written:?}")))?;
297 (Self::integer(numerator)?, Self::integer(denominator)?)
298 };
299 let ratio = if (numerator == 1 && denominator == 1) || denominator == 0 {
300 "4/4".to_string()
301 } else {
302 format!("{numerator}/{denominator}")
303 };
304 TimeSignature::from_ratio_string(&ratio)
305 }
306
307 fn clef(&self) -> Result<Option<Clef>> {
308 if self.stage == 1 {
309 return Ok(None);
310 }
311 let line = self.attributes()?;
312 let mut written = Self::digits_after(&line, "C:");
313 if written.is_empty() {
314 let staves = match Self::digits_after(&line, "S:").as_str() {
315 "" => 1,
316 raw => Self::integer(raw)?,
317 };
318 written = (1..=staves)
319 .map(|staff| Self::digits_after(&line, &format!("C{staff}:")))
320 .find(|raw| !raw.is_empty())
321 .ok_or_else(|| error("a part with no clef"))?;
322 }
323 let kind = match Self::integer(&written)?.to_string().as_str() {
324 "5" => ClefKind::FrenchViolinClef,
325 "4" => ClefKind::TrebleClef,
326 "34" => ClefKind::Treble8vbClef,
327 "64" => ClefKind::Treble8vaClef,
328 "3" => ClefKind::GSopranoClef,
329 "11" => ClefKind::CBaritoneClef,
330 "12" => ClefKind::TenorClef,
331 "13" => ClefKind::AltoClef,
332 "14" => ClefKind::MezzoSopranoClef,
333 "15" => ClefKind::SopranoClef,
334 "22" => ClefKind::BassClef,
335 "23" => ClefKind::FBaritoneClef,
336 "52" => ClefKind::Bass8vbClef,
337 "82" => ClefKind::Bass8vaClef,
338 other => return Err(error(format!("cannot determine clef from: {other}"))),
339 };
340 Ok(Some(Clef::of_kind(kind)))
341 }
342
343 fn directive(&self) -> Result<Option<String>> {
344 if self.stage == 1 {
345 return Ok(None);
346 }
347 let words = Self::words_after(&self.attributes()?, "D:");
348 Ok((!words.is_empty()).then(|| words.trim().to_string()))
349 }
350
351 fn transposition(&self) -> Result<Option<Interval>> {
353 if self.stage == 1 {
354 return Ok(None);
355 }
356 match Self::digits_after(&self.attributes()?, "X:").as_str() {
357 "" => Ok(None),
358 raw => base40_interval(Self::integer(raw)?).map(Some),
359 }
360 }
361
362 fn divisions(&self) -> Result<FloatType> {
365 if self.stage == 1 {
366 let bar = Self::integer(&self.stage_one_field(2)?)?;
367 Ok(FloatType::from(bar) / self.time_signature()?.bar_duration().quarter_length())
368 } else {
369 Ok(FloatType::from(Self::integer(&Self::digits_after(
370 &self.attributes()?,
371 "Q:",
372 ))?))
373 }
374 }
375
376 fn measure_bounds(&self) -> Result<Vec<(usize, usize)>> {
379 let lines = &self.lines;
380 let end = lines.len();
381 let mut marks = Vec::new();
382 let mut first_after: Option<usize> = None;
383 let mut attributes = 0;
384 for (index, line) in lines.iter().enumerate() {
385 if self.stage == 2 {
386 if line.starts_with('$') {
387 attributes += 1;
388 continue;
389 }
390 if attributes > 0 && first_after.is_none() {
391 first_after = Some(index);
392 }
393 }
394 if line.starts_with('m') {
395 marks.push(index);
396 }
397 }
398 let first_mark = *marks
399 .first()
400 .ok_or_else(|| error("a part with no measure records"))?;
401 if self.stage == 1 {
402 first_after = Some(first_mark);
403 }
404 let mut bounds = Vec::new();
405 let start = if Some(first_mark) == first_after {
406 if marks.len() == 1 {
407 bounds.push((first_mark, end));
408 None
409 } else {
410 bounds.push((first_mark, marks[1]));
411 Some(1)
412 }
413 } else {
414 let first_after =
415 first_after.ok_or_else(|| error("a part with nothing after its attributes"))?;
416 bounds.push((first_after, first_mark));
417 Some(0)
418 };
419 if let Some(start) = start {
420 for index in start..marks.len() {
421 bounds.push((marks[index], marks.get(index + 1).copied().unwrap_or(end)));
422 }
423 }
424 Ok(bounds)
425 }
426
427 fn to_part(&self) -> Result<Stream> {
429 let mut part = Stream::with_kind(StreamKind::Part);
430 let name = self.part_name();
431 part.set_id(name.clone());
432 part.set_name(name);
433 let divisions = self.divisions()?;
434 let bounds = self.measure_bounds()?;
435 let measures: Vec<&[String]> = bounds
436 .iter()
437 .map(|(start, end)| self.lines.get(*start..*end).unwrap_or(&[]))
438 .collect();
439 let mut previous: Option<Option<TieType>> = None;
440 let mut bars = 0;
441 for (index, lines) in measures.iter().enumerate() {
442 if !has_notes(lines) {
443 continue;
444 }
445 let mut measure = measure_object(lines)?;
446 if let Some(next) = measures.get(index + 1)
447 && !has_notes(next)
448 {
449 measure.set_right_barline(Some(bar_object(next)?));
450 }
451 if bars == 0 {
452 if let Some(clef) = self.clef()? {
453 append_at(&mut measure, 0.0, StreamElement::Clef(clef));
454 }
455 append_at(
456 &mut measure,
457 0.0,
458 StreamElement::TimeSignature(self.time_signature()?),
459 );
460 append_at(
461 &mut measure,
462 0.0,
463 StreamElement::KeySignature(self.key_signature()?),
464 );
465 if let Some(directive) = self.directive()? {
466 let words = TempoText::new(directive);
467 if words.is_common_tempo_text() {
468 append_at(
469 &mut measure,
470 0.0,
471 StreamElement::MetronomeMark(words.metronome_mark()),
472 );
473 }
474 }
475 }
476 self.fill(&mut measure, lines, divisions, &mut previous)?;
477 if bars == 0 {
478 let bar = self.time_signature()?.bar_duration().quarter_length();
479 let length = measure.end_offset();
480 if length / bar < 1.0 {
481 measure.set_padding_left(bar - length);
482 }
483 }
484 part.push(measure);
485 bars += 1;
486 }
487 if let Some(interval) = self.transposition()? {
488 let mut failure = None;
489 part.for_each_mut(&mut |_, element| {
490 let moved = match element {
491 StreamElement::Note(note) => note.transpose(&interval).map(StreamElement::Note),
492 StreamElement::Chord(chord) => {
493 chord.transpose(&interval).map(StreamElement::Chord)
494 }
495 StreamElement::KeySignature(signature) => signature
496 .transpose(&interval)
497 .map(StreamElement::KeySignature),
498 _ => return,
499 };
500 match moved {
501 Ok(moved) => *element = moved,
502 Err(error) => failure = Some(error),
503 }
504 });
505 if let Some(error) = failure {
506 return Err(error);
507 }
508 make_accidentals(&mut part);
509 }
510 if self.stage == 1 {
511 let clef = Clef::best_for(&part.pitches(), false);
513 if let Some(StreamElement::Stream(first)) = part
514 .events_mut()
515 .iter_mut()
516 .map(StreamEvent::element_mut)
517 .find(|element| {
518 matches!(element, StreamElement::Stream(inner) if inner.kind() == StreamKind::Measure)
519 })
520 {
521 append_at(first, 0.0, StreamElement::Clef(clef));
522 }
523 let _ = make_beams(&mut part);
525 make_accidentals(&mut part);
526 }
527 Ok(part)
528 }
529
530 fn fill(
533 &self,
534 measure: &mut Stream,
535 lines: &[String],
536 divisions: FloatType,
537 previous: &mut Option<Option<TieType>>,
538 ) -> Result<()> {
539 let voices = lines.iter().any(|line| line.starts_with("back"));
540 let mut voice = voices.then(|| Stream::with_kind(StreamKind::Voice));
541 let mut pending: Vec<Record<'_>> = Vec::new();
542 for line in lines {
543 let record = Record {
544 line,
545 stage: self.stage,
546 };
547 if record.is_back() {
548 self.flush(&mut pending, measure, voice.as_mut(), divisions, previous)?;
549 if let Some(done) = voice.replace(Stream::with_kind(StreamKind::Voice)) {
550 append_at(measure, 0.0, StreamElement::Stream(Box::new(done)));
551 }
552 }
553 if record.is_rest() {
554 self.flush(&mut pending, measure, voice.as_mut(), divisions, previous)?;
555 let rest = Rest::new(Duration::new(record.quarter_length(divisions)?)?);
556 let target = voice.as_mut().unwrap_or(&mut *measure);
557 append(target, StreamElement::Rest(rest));
558 *previous = Some(None);
559 } else if record.is_chord() {
560 pending.push(record);
561 } else if record.is_note() {
562 self.flush(&mut pending, measure, voice.as_mut(), divisions, previous)?;
563 pending.push(record);
564 }
565 }
566 self.flush(&mut pending, measure, voice.as_mut(), divisions, previous)?;
567 if let Some(voice) = voice
568 && !voice.is_empty()
569 {
570 append_at(measure, 0.0, StreamElement::Stream(Box::new(voice)));
571 }
572 Ok(())
573 }
574
575 fn flush(
578 &self,
579 pending: &mut Vec<Record<'_>>,
580 measure: &mut Stream,
581 voice: Option<&mut Stream>,
582 divisions: FloatType,
583 previous: &mut Option<Option<TieType>>,
584 ) -> Result<()> {
585 if pending.is_empty() {
586 return Ok(());
587 }
588 let records = std::mem::take(pending);
589 let (element, dynamics, tie) = note_or_chord(&records, divisions, *previous)?;
590 *previous = Some(tie);
591 let target = voice.unwrap_or(measure);
592 let at = target.end_offset();
593 append_at(target, at, element);
594 for dynamic in dynamics {
595 append_at(target, at, StreamElement::Dynamic(dynamic));
596 }
597 Ok(())
598 }
599}
600
601fn append(stream: &mut Stream, element: StreamElement) {
604 let at = stream.end_offset();
605 append_at(stream, at, element);
606}
607
608fn append_at(stream: &mut Stream, offset: FloatType, element: StreamElement) {
610 stream.insert_sorted(vec![StreamEvent::new(offset, element)]);
611}
612
613fn has_notes(lines: &[String]) -> bool {
615 lines.iter().any(|line| {
616 line.chars()
617 .next()
618 .is_some_and(|c| "ABCDEFGrgc".contains(c))
619 })
620}
621
622fn measure_line(lines: &[String]) -> Result<String> {
625 let line = lines
626 .first()
627 .ok_or_else(|| error("a measure with no records"))?
628 .trim()
629 .to_string();
630 Ok(if line.starts_with('m') {
631 line
632 } else {
633 "measure".to_string()
634 })
635}
636
637fn bar_object(lines: &[String]) -> Result<Barline> {
639 let line = measure_line(lines)?;
640 let kind: String = line.chars().skip(1).take(6).collect();
641 let bar_type = match kind.as_str() {
642 "easure" => BarlineType::Regular,
643 "dotted" => BarlineType::Dotted,
644 "double" => BarlineType::Double,
645 "heavy1" | "heavy" => BarlineType::Heavy,
646 "heavy2" => BarlineType::Final,
647 "heavy3" => BarlineType::HeavyLight,
648 "heavy4" | "heave4" => BarlineType::HeavyHeavy,
649 other => {
650 return Err(error(format!(
651 "cannot process bar data definition: {other}"
652 )));
653 }
654 };
655 let flag: String = line.chars().skip(16).collect();
656 let flag = flag.trim();
657 let mut barline = Barline::new(bar_type);
658 if line.chars().count() > 16 && !flag.is_empty() {
659 let direction = if flag.contains(":|") {
660 Some(RepeatDirection::End)
661 } else if flag.contains("|:") {
662 Some(RepeatDirection::Start)
663 } else {
664 None
665 };
666 if let Some(direction) = direction {
667 barline = Barline::repeat(direction, None);
668 barline.set_bar_type(bar_type);
669 }
670 }
671 Ok(barline)
672}
673
674fn measure_object(lines: &[String]) -> Result<Stream> {
677 let line = measure_line(lines)?;
678 let rest: String = line.chars().skip(8).collect();
679 let number = if line.chars().count() >= 9 && !rest.trim().is_empty() {
680 digits(&line)
681 } else {
682 "1".to_string()
683 };
684 let mut measure = Stream::with_kind(StreamKind::Measure);
685 measure.set_left_barline(Some(bar_object(lines)?));
686 if !number.is_empty() {
687 measure.set_number(
688 number
689 .parse()
690 .map_err(|_| error(format!("measure number {number}")))?,
691 );
692 }
693 Ok(measure)
694}
695
696#[derive(Clone, Copy)]
698struct Record<'a> {
699 line: &'a str,
700 stage: u8,
701}
702
703impl Record<'_> {
704 fn first(&self) -> Option<char> {
705 self.line.chars().next()
706 }
707
708 fn at(&self, column: usize) -> Option<char> {
710 self.line.chars().nth(column)
711 }
712
713 fn columns(&self, from: usize, to: usize) -> String {
715 self.line
716 .chars()
717 .skip(from)
718 .take(to.saturating_sub(from))
719 .collect()
720 }
721
722 fn is_rest(&self) -> bool {
723 self.first() == Some('r')
724 }
725
726 fn is_note(&self) -> bool {
727 self.first().is_some_and(|c| "ABCDEFG".contains(c))
728 }
729
730 fn is_chord(&self) -> bool {
731 self.first() == Some(' ') && self.at(1).is_some_and(|c| "ABCDEFG".contains(c))
732 }
733
734 fn is_back(&self) -> bool {
735 self.line.starts_with("back")
736 }
737
738 fn is_tied(&self) -> bool {
739 let column = if self.stage == 1 { 7 } else { 8 };
740 self.at(column) == Some('-')
741 }
742
743 fn pitch(&self) -> Result<Pitch> {
745 let token = if self.is_note() {
746 self.line
747 } else {
748 self.line.get(1..).unwrap_or("")
749 };
750 let data = token.split_whitespace().next().unwrap_or("");
751 let step = data
752 .chars()
753 .next()
754 .ok_or_else(|| error("a note with no pitch"))?;
755 let accidental = if data.contains('#') {
757 "#"
758 } else if data.contains('f') {
759 "-"
760 } else {
761 ""
762 };
763 let mut pitch = Pitch::from_name(format!("{step}{accidental}{}", digits(data)))?;
764 if self.stage == 1 {
765 return Ok(pitch);
766 }
767 if let Some(accidental) = pitch.written_accidental_mut() {
768 accidental.set_display_status(Some(false));
769 }
770 if let Some(shown) = self.shown_accidental()? {
771 pitch.set_accidental(shown);
772 if let Some(accidental) = pitch.written_accidental_mut() {
773 accidental.set_display_type("always")?;
774 accidental.set_display_status(Some(true));
775 }
776 }
777 if self
778 .notations()
779 .is_some_and(|notations| notations.contains('+'))
780 && let Some(accidental) = pitch.written_accidental_mut()
781 {
782 accidental.set_display_status(Some(true));
783 }
784 Ok(pitch)
785 }
786
787 fn shown_accidental(&self) -> Result<Option<Accidental>> {
789 if self.line.chars().count() <= 18 {
790 return Ok(None);
791 }
792 let name = match self.at(18) {
793 Some('#' | 'S') => "sharp",
794 Some('n') => "natural",
795 Some('f' | 'F') => "flat",
796 Some('x' | 'X') => "double-sharp",
797 Some('&') => "double-flat",
798 _ => return Ok(None),
799 };
800 Accidental::new(name).map(Some)
801 }
802
803 fn quarter_length(&self, divisions: FloatType) -> Result<FloatType> {
805 let mut count = if self.stage == 1 {
806 Part::integer(&self.columns(5, 7))?
807 } else {
808 Part::integer(&self.columns(5, 8))?
809 };
810 if let Some(blank) = self.at(4)
811 && blank != ' '
812 {
813 let hundreds = blank.to_digit(10).ok_or_else(|| {
814 error(format!(
815 "Error in parsing: {}\n Column 5 must be blank.",
816 self.line
817 ))
818 })?;
819 count += 100 * IntegerType::try_from(hundreds).unwrap_or(0);
820 }
821 Ok(op_frac(FloatType::from(count) / divisions))
822 }
823
824 fn lyrics(&self) -> Option<Vec<String>> {
825 if self.stage == 1 || self.line.chars().count() < 44 {
826 return None;
827 }
828 Some(
829 self.line
830 .chars()
831 .skip(43)
832 .collect::<String>()
833 .split('|')
834 .map(|lyric| lyric.trim().to_string())
835 .collect(),
836 )
837 }
838
839 fn beams(&self) -> Option<String> {
840 if self.stage == 1 {
841 return None;
842 }
843 let data = self.columns(25, 31);
844 (!data.is_empty()).then(|| data.trim_end().to_string())
845 }
846
847 fn notations(&self) -> Option<String> {
849 if self.line.chars().count() < 31 {
850 return None;
851 }
852 Some(self.columns(31, 43).trim().to_string())
853 }
854
855 fn articulations(&self) -> Vec<Articulation> {
856 let Some(notations) = self.notations() else {
857 return Vec::new();
858 };
859 notations
860 .chars()
861 .filter_map(|c| {
862 let kind = match c {
863 'A' | 'V' => ArticulationKind::StrongAccent,
864 '>' => ArticulationKind::Accent,
865 '.' => ArticulationKind::Staccato,
866 '_' => ArticulationKind::Tenuto,
867 '=' => ArticulationKind::DetachedLegato,
868 'i' => ArticulationKind::Spiccato,
869 ',' => ArticulationKind::BreathMark,
870 _ => return None,
871 };
872 Some(Articulation::of_kind(kind))
873 })
874 .collect()
875 }
876
877 fn expressions(&self) -> Vec<Expression> {
878 let Some(notations) = self.notations() else {
879 return Vec::new();
880 };
881 notations
882 .chars()
883 .filter_map(|c| match c {
884 'F' | 'E' => Some(Expression::from(Fermata::new())),
885 't' => Some(Expression::from(Ornament::of_kind(OrnamentKind::Trill))),
886 'r' => Some(Expression::from(Ornament::of_kind(OrnamentKind::Turn))),
887 'M' => Some(Expression::from(Ornament::of_kind(OrnamentKind::Mordent))),
888 _ => None,
889 })
890 .collect()
891 }
892
893 fn dynamics(&self) -> Vec<Dynamic> {
896 let Some(mut data) = self.notations() else {
897 return Vec::new();
898 };
899 let mut out = Vec::new();
900 for target in [
901 "ppp", "fff", "pp", "ff", "fp", "mp", "mf", "p", "f", "m", "Z", "Zp", "R",
902 ] {
903 let Some(at) = data.find(target) else {
904 continue;
905 };
906 data.replace_range(at..at + target.len(), "");
907 let value = match target {
908 "m" => "mp",
909 "Z" | "Zp" | "R" => "sf",
910 other => other,
911 };
912 out.push(Dynamic::new(value));
913 }
914 out
915 }
916}
917
918fn beams_of(written: &str) -> Beams {
920 let mut beams = Beams::default();
921 for c in written.chars() {
922 let (kind, direction) = match c {
923 '[' => (BeamType::Start, None),
924 ']' => (BeamType::Stop, None),
925 '=' => (BeamType::Continue, None),
926 '/' => (BeamType::PartialBeam, Some(BeamDirection::Right)),
927 '\\' => (BeamType::PartialBeam, Some(BeamDirection::Left)),
928 _ => continue,
929 };
930 beams.append(kind, direction);
931 }
932 beams
933}
934
935fn note_or_chord(
939 records: &[Record<'_>],
940 divisions: FloatType,
941 previous: Option<Option<TieType>>,
942) -> Result<(StreamElement, Vec<Dynamic>, Option<TieType>)> {
943 let first = records[0];
944 let duration = Duration::new(first.quarter_length(divisions)?)?;
945 let held = matches!(previous, Some(Some(TieType::Start | TieType::Continue)));
946 let tie = if first.is_tied() {
947 Some(if held {
948 TieType::Continue
949 } else {
950 TieType::Start
951 })
952 } else if held {
953 Some(TieType::Stop)
954 } else {
955 None
956 };
957 let mut pitches = Vec::new();
958 for record in records {
959 pitches.push(record.pitch()?);
960 }
961 let lyrics = first.lyrics();
962 let beams = first.beams().map(|written| beams_of(&written));
963 let element = if pitches.len() == 1 {
964 let mut note = Note::from_pitch(pitches.remove(0)).with_duration(duration);
965 for lyric in lyrics.iter().flatten() {
966 note.add_lyric(lyric, None, false)?;
967 }
968 if let Some(beams) = beams {
969 note.set_beams(beams);
970 }
971 note.articulations_mut().extend(first.articulations());
972 note.expressions_mut().extend(first.expressions());
973 note.set_tie(tie.map(Tie::new));
974 StreamElement::Note(note)
975 } else {
976 let notes: Vec<Note> = pitches.into_iter().map(Note::from_pitch).collect();
977 let mut chord = Chord::new(notes)?.with_duration(duration);
978 for lyric in lyrics.iter().flatten() {
979 chord.add_lyric(lyric, None, false)?;
980 }
981 if let Some(beams) = beams {
982 chord.set_beams(beams);
983 }
984 chord.articulations_mut().extend(first.articulations());
985 chord.expressions_mut().extend(first.expressions());
986 for note in chord.notes_mut() {
987 note.set_tie(tie.map(Tie::new));
988 }
989 StreamElement::Chord(chord)
990 };
991 Ok((element, first.dynamics(), tie))
992}
993
994const BASE40_INTERVALS: [(i32, &str); 25] = [
997 (0, "P1"),
998 (1, "A1"),
999 (4, "d2"),
1000 (5, "m2"),
1001 (6, "M2"),
1002 (7, "A2"),
1003 (10, "d3"),
1004 (11, "m3"),
1005 (12, "M3"),
1006 (13, "A3"),
1007 (16, "d4"),
1008 (17, "P4"),
1009 (18, "A4"),
1010 (22, "d5"),
1011 (23, "P5"),
1012 (24, "A5"),
1013 (27, "d6"),
1014 (28, "m6"),
1015 (29, "M6"),
1016 (30, "A6"),
1017 (33, "d7"),
1018 (34, "m7"),
1019 (35, "M7"),
1020 (36, "A7"),
1021 (39, "d8"),
1022];
1023
1024fn base40_interval(delta: i32) -> Result<Interval> {
1027 let simple = delta.abs() % 40;
1028 let name = BASE40_INTERVALS
1029 .iter()
1030 .find(|(distance, _)| *distance == simple)
1031 .map(|(_, name)| *name)
1032 .ok_or_else(|| error(format!("Interval not handled by Base40 {simple}")))?;
1033 let (specifier, number) = name.split_at(1);
1034 let number: i32 = number.parse().unwrap_or(1);
1035 let octaves = delta.abs() / 40;
1036 let generic = number + 7 * octaves;
1037 let generic = if delta < 0 { -generic } else { generic };
1038 Interval::from_name(format!("{specifier}{generic}"))
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043 use super::*;
1044
1045 #[test]
1046 fn a_base40_distance_is_an_interval_with_its_octaves() {
1047 assert_eq!(base40_interval(-11).unwrap().directed_name(), "m-3");
1048 assert_eq!(base40_interval(23).unwrap().directed_name(), "P5");
1049 assert_eq!(base40_interval(63).unwrap().directed_name(), "P12");
1050 assert!(base40_interval(2).is_err());
1051 }
1052
1053 #[test]
1054 fn a_measure_number_is_every_digit_of_its_record() {
1055 let lines = ["mheavy2 12".to_string(), "C4 1 q".to_string()];
1056 assert_eq!(measure_object(&lines).unwrap().number(), 212);
1057 let lines = ["C4 1 q".to_string()];
1058 assert_eq!(measure_object(&lines).unwrap().number(), 1);
1059 }
1060
1061 #[test]
1062 fn beams_are_read_from_their_column() {
1063 let beams = beams_of("[[/");
1064 assert_eq!(beams.len(), 3);
1065 }
1066
1067 #[test]
1068 fn a_dynamic_is_taken_out_before_the_next_is_looked_for() {
1069 let record = Record {
1070 line: "C5 3 e d [ pp.",
1071 stage: 2,
1072 };
1073 let values: Vec<String> = record
1074 .dynamics()
1075 .iter()
1076 .map(|dynamic| dynamic.value().to_string())
1077 .collect();
1078 assert_eq!(values, ["pp"]);
1079 }
1080}