Skip to main content

music21/
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
10pub mod base12_26;
11pub mod base40;
12
13use std::collections::HashMap;
14
15use crate::articulations::{Articulation, ArticulationKind};
16use crate::bar::{Barline, BarlineType, RepeatDirection};
17use crate::chord::Chord;
18use crate::clef::{Clef, ClefKind};
19use crate::defaults::{FloatType, IntegerType};
20use crate::duration::Duration;
21use crate::dynamics::Dynamic;
22use crate::error::{Error, Position, Result};
23use crate::expressions::{Expression, Fermata, Ornament, OrnamentKind};
24use crate::interval::Interval;
25use crate::key::KeySignature;
26use crate::makenotation::{make_accidentals, make_beams, op_frac};
27use crate::metadata::Metadata;
28use crate::meter::TimeSignature;
29use crate::notation::{BeamDirection, BeamType, Beams, Tie, TieType};
30use crate::note::Note;
31use crate::pitch::Pitch;
32use crate::pitch::accidental::Accidental;
33use crate::rest::Rest;
34use crate::spanner::Spanner;
35use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
36use crate::tempo::TempoText;
37
38fn error(message: impl Into<String>) -> Error {
39    Error::muse_data(message.into())
40}
41
42/// Reads the files of a MuseData work, in order, into a score, as music21's
43/// `converter.parse` reads a work's directory or a single file.
44///
45/// Each part is a part of the score, named as its header names it, in
46/// measures as its `measure` records divide it, each measure opening with
47/// the barline its record draws and the first with the clef, key, meter and
48/// any tempo word the part's attribute record gives. Notes, chords and
49/// rests take their lengths from the divisions of a quarter note; a stage-2
50/// file also gives each note its beams, the accidental it shows, its
51/// articulations, fermatas, ornaments and dynamics, and its lyrics, and a
52/// `back` record starts another voice. A part written for a transposing
53/// instrument (`X:`) is turned to the pitch it sounds, and its accidentals
54/// are worked out again; a stage-1 part is given its best clef, its beams
55/// and its accidentals. Stage-2 slur marks become slurs. The work's title,
56/// number and movement are kept.
57///
58/// music21's own habits are kept: every measure starts with a barline, the
59/// first measure is numbered one (a pickup before it zero), a measure's number is every digit of its
60/// record (so `mheavy2 12` is measure 212), and a pitch of double sharps
61/// reads as a single sharp, of double flats as a single flat. Cue and grace
62/// notes are passed over, as is an attribute record after the first.
63///
64/// ```
65/// use music21::musedata::from_musedata;
66///
67/// let part = "\n\n\n01/01/01 x\nWK#:1 MV#:1\nsource\nWork\nMovement\nFlute\n\
68/// 1 0\nGroup memberships: score\nscore: part 1 of 1\n\
69/// $  K:0   Q:2   T:2/4   C:4\nC5     2        q     u\nD5     2        q     u\n\
70/// measure 2\nE5     4        h     u\n/END\n";
71/// let score = from_musedata(&[part])?;
72/// assert_eq!(score.parts()[0].measures().len(), 2);
73/// # Ok::<(), music21::Error>(())
74/// ```
75///
76/// # Errors
77///
78/// A file with no part in it, an attribute record music21 cannot read, a
79/// clef or barline it does not know, a record whose length is not a number,
80/// or a transposition it has no interval for.
81pub fn from_musedata(files: &[&str]) -> Result<Stream> {
82    let mut parts = Vec::new();
83    for text in files {
84        parts.extend(split_parts(text)?);
85    }
86    let first = parts
87        .first()
88        .ok_or_else(|| error("a MuseData work with no parts"))?;
89    let mut metadata = Metadata::new();
90    if let Some(title) = first.work_title() {
91        metadata.add_text("title", title);
92    }
93    if let Some(number) = first.movement_number() {
94        metadata.add_text("movementNumber", number);
95    }
96    if let Some(name) = first.movement_title() {
97        metadata.add_text("movementName", name);
98    }
99    if let Some(number) = first.work_number() {
100        metadata.add_text("number", number);
101    }
102    let mut score = Stream::with_kind(StreamKind::Score);
103    score.set_metadata(Some(metadata));
104    let mut slurs = Vec::new();
105    for part in &parts {
106        let (made, joined) = part.to_part()?;
107        score.insert(0.0, made);
108        slurs.push(joined);
109    }
110    // Each part's notes and chords in the order they were read, as places
111    // among the score's leaves.
112    let mut places: Vec<Vec<usize>> = vec![Vec::new(); slurs.len()];
113    for (position, (top, _, element)) in score.leaves_under_top().into_iter().enumerate() {
114        if matches!(element, StreamElement::Note(_) | StreamElement::Chord(_))
115            && let Some(read) = places.get_mut(top)
116        {
117            read.push(position);
118        }
119    }
120    for (joined, read) in slurs.iter().zip(&places) {
121        for (start, stop) in joined {
122            if let (Some(first), Some(last)) = (read.get(*start), read.get(*stop)) {
123                score.add_spanner(Spanner::slur(vec![*first, *last]));
124            }
125        }
126    }
127    Ok(score)
128}
129
130/// The slurs of one part, as music21's `_SlurCollector` builds them: the
131/// marks ( ) [ ] { } and z x start and stop slurs 1 to 4, a stop closing the
132/// open slur of its number. Notes and chords are counted in the order they
133/// are read.
134#[derive(Default)]
135struct Slurs {
136    open: [Option<usize>; 4],
137    joined: Vec<(usize, usize)>,
138    read: usize,
139}
140
141impl Slurs {
142    const STARTS: [char; 4] = ['(', '[', '{', 'z'];
143    const STOPS: [char; 4] = [')', ']', '}', 'x'];
144
145    /// Stops, then starts, the slurs the marks name on the next note or
146    /// chord, so one note can end a slur and begin the next of its number.
147    fn add(&mut self, marks: &str) {
148        let at = self.read;
149        self.read += 1;
150        for mark in marks.chars() {
151            if let Some(number) = Self::STOPS.iter().position(|stop| *stop == mark)
152                && let Some(start) = self.open[number].take()
153            {
154                self.joined.push((start, at));
155            }
156        }
157        for mark in marks.chars() {
158            if let Some(number) = Self::STARTS.iter().position(|start| *start == mark) {
159                self.open[number] = Some(at);
160            }
161        }
162    }
163}
164
165/// music21's `MuseDataFile.readstr`: the parts of one file, comments and
166/// `@` lines left out, read the same with Windows line endings.
167fn split_parts(text: &str) -> Result<Vec<Part>> {
168    let mut parts = Vec::new();
169    let mut lines: Vec<String> = Vec::new();
170    // The line of the file each of them is, counted from one.
171    let mut numbers: Vec<usize> = Vec::new();
172    let mut comment = false;
173    for (index, line) in text.replace("\r\n", "\n").split('\n').enumerate() {
174        if line.starts_with('&') {
175            comment = !comment;
176            continue;
177        }
178        if comment || line.starts_with('@') {
179            continue;
180        }
181        if line.starts_with("/END") || line.starts_with("END") {
182            if lines.len() <= 1 {
183                lines.clear();
184                numbers.clear();
185                continue;
186            }
187            let at = numbers.first().copied().unwrap_or(index + 1);
188            let part = Part::new(std::mem::take(&mut lines), std::mem::take(&mut numbers))
189                .map_err(|error| error.placed(|| Position::line(at)))?;
190            parts.push(part);
191        } else if !line.starts_with("/eof") {
192            lines.push(line.to_string());
193            numbers.push(index + 1);
194        }
195    }
196    Ok(parts)
197}
198
199/// Every digit of a string, in order: music21's `getNumFromStr`.
200fn digits(text: &str) -> String {
201    text.chars().filter(char::is_ascii_digit).collect()
202}
203
204/// The stage of the encoding a part is written in: stage 2 has `$`
205/// attribute records and columns stage 1 lacks.
206#[derive(Clone, Copy, PartialEq, Eq)]
207enum Stage {
208    One,
209    Two,
210}
211
212/// A part's lines and the stage they are written in.
213struct Part {
214    lines: Vec<String>,
215    /// The line of its file each line is, counted from one.
216    numbers: Vec<usize>,
217    stage: Stage,
218}
219
220impl Part {
221    /// music21's `MuseDataPart`: stage 2 where any line is an attribute
222    /// record, stage 1 otherwise, whose leading blank lines are dropped.
223    fn new(lines: Vec<String>, numbers: Vec<usize>) -> Result<Self> {
224        if lines.iter().any(|line| line.starts_with('$')) {
225            return Ok(Self {
226                lines,
227                numbers,
228                stage: Stage::Two,
229            });
230        }
231        if lines.len() <= 1 {
232            return Err(error("cannot scrub empty source"));
233        }
234        let (lines, numbers) = lines
235            .into_iter()
236            .zip(numbers)
237            .skip_while(|(line, _)| line.trim().is_empty())
238            .unzip();
239        Ok(Self {
240            lines,
241            numbers,
242            stage: Stage::One,
243        })
244    }
245
246    /// Where the line at `index` stands in the file, in this part.
247    fn place(&self, index: usize) -> Position {
248        let position = Position {
249            line: self.numbers.get(index).copied(),
250            ..Position::default()
251        };
252        match self.part_name() {
253            Some(name) => position.in_part(name),
254            None => position,
255        }
256    }
257
258    /// Where the attribute record stands: what goes wrong reading a
259    /// part's key, meter, clef or divisions is placed there.
260    fn attributes_place(&self) -> Position {
261        let index = match self.stage {
262            Stage::One => Some(6),
263            Stage::Two => self
264                .lines
265                .iter()
266                .skip(11)
267                .position(|line| line.starts_with('$'))
268                .map(|index| index + 11),
269        };
270        match index {
271            Some(index) => self.place(index),
272            None => self.place(usize::MAX),
273        }
274    }
275
276    fn line(&self, index: usize) -> Result<&str> {
277        self.lines
278            .get(index)
279            .map(String::as_str)
280            .ok_or_else(|| error("a header shorter than MuseData's"))
281    }
282
283    /// `_getDigitsFollowingTag`: the digits after a tag, spaces passed
284    /// over, `-` and `/` kept, anything else ending it.
285    fn digits_after(line: &str, tag: &str) -> String {
286        let Some(at) = line.find(tag) else {
287            return String::new();
288        };
289        let mut out = String::new();
290        for c in line[at + tag.len()..].chars() {
291            if c.is_ascii_digit() || c == '-' || c == '/' {
292                out.push(c);
293            } else if !c.is_whitespace() {
294                break;
295            }
296        }
297        out
298    }
299
300    /// `_getAlphasFollowingTag` with `keepSpace` and `keepCase`: the letters,
301    /// spaces and commas after a tag.
302    fn words_after(line: &str, tag: &str) -> String {
303        let Some(at) = line.find(tag) else {
304            return String::new();
305        };
306        line[at + tag.len()..]
307            .chars()
308            .take_while(|c| c.is_alphabetic() || c.is_whitespace() || *c == ',')
309            .collect()
310    }
311
312    fn work_number(&self) -> Option<String> {
313        if self.stage == Stage::One {
314            let data: String = self.lines.get(1)?.chars().take(6).collect();
315            let data = data.trim();
316            Some(if data.contains(',') {
317                data.split(',').nth(1).unwrap_or("").to_string()
318            } else {
319                data.to_string()
320            })
321        } else {
322            Some(Self::digits_after(self.lines.get(4)?, "WK#:"))
323        }
324    }
325
326    fn movement_number(&self) -> Option<String> {
327        if self.stage == Stage::One {
328            Some(
329                self.lines
330                    .get(1)?
331                    .chars()
332                    .skip(6)
333                    .collect::<String>()
334                    .trim()
335                    .to_string(),
336            )
337        } else {
338            Some(Self::digits_after(self.lines.get(4)?, "MV#:"))
339        }
340    }
341
342    fn work_title(&self) -> Option<String> {
343        if self.stage == Stage::One {
344            Some(self.lines.first()?.trim().to_string())
345        } else {
346            self.lines.get(6).cloned()
347        }
348    }
349
350    fn movement_title(&self) -> Option<String> {
351        if self.stage == Stage::One {
352            None
353        } else {
354            self.lines.get(7).cloned()
355        }
356    }
357
358    fn part_name(&self) -> Option<String> {
359        if self.stage == Stage::One {
360            None
361        } else {
362            self.lines.get(8).map(|name| name.trim().to_string())
363        }
364    }
365
366    /// `_getAttributesRecord`.
367    fn attributes(&self) -> Result<String> {
368        if self.stage == Stage::One {
369            return Ok(format!("{} {}", self.line(6)?.trim(), self.line(7)?.trim()));
370        }
371        self.lines
372            .iter()
373            .skip(11)
374            .find(|line| line.starts_with('$'))
375            .cloned()
376            .ok_or_else(|| error("a part with no attribute record"))
377    }
378
379    /// The stage-1 attribute record's fields, split as music21 splits it.
380    fn stage_one_field(&self, index: usize) -> Result<String> {
381        self.attributes()?
382            .split(' ')
383            .nth(index)
384            .map(str::to_string)
385            .ok_or_else(|| error("a stage-1 attribute record too short"))
386    }
387
388    fn integer(text: &str) -> Result<IntegerType> {
389        text.trim()
390            .parse()
391            .map_err(|_| error(format!("{text:?} is not a number")))
392    }
393
394    fn key_signature(&self) -> Result<KeySignature> {
395        let sharps = if self.stage == Stage::One {
396            Self::integer(&self.stage_one_field(1)?)?
397        } else {
398            Self::integer(&Self::digits_after(&self.attributes()?, "K:"))?
399        };
400        Ok(KeySignature::new(sharps))
401    }
402
403    fn time_signature(&self) -> Result<TimeSignature> {
404        let (numerator, denominator) = if self.stage == Stage::One {
405            (
406                Self::integer(&self.stage_one_field(4)?)?,
407                Self::integer(&self.stage_one_field(5)?)?,
408            )
409        } else {
410            let written = Self::digits_after(&self.attributes()?, "T:");
411            let (numerator, denominator) = written
412                .split_once('/')
413                .filter(|(_, denominator)| !denominator.contains('/'))
414                .ok_or_else(|| error(format!("no meter in {written:?}")))?;
415            (Self::integer(numerator)?, Self::integer(denominator)?)
416        };
417        let ratio = if (numerator == 1 && denominator == 1) || denominator == 0 {
418            "4/4".to_string()
419        } else {
420            format!("{numerator}/{denominator}")
421        };
422        TimeSignature::from_ratio_string(&ratio)
423    }
424
425    fn clef(&self) -> Result<Option<Clef>> {
426        if self.stage == Stage::One {
427            return Ok(None);
428        }
429        let line = self.attributes()?;
430        let mut written = Self::digits_after(&line, "C:");
431        if written.is_empty() {
432            let staves = match Self::digits_after(&line, "S:").as_str() {
433                "" => 1,
434                raw => Self::integer(raw)?,
435            };
436            written = (1..=staves)
437                .map(|staff| Self::digits_after(&line, &format!("C{staff}:")))
438                .find(|raw| !raw.is_empty())
439                .ok_or_else(|| error("a part with no clef"))?;
440        }
441        let kind = match Self::integer(&written)?.to_string().as_str() {
442            "5" => ClefKind::FrenchViolinClef,
443            "4" => ClefKind::TrebleClef,
444            "34" => ClefKind::Treble8vbClef,
445            "64" => ClefKind::Treble8vaClef,
446            "3" => ClefKind::GSopranoClef,
447            "11" => ClefKind::CBaritoneClef,
448            "12" => ClefKind::TenorClef,
449            "13" => ClefKind::AltoClef,
450            "14" => ClefKind::MezzoSopranoClef,
451            "15" => ClefKind::SopranoClef,
452            "22" => ClefKind::BassClef,
453            "23" => ClefKind::FBaritoneClef,
454            "52" => ClefKind::Bass8vbClef,
455            "82" => ClefKind::Bass8vaClef,
456            other => return Err(error(format!("cannot determine clef from: {other}"))),
457        };
458        Ok(Some(Clef::of_kind(kind)))
459    }
460
461    fn directive(&self) -> Result<Option<String>> {
462        if self.stage == Stage::One {
463            return Ok(None);
464        }
465        let words = Self::words_after(&self.attributes()?, "D:");
466        Ok((!words.is_empty()).then(|| words.trim().to_string()))
467    }
468
469    /// The interval a transposing part is written at, in base 40 (`X:`).
470    fn transposition(&self) -> Result<Option<Interval>> {
471        if self.stage == Stage::One {
472            return Ok(None);
473        }
474        match Self::digits_after(&self.attributes()?, "X:").as_str() {
475            "" => Ok(None),
476            raw => base40_interval(Self::integer(raw)?).map(Some),
477        }
478    }
479
480    /// Divisions of a quarter note: `Q:`, or in stage 1 the divisions of a
481    /// bar over the bar's length in quarters.
482    fn divisions(&self) -> Result<FloatType> {
483        if self.stage == Stage::One {
484            let bar = Self::integer(&self.stage_one_field(2)?)?;
485            Ok(FloatType::from(bar) / self.time_signature()?.bar_duration().quarter_length())
486        } else {
487            Ok(FloatType::from(Self::integer(&Self::digits_after(
488                &self.attributes()?,
489                "Q:",
490            ))?))
491        }
492    }
493
494    /// `_getMeasureBoundaryIndices`: the lines of each measure, from its
495    /// first up to the one after its last.
496    fn measure_bounds(&self) -> Result<Vec<(usize, usize)>> {
497        let lines = &self.lines;
498        let end = lines.len();
499        let mut marks = Vec::new();
500        let mut first_after: Option<usize> = None;
501        let mut attributes = 0;
502        for (index, line) in lines.iter().enumerate() {
503            if self.stage == Stage::Two {
504                if line.starts_with('$') {
505                    attributes += 1;
506                    continue;
507                }
508                if attributes > 0 && first_after.is_none() {
509                    first_after = Some(index);
510                }
511            }
512            if line.starts_with('m') {
513                marks.push(index);
514            }
515        }
516        let first_mark = *marks
517            .first()
518            .ok_or_else(|| error("a part with no measure records"))?;
519        if self.stage == Stage::One {
520            first_after = Some(first_mark);
521        }
522        let mut bounds = Vec::new();
523        let start = if Some(first_mark) == first_after {
524            if marks.len() == 1 {
525                bounds.push((first_mark, end));
526                None
527            } else {
528                bounds.push((first_mark, marks[1]));
529                Some(1)
530            }
531        } else {
532            let first_after =
533                first_after.ok_or_else(|| error("a part with nothing after its attributes"))?;
534            bounds.push((first_after, first_mark));
535            Some(0)
536        };
537        if let Some(start) = start {
538            for index in start..marks.len() {
539                bounds.push((marks[index], marks.get(index + 1).copied().unwrap_or(end)));
540            }
541        }
542        Ok(bounds)
543    }
544
545    /// music21's `musedataPartToStreamPart`.
546    fn to_part(&self) -> Result<(Stream, Vec<(usize, usize)>)> {
547        let mut part = Stream::with_kind(StreamKind::Part);
548        let name = self.part_name();
549        part.set_id(name.clone());
550        part.set_name(name);
551        let header = |error: Error| error.placed(|| self.attributes_place());
552        let divisions = self.divisions().map_err(header)?;
553        let bounds = self.measure_bounds()?;
554        let measures: Vec<&[String]> = bounds
555            .iter()
556            .map(|(start, end)| self.lines.get(*start..*end).unwrap_or(&[]))
557            .collect();
558        // The last note, chord or rest of each voice, by its number, for the
559        // ties across measures; a measure without voices continues voice 0.
560        let mut last_by_voice: HashMap<usize, Option<Option<TieType>>> = HashMap::new();
561        let mut slurs = Slurs::default();
562        let mut bars = 0;
563        for (index, lines) in measures.iter().enumerate() {
564            if !has_notes(lines) {
565                continue;
566            }
567            // What goes wrong inside a measure says which, by the number
568            // its record gives it, and the line it opens on.
569            let first = bounds[index].0;
570            let here = |error: Error| {
571                error.placed(|| {
572                    let mut position = self.place(first);
573                    position.measure = measure_number(lines).ok();
574                    position
575                })
576            };
577            let mut measure = measure_object(lines).map_err(here)?;
578            if let Some(next) = measures.get(index + 1)
579                && !has_notes(next)
580            {
581                let after = bounds[index + 1].0;
582                let barline = bar_object(next).map_err(|error| error.placed(|| self.place(after)));
583                measure.set_right_barline(Some(barline.map_err(here)?));
584            }
585            if bars == 0 {
586                if let Some(clef) = self.clef().map_err(header)? {
587                    append_at(&mut measure, 0.0, StreamElement::Clef(clef));
588                }
589                append_at(
590                    &mut measure,
591                    0.0,
592                    StreamElement::TimeSignature(self.time_signature().map_err(header)?),
593                );
594                append_at(
595                    &mut measure,
596                    0.0,
597                    StreamElement::KeySignature(self.key_signature().map_err(header)?),
598                );
599                if let Some(directive) = self.directive().map_err(header)? {
600                    let words = TempoText::new(directive);
601                    if words.is_common_tempo_text() {
602                        append_at(
603                            &mut measure,
604                            0.0,
605                            StreamElement::MetronomeMark(words.metronome_mark()),
606                        );
607                    }
608                }
609            }
610            self.fill(
611                &mut measure,
612                lines,
613                first,
614                divisions,
615                &mut last_by_voice,
616                &mut slurs,
617            )
618            .map_err(here)?;
619            if bars == 0 {
620                let bar = self.time_signature()?.bar_duration().quarter_length();
621                let length = measure.end_offset();
622                if length / bar < 1.0 {
623                    measure.set_padding_left(bar - length);
624                }
625            }
626            part.push(measure);
627            bars += 1;
628        }
629        if let Some(interval) = self.transposition().map_err(header)? {
630            let mut failure = None;
631            part.for_each_mut(&mut |_, element| {
632                let moved = match element {
633                    StreamElement::Note(note) => note.transpose(&interval).map(StreamElement::Note),
634                    StreamElement::Chord(chord) => {
635                        chord.transpose(&interval).map(StreamElement::Chord)
636                    }
637                    StreamElement::KeySignature(signature) => signature
638                        .transpose(&interval)
639                        .map(StreamElement::KeySignature),
640                    _ => return,
641                };
642                match moved {
643                    Ok(moved) => *element = moved,
644                    Err(error) => failure = Some(error),
645                }
646            });
647            if let Some(error) = failure {
648                return Err(error);
649            }
650            make_accidentals(&mut part);
651        }
652        if self.stage == Stage::One {
653            // A stage-1 part states no clef for this to replace.
654            let clef = Clef::best_for(&part.pitches(), false);
655            if let Some(StreamElement::Stream(first)) = part
656                .events_mut()
657                .iter_mut()
658                .map(StreamEvent::element_mut)
659                .find(|element| {
660                    matches!(element, StreamElement::Stream(inner) if inner.kind() == StreamKind::Measure)
661                })
662            {
663                append_at(first, 0.0, StreamElement::Clef(clef));
664            }
665            // music21 warns and carries on where a meter cannot beam its bar.
666            let _ = make_beams(&mut part);
667            make_accidentals(&mut part);
668        }
669        Ok((part, slurs.joined))
670    }
671
672    /// The notes, chords and rests of one measure's records, in voices where
673    /// a `back` record says to start another. Each voice ties on from the
674    /// voice of its number in the measure before.
675    fn fill(
676        &self,
677        measure: &mut Stream,
678        lines: &[String],
679        first: usize,
680        divisions: FloatType,
681        last_by_voice: &mut HashMap<usize, Option<Option<TieType>>>,
682        slurs: &mut Slurs,
683    ) -> Result<()> {
684        let voices = lines.iter().any(|line| line.starts_with("back"));
685        let mut voice = voices.then(|| Stream::with_kind(StreamKind::Voice));
686        let mut pending: Vec<Record<'_>> = Vec::new();
687        let mut number = 0;
688        let mut last = last_by_voice.get(&number).copied().flatten();
689        let previous = &mut last;
690        for (index, line) in lines.iter().enumerate() {
691            let record = Record {
692                line,
693                stage: self.stage,
694                number: self.numbers.get(first + index).copied(),
695            };
696            if record.is_back() {
697                self.flush(
698                    &mut pending,
699                    measure,
700                    voice.as_mut(),
701                    divisions,
702                    previous,
703                    slurs,
704                )?;
705                if let Some(done) = voice.replace(Stream::with_kind(StreamKind::Voice)) {
706                    append_at(measure, 0.0, StreamElement::Stream(Box::new(done)));
707                }
708                last_by_voice.insert(number, *previous);
709                number += 1;
710                *previous = last_by_voice.get(&number).copied().flatten();
711            }
712            if record.is_rest() {
713                self.flush(
714                    &mut pending,
715                    measure,
716                    voice.as_mut(),
717                    divisions,
718                    previous,
719                    slurs,
720                )?;
721                let length = record
722                    .quarter_length(divisions)
723                    .and_then(Duration::new)
724                    .map_err(|error| record.placed(error))?;
725                let rest = Rest::new(length);
726                let target = voice.as_mut().unwrap_or(&mut *measure);
727                append(target, StreamElement::Rest(rest));
728                *previous = Some(None);
729            } else if record.is_chord() {
730                pending.push(record);
731            } else if record.is_note() {
732                self.flush(
733                    &mut pending,
734                    measure,
735                    voice.as_mut(),
736                    divisions,
737                    previous,
738                    slurs,
739                )?;
740                pending.push(record);
741            }
742        }
743        self.flush(
744            &mut pending,
745            measure,
746            voice.as_mut(),
747            divisions,
748            previous,
749            slurs,
750        )?;
751        last_by_voice.insert(number, *previous);
752        if let Some(voice) = voice
753            && !voice.is_empty()
754        {
755            append_at(measure, 0.0, StreamElement::Stream(Box::new(voice)));
756        }
757        Ok(())
758    }
759
760    /// music21's `_processPending`: the records waiting turned into a note
761    /// or chord, its dynamics put beside it.
762    fn flush(
763        &self,
764        pending: &mut Vec<Record<'_>>,
765        measure: &mut Stream,
766        voice: Option<&mut Stream>,
767        divisions: FloatType,
768        previous: &mut Option<Option<TieType>>,
769        slurs: &mut Slurs,
770    ) -> Result<()> {
771        if pending.is_empty() {
772            return Ok(());
773        }
774        let records = std::mem::take(pending);
775        slurs.add(&records[0].slur_marks());
776        let (element, dynamics, tie) = note_or_chord(&records, divisions, *previous)
777            .map_err(|error| records[0].placed(error))?;
778        *previous = Some(tie);
779        let target = voice.unwrap_or(measure);
780        let at = target.end_offset();
781        append_at(target, at, element);
782        for dynamic in dynamics {
783            append_at(target, at, StreamElement::Dynamic(dynamic));
784        }
785        Ok(())
786    }
787}
788
789/// music21's `append`: at the end of the stream, sorted among what stands
790/// there.
791fn append(stream: &mut Stream, element: StreamElement) {
792    let at = stream.end_offset();
793    append_at(stream, at, element);
794}
795
796/// music21's `insert`: sorted among what already stands at the offset.
797fn append_at(stream: &mut Stream, offset: FloatType, element: StreamElement) {
798    stream.insert_sorted(vec![StreamEvent::new(offset, element)]);
799}
800
801/// `MuseDataMeasure.hasNotes`.
802fn has_notes(lines: &[String]) -> bool {
803    lines.iter().any(|line| {
804        line.chars()
805            .next()
806            .is_some_and(|c| "ABCDEFGrgc".contains(c))
807    })
808}
809
810/// The record opening a measure, as music21 reads it: its own line where it
811/// is a measure record, `measure` otherwise.
812fn measure_line(lines: &[String]) -> Result<String> {
813    let line = lines
814        .first()
815        .ok_or_else(|| error("a measure with no records"))?
816        .trim()
817        .to_string();
818    Ok(if line.starts_with('m') {
819        line
820    } else {
821        "measure".to_string()
822    })
823}
824
825/// `MuseDataMeasure.getBarObject`.
826fn bar_object(lines: &[String]) -> Result<Barline> {
827    let line = measure_line(lines)?;
828    let kind: String = line.chars().skip(1).take(6).collect();
829    let bar_type = match kind.as_str() {
830        "easure" => BarlineType::Regular,
831        "dotted" => BarlineType::Dotted,
832        "double" => BarlineType::Double,
833        "heavy1" | "heavy" => BarlineType::Heavy,
834        "heavy2" => BarlineType::Final,
835        "heavy3" => BarlineType::HeavyLight,
836        "heavy4" | "heave4" => BarlineType::HeavyHeavy,
837        other => {
838            return Err(error(format!(
839                "cannot process bar data definition: {other}"
840            )));
841        }
842    };
843    let flag: String = line.chars().skip(16).collect();
844    let flag = flag.trim();
845    let mut barline = Barline::new(bar_type);
846    if line.chars().count() > 16 && !flag.is_empty() {
847        let direction = if flag.contains(":|") {
848            Some(RepeatDirection::End)
849        } else if flag.contains("|:") {
850            Some(RepeatDirection::Start)
851        } else {
852            None
853        };
854        if let Some(direction) = direction {
855            barline = Barline::repeat(direction, None);
856            barline.set_bar_type(bar_type);
857        }
858    }
859    Ok(barline)
860}
861
862/// The number a measure's record gives it, as music21 reads it: every
863/// digit of the record, `1` where it gives none, and `0` for the notes
864/// before the first record, a pickup.
865fn measure_number(lines: &[String]) -> Result<String> {
866    let line = measure_line(lines)?;
867    let rest: String = line.chars().skip(8).collect();
868    let opens_with_record = lines
869        .first()
870        .is_some_and(|first| first.trim().starts_with('m'));
871    Ok(if line.chars().count() >= 9 && !rest.trim().is_empty() {
872        digits(&line)
873    } else if opens_with_record {
874        "1".to_string()
875    } else {
876        "0".to_string()
877    })
878}
879
880/// `MuseDataMeasure.getMeasureObject`: a measure numbered as its record
881/// says, opening with its barline.
882fn measure_object(lines: &[String]) -> Result<Stream> {
883    let number = measure_number(lines)?;
884    let mut measure = Stream::with_kind(StreamKind::Measure);
885    measure.set_left_barline(Some(bar_object(lines)?));
886    if !number.is_empty() {
887        measure.set_number(
888            number
889                .parse()
890                .map_err(|_| error(format!("measure number {number}")))?,
891        );
892    }
893    Ok(measure)
894}
895
896/// One line of a measure: `MuseDataRecord`.
897#[derive(Clone, Copy)]
898struct Record<'a> {
899    line: &'a str,
900    stage: Stage,
901    /// The line of the file it is, counted from one.
902    number: Option<usize>,
903}
904
905impl Record<'_> {
906    /// `error`, placed at this record's line.
907    fn placed(&self, error: Error) -> Error {
908        match self.number {
909            Some(number) => error.placed(|| Position::line(number)),
910            None => error,
911        }
912    }
913
914    fn first(&self) -> Option<char> {
915        self.line.chars().next()
916    }
917
918    /// The character at a column, counted from nought.
919    fn at(&self, column: usize) -> Option<char> {
920        self.line.chars().nth(column)
921    }
922
923    /// The characters from one column up to another.
924    fn columns(&self, from: usize, to: usize) -> String {
925        self.line
926            .chars()
927            .skip(from)
928            .take(to.saturating_sub(from))
929            .collect()
930    }
931
932    fn is_rest(&self) -> bool {
933        self.first() == Some('r')
934    }
935
936    fn is_note(&self) -> bool {
937        self.first().is_some_and(|c| "ABCDEFG".contains(c))
938    }
939
940    fn is_chord(&self) -> bool {
941        self.first() == Some(' ') && self.at(1).is_some_and(|c| "ABCDEFG".contains(c))
942    }
943
944    fn is_back(&self) -> bool {
945        self.line.starts_with("back")
946    }
947
948    fn is_tied(&self) -> bool {
949        let column = if self.stage == Stage::One { 7 } else { 8 };
950        self.at(column) == Some('-')
951    }
952
953    /// `_getPitchParameters` and `getPitchObject`.
954    fn pitch(&self) -> Result<Pitch> {
955        let token = if self.is_note() {
956            self.line
957        } else {
958            self.line.get(1..).unwrap_or("")
959        };
960        let data = token.split_whitespace().next().unwrap_or("");
961        let step = data
962            .chars()
963            .next()
964            .ok_or_else(|| error("a note with no pitch"))?;
965        // music21 asks for one sharp before two, and one flat before two.
966        let accidental = if data.contains('#') {
967            "#"
968        } else if data.contains('f') {
969            "-"
970        } else {
971            ""
972        };
973        let mut pitch = Pitch::from_name(format!("{step}{accidental}{}", digits(data)))?;
974        if self.stage == Stage::One {
975            return Ok(pitch);
976        }
977        if let Some(accidental) = pitch.written_accidental_mut() {
978            accidental.set_display_status(Some(false));
979        }
980        if let Some(shown) = self.shown_accidental()? {
981            pitch.set_accidental(shown);
982            if let Some(accidental) = pitch.written_accidental_mut() {
983                accidental.set_display_type("always")?;
984                accidental.set_display_status(Some(true));
985            }
986        }
987        if self
988            .notations()
989            .is_some_and(|notations| notations.contains('+'))
990            && let Some(accidental) = pitch.written_accidental_mut()
991        {
992            accidental.set_display_status(Some(true));
993        }
994        Ok(pitch)
995    }
996
997    /// `_getAccidentalObject`: the accidental column 19 shows.
998    fn shown_accidental(&self) -> Result<Option<Accidental>> {
999        if self.line.chars().count() <= 18 {
1000            return Ok(None);
1001        }
1002        let name = match self.at(18) {
1003            Some('#' | 'S') => "sharp",
1004            Some('n') => "natural",
1005            Some('f' | 'F') => "flat",
1006            Some('x' | 'X') => "double-sharp",
1007            Some('&') => "double-flat",
1008            _ => return Ok(None),
1009        };
1010        Accidental::new(name).map(Some)
1011    }
1012
1013    /// `getQuarterLength`.
1014    fn quarter_length(&self, divisions: FloatType) -> Result<FloatType> {
1015        let mut count = if self.stage == Stage::One {
1016            Part::integer(&self.columns(5, 7))?
1017        } else {
1018            Part::integer(&self.columns(5, 8))?
1019        };
1020        if let Some(blank) = self.at(4)
1021            && blank != ' '
1022        {
1023            let hundreds = blank.to_digit(10).ok_or_else(|| {
1024                error(format!(
1025                    "Error in parsing: {}\n   Column 5 must be blank.",
1026                    self.line
1027                ))
1028            })?;
1029            count += 100 * IntegerType::try_from(hundreds).unwrap_or(0);
1030        }
1031        Ok(op_frac(FloatType::from(count) / divisions))
1032    }
1033
1034    fn lyrics(&self) -> Option<Vec<String>> {
1035        if self.stage == Stage::One || self.line.chars().count() < 44 {
1036            return None;
1037        }
1038        Some(
1039            self.line
1040                .chars()
1041                .skip(43)
1042                .collect::<String>()
1043                .split('|')
1044                .map(|lyric| lyric.trim().to_string())
1045                .collect(),
1046        )
1047    }
1048
1049    fn beams(&self) -> Option<String> {
1050        if self.stage == Stage::One {
1051            return None;
1052        }
1053        let data = self.columns(25, 31);
1054        (!data.is_empty()).then(|| data.trim_end().to_string())
1055    }
1056
1057    /// `getSlurSymbols`: the slur marks among the notations, in the order
1058    /// they are written; a stage-1 record has none.
1059    fn slur_marks(&self) -> String {
1060        if self.stage == Stage::One {
1061            return String::new();
1062        }
1063        self.notations()
1064            .unwrap_or_default()
1065            .chars()
1066            .filter(|mark| "()[]{}zx".contains(*mark))
1067            .collect()
1068    }
1069
1070    /// `_getAdditionalNotations`: columns 32 to 43.
1071    fn notations(&self) -> Option<String> {
1072        if self.line.chars().count() < 31 {
1073            return None;
1074        }
1075        Some(self.columns(31, 43).trim().to_string())
1076    }
1077
1078    fn articulations(&self) -> Vec<Articulation> {
1079        let Some(notations) = self.notations() else {
1080            return Vec::new();
1081        };
1082        notations
1083            .chars()
1084            .filter_map(|c| {
1085                let kind = match c {
1086                    'A' | 'V' => ArticulationKind::StrongAccent,
1087                    '>' => ArticulationKind::Accent,
1088                    '.' => ArticulationKind::Staccato,
1089                    '_' => ArticulationKind::Tenuto,
1090                    '=' => ArticulationKind::DetachedLegato,
1091                    'i' => ArticulationKind::Spiccato,
1092                    ',' => ArticulationKind::BreathMark,
1093                    _ => return None,
1094                };
1095                Some(Articulation::of_kind(kind))
1096            })
1097            .collect()
1098    }
1099
1100    fn expressions(&self) -> Vec<Expression> {
1101        let Some(notations) = self.notations() else {
1102            return Vec::new();
1103        };
1104        notations
1105            .chars()
1106            .filter_map(|c| match c {
1107                'F' | 'E' => Some(Expression::from(Fermata::new())),
1108                't' => Some(Expression::from(Ornament::of_kind(OrnamentKind::Trill))),
1109                'r' => Some(Expression::from(Ornament::of_kind(OrnamentKind::Turn))),
1110                'M' => Some(Expression::from(Ornament::of_kind(OrnamentKind::Mordent))),
1111                _ => None,
1112            })
1113            .collect()
1114    }
1115
1116    /// `getDynamicObjects`: each mark found once, in music21's order of
1117    /// looking, and taken out of what is left to look in.
1118    fn dynamics(&self) -> Vec<Dynamic> {
1119        let Some(mut data) = self.notations() else {
1120            return Vec::new();
1121        };
1122        let mut out = Vec::new();
1123        for target in [
1124            "ppp", "fff", "pp", "ff", "fp", "mp", "mf", "p", "f", "m", "Z", "Zp", "R",
1125        ] {
1126            let Some(at) = data.find(target) else {
1127                continue;
1128            };
1129            data.replace_range(at..at + target.len(), "");
1130            let value = match target {
1131                "m" => "mp",
1132                "Z" | "Zp" | "R" => "sf",
1133                other => other,
1134            };
1135            out.push(Dynamic::new(value));
1136        }
1137        out
1138    }
1139}
1140
1141/// `_musedataBeamToBeams`.
1142fn beams_of(written: &str) -> Beams {
1143    let mut beams = Beams::default();
1144    for c in written.chars() {
1145        let (kind, direction) = match c {
1146            '[' => (BeamType::Start, None),
1147            ']' => (BeamType::Stop, None),
1148            '=' => (BeamType::Continue, None),
1149            '/' => (BeamType::PartialBeam, Some(BeamDirection::Right)),
1150            '\\' => (BeamType::PartialBeam, Some(BeamDirection::Left)),
1151            _ => continue,
1152        };
1153        beams.append(kind, direction);
1154    }
1155    beams
1156}
1157
1158/// `_musedataRecordListToNoteOrChord`: a note, or a chord of several
1159/// records, what the first record says of it, the dynamics it carries, and
1160/// the tie it leaves for the next.
1161fn note_or_chord(
1162    records: &[Record<'_>],
1163    divisions: FloatType,
1164    previous: Option<Option<TieType>>,
1165) -> Result<(StreamElement, Vec<Dynamic>, Option<TieType>)> {
1166    let first = records[0];
1167    let duration = Duration::new(first.quarter_length(divisions)?)?;
1168    let held = matches!(previous, Some(Some(TieType::Start | TieType::Continue)));
1169    let tie = if first.is_tied() {
1170        Some(if held {
1171            TieType::Continue
1172        } else {
1173            TieType::Start
1174        })
1175    } else if held {
1176        Some(TieType::Stop)
1177    } else {
1178        None
1179    };
1180    let mut pitches = Vec::new();
1181    for record in records {
1182        pitches.push(record.pitch()?);
1183    }
1184    let lyrics = first.lyrics();
1185    let beams = first.beams().map(|written| beams_of(&written));
1186    let element = if pitches.len() == 1 {
1187        let mut note = Note::from_pitch(pitches.remove(0)).with_duration(duration);
1188        for lyric in lyrics.iter().flatten() {
1189            note.add_lyric(lyric, None, false)?;
1190        }
1191        if let Some(beams) = beams {
1192            note.set_beams(beams);
1193        }
1194        note.articulations_mut().extend(first.articulations());
1195        note.expressions_mut().extend(first.expressions());
1196        note.set_tie(tie.map(Tie::new));
1197        StreamElement::Note(note)
1198    } else {
1199        let notes: Vec<Note> = pitches.into_iter().map(Note::from_pitch).collect();
1200        let mut chord = Chord::new(notes)?.with_duration(duration);
1201        for lyric in lyrics.iter().flatten() {
1202            chord.add_lyric(lyric, None, false)?;
1203        }
1204        if let Some(beams) = beams {
1205            chord.set_beams(beams);
1206        }
1207        chord.articulations_mut().extend(first.articulations());
1208        chord.expressions_mut().extend(first.expressions());
1209        for note in chord.notes_mut() {
1210            note.set_tie(tie.map(Tie::new));
1211        }
1212        StreamElement::Chord(chord)
1213    };
1214    Ok((element, first.dynamics(), tie))
1215}
1216
1217/// music21's `base40DeltaToInterval`, refused as a MuseData error.
1218fn base40_interval(delta: i32) -> Result<Interval> {
1219    base40::delta_to_interval(delta).map_err(|refused| error(refused.message()))
1220}
1221
1222#[cfg(test)]
1223mod tests {
1224    use super::*;
1225
1226    #[test]
1227    fn a_base40_distance_is_an_interval_with_its_octaves() {
1228        assert_eq!(base40_interval(-11).unwrap().directed_name(), "m-3");
1229        assert_eq!(base40_interval(23).unwrap().directed_name(), "P5");
1230        assert_eq!(base40_interval(63).unwrap().directed_name(), "P12");
1231        assert!(base40_interval(2).is_err());
1232    }
1233
1234    #[test]
1235    fn a_measure_number_is_every_digit_of_its_record_and_a_pickup_is_0() {
1236        let lines = ["mheavy2 12".to_string(), "C4 1 q".to_string()];
1237        assert_eq!(measure_object(&lines).unwrap().number(), 212);
1238        let lines = ["measure".to_string(), "C4 1 q".to_string()];
1239        assert_eq!(measure_object(&lines).unwrap().number(), 1);
1240        let lines = ["C4 1 q".to_string()];
1241        assert_eq!(measure_object(&lines).unwrap().number(), 0);
1242    }
1243
1244    #[test]
1245    fn a_refusal_says_its_line_measure_and_part() {
1246        let part = |attributes: &str, record: &str| {
1247            format!(
1248                "\n\n\n01/01/01 x\nWK#:1 MV#:1\nsource\nWork\nMovement\nFlute\n\
1249                 1 0\nGroup memberships: score\nscore: part 1 of 1\n\
1250                 & a comment\n&\n{attributes}\nC5     2        q     u\n\
1251                 D5     2        q     u\nmeasure 2\n{record}\n/END\n"
1252            )
1253        };
1254        let error =
1255            from_musedata(&[&part("$  K:0   Q:2   T:2/4   C:4", "E5     x        h")]).unwrap_err();
1256        assert_eq!(error.to_string(), "MuseData error: \"  x\" is not a number");
1257        let mut wanted = Position::line(19).in_part("Flute");
1258        wanted.measure = Some("2".to_string());
1259        assert_eq!(error.position(), Some(&wanted));
1260
1261        let error =
1262            from_musedata(&[&part("$  K:0   Q:2   T:x/4   C:4", "E5     4        h")]).unwrap_err();
1263        assert_eq!(error.position(), Some(&Position::line(15).in_part("Flute")));
1264    }
1265
1266    #[test]
1267    fn beams_are_read_from_their_column() {
1268        let beams = beams_of("[[/");
1269        assert_eq!(beams.len(), 3);
1270    }
1271
1272    #[test]
1273    fn a_dynamic_is_taken_out_before_the_next_is_looked_for() {
1274        let record = Record {
1275            line: "C5     3        e     d  [     pp.",
1276            stage: Stage::Two,
1277            number: None,
1278        };
1279        let values: Vec<String> = record
1280            .dynamics()
1281            .iter()
1282            .map(|dynamic| dynamic.value().to_string())
1283            .collect();
1284        assert_eq!(values, ["pp"]);
1285    }
1286
1287    #[test]
1288    fn ties_stay_in_their_voice_across_a_back() {
1289        let part = "\n\n\n04/16/93 E. Correia\nWK#:581       MV#:3c\n\
1290            Breitkopf & Hartel, Vol. 13\nClarinet Quintet\nTrio II\nPiano\n1 0\n\
1291            Group memberships: score\nscore: part 1 of 1\n\
1292            $  K:0   Q:1   T:2/4   C:4\nmeasure 1\nC5     1        q     u\n\
1293            E5     1-       q     u\nback   2\nC4     1        q     d\n\
1294            G3     1-       q     d\nmeasure 2\nE5     2        h     u\n\
1295            back   2\nG3     1        q     d\nA3     1        q     d\n\
1296            measure 3\nF5     2        h\n/END\n";
1297        let score = from_musedata(&[part]).expect("the part is read");
1298        let ties = |stream: &Stream| -> Vec<Option<&'static str>> {
1299            stream
1300                .notes()
1301                .iter()
1302                .map(|(_, element)| match element {
1303                    StreamElement::Note(note) => note.tie().map(|tie| tie.tie_type().as_str()),
1304                    _ => None,
1305                })
1306                .collect()
1307        };
1308        let parts = score.parts();
1309        let measures = parts[0].measures();
1310        let first = measures[0].voices();
1311        let second = measures[1].voices();
1312        assert_eq!(ties(first[0]), [None, Some("start")]);
1313        assert_eq!(ties(first[1]), [None, Some("start")]);
1314        assert_eq!(ties(second[0]), [Some("stop")]);
1315        assert_eq!(ties(second[1]), [Some("stop"), None]);
1316        assert_eq!(ties(measures[2]), [None]);
1317    }
1318
1319    #[test]
1320    fn windows_line_endings_are_read_as_unix_ones() {
1321        let unix = "Title\nSource\n$ K:0 Q:4 T:4/4 C:4\nC4 4 q\n/END\n";
1322        let windows = unix.replace('\n', "\r\n");
1323        let lines = |text: &str| -> Vec<Vec<String>> {
1324            split_parts(text)
1325                .expect("the parts are read")
1326                .into_iter()
1327                .map(|part| part.lines)
1328                .collect()
1329        };
1330        assert_eq!(lines(&windows), lines(unix));
1331        assert!(
1332            lines(&windows)
1333                .iter()
1334                .flatten()
1335                .all(|line| !line.contains('\r'))
1336        );
1337    }
1338}