Skip to main content

music21_rs/
musedata.rs

1//! MuseData, the Center for Computer Assisted Research in the Humanities'
2//! encoding: music21's `musedata` and `musedata.translate`.
3//!
4//! A MuseData file holds one part or several, each ended by a `/END` line,
5//! and a work is usually a file per part. [`from_musedata`] reads the files
6//! of a work into a score of a part each. Both of the encoding's stages are
7//! read: stage 2, with its `$` attribute records and its columns for beams,
8//! accidentals, notations and lyrics, and the older stage 1.
9
10use 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
36/// Reads the files of a MuseData work, in order, into a score, as music21's
37/// `converter.parse` reads a work's directory or a single file.
38///
39/// Each part is a part of the score, named as its header names it, in
40/// measures as its `measure` records divide it, each measure opening with
41/// the barline its record draws and the first with the clef, key, meter and
42/// any tempo word the part's attribute record gives. Notes, chords and
43/// rests take their lengths from the divisions of a quarter note; a stage-2
44/// file also gives each note its beams, the accidental it shows, its
45/// articulations, fermatas, ornaments and dynamics, and its lyrics, and a
46/// `back` record starts another voice. A part written for a transposing
47/// instrument (`X:`) is turned to the pitch it sounds, and its accidentals
48/// are worked out again; a stage-1 part is given its best clef, its beams
49/// and its accidentals. The work's title, number and movement are kept.
50///
51/// music21's own habits are kept: every measure starts with a barline, the
52/// first measure is numbered one, a measure's number is every digit of its
53/// record (so `mheavy2 12` is measure 212), and a pitch of double sharps
54/// reads as a single sharp, of double flats as a single flat. Cue and grace
55/// notes are passed over, as is an attribute record after the first.
56///
57/// ```
58/// use music21_rs::musedata::from_musedata;
59///
60/// let part = "\n\n\n01/01/01 x\nWK#:1 MV#:1\nsource\nWork\nMovement\nFlute\n\
61/// 1 0\nGroup memberships: score\nscore: part 1 of 1\n\
62/// $  K:0   Q:2   T:2/4   C:4\nC5     2        q     u\nD5     2        q     u\n\
63/// measure 2\nE5     4        h     u\n/END\n";
64/// let score = from_musedata(&[part])?;
65/// assert_eq!(score.parts()[0].measures().len(), 2);
66/// # Ok::<(), music21_rs::Error>(())
67/// ```
68///
69/// # Errors
70///
71/// A file with no part in it, an attribute record music21 cannot read, a
72/// clef or barline it does not know, a record whose length is not a number,
73/// or a transposition it has no interval for.
74pub 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
103/// music21's `MuseDataFile.readstr`: the parts of one file, comments and
104/// `@` lines left out.
105fn 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
130/// Every digit of a string, in order: music21's `getNumFromStr`.
131fn digits(text: &str) -> String {
132    text.chars().filter(char::is_ascii_digit).collect()
133}
134
135/// A part's lines and the stage they are written in.
136struct Part {
137    lines: Vec<String>,
138    stage: u8,
139}
140
141impl Part {
142    /// music21's `MuseDataPart`: stage 2 where any line is an attribute
143    /// record, stage 1 otherwise, whose leading blank lines are dropped.
144    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    /// `_getDigitsFollowingTag`: the digits after a tag, spaces passed
166    /// over, `-` and `/` kept, anything else ending it.
167    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    /// `_getAlphasFollowingTag` with `keepSpace` and `keepCase`: the letters,
183    /// spaces and commas after a tag.
184    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    /// `_getAttributesRecord`.
249    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    /// The stage-1 attribute record's fields, split as music21 splits it.
262    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    /// The interval a transposing part is written at, in base 40 (`X:`).
352    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    /// Divisions of a quarter note: `Q:`, or in stage 1 the divisions of a
363    /// bar over the bar's length in quarters.
364    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    /// `_getMeasureBoundaryIndices`: the lines of each measure, from its
377    /// first up to the one after its last.
378    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    /// music21's `musedataPartToStreamPart`.
428    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            // A stage-1 part states no clef for this to replace.
512            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            // music21 warns and carries on where a meter cannot beam its bar.
524            let _ = make_beams(&mut part);
525            make_accidentals(&mut part);
526        }
527        Ok(part)
528    }
529
530    /// The notes, chords and rests of one measure's records, in voices where
531    /// a `back` record says to start another.
532    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    /// music21's `_processPending`: the records waiting turned into a note
576    /// or chord, its dynamics put beside it.
577    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
601/// music21's `append`: at the end of the stream, sorted among what stands
602/// there.
603fn append(stream: &mut Stream, element: StreamElement) {
604    let at = stream.end_offset();
605    append_at(stream, at, element);
606}
607
608/// music21's `insert`: sorted among what already stands at the offset.
609fn append_at(stream: &mut Stream, offset: FloatType, element: StreamElement) {
610    stream.insert_sorted(vec![StreamEvent::new(offset, element)]);
611}
612
613/// `MuseDataMeasure.hasNotes`.
614fn 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
622/// The record opening a measure, as music21 reads it: its own line where it
623/// is a measure record, `measure` otherwise.
624fn 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
637/// `MuseDataMeasure.getBarObject`.
638fn 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
674/// `MuseDataMeasure.getMeasureObject`: a measure numbered as its record
675/// says, opening with its barline.
676fn 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/// One line of a measure: `MuseDataRecord`.
697#[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    /// The character at a column, counted from nought.
709    fn at(&self, column: usize) -> Option<char> {
710        self.line.chars().nth(column)
711    }
712
713    /// The characters from one column up to another.
714    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    /// `_getPitchParameters` and `getPitchObject`.
744    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        // music21 asks for one sharp before two, and one flat before two.
756        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    /// `_getAccidentalObject`: the accidental column 19 shows.
788    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    /// `getQuarterLength`.
804    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    /// `_getAdditionalNotations`: columns 32 to 43.
848    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    /// `getDynamicObjects`: each mark found once, in music21's order of
894    /// looking, and taken out of what is left to look in.
895    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
918/// `_musedataBeamToBeams`.
919fn 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
935/// `_musedataRecordListToNoteOrChord`: a note, or a chord of several
936/// records, what the first record says of it, the dynamics it carries, and
937/// the tie it leaves for the next.
938fn 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
994/// music21's `base40IntervalTable`: the simple interval each distance in
995/// base 40 is.
996const 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
1024/// music21's `base40DeltaToInterval`: a distance in base 40 as an interval,
1025/// its octaves counted and its direction kept.
1026fn 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}