1mod comment;
12pub mod harm;
13mod tables;
14mod tandem;
15
16use std::collections::BTreeMap;
17
18use crate::articulations::{Articulation, ArticulationKind};
19use crate::bar::{Barline, BarlineType, RepeatDirection};
20use crate::chord::Chord;
21use crate::clef::{Clef, ClefKind};
22use crate::defaults::{FloatType, IntegerType};
23use crate::duration::{Duration, DurationType, Grace, Tuplet, TupletType};
24use crate::dynamics::Dynamic;
25use crate::error::{Error, Position, Result};
26use crate::expressions::{Expression, Fermata, Ornament, OrnamentKind};
27use crate::instrument::Instrument;
28use crate::key::{Key, KeySignature};
29use crate::makenotation::op_frac;
30use crate::metadata::{Metadata, is_contributor_unique_name};
31use crate::meter::TimeSignature;
32use crate::notation::{BeamDirection, BeamType, StemDirection, Tie, TieType};
33use crate::note::Note;
34use crate::pitch::{Accidental, PitchOptions};
35use crate::rest::Rest;
36use crate::roman::{Minor67Default, RomanNumeral};
37use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
38use crate::tempo::MetronomeMark;
39
40pub use comment::{GlobalComment, SpineComment};
41pub use tables::{INSTRUMENT_CLASSES, INSTRUMENTS};
42pub use tandem::MiscTandem;
43
44use tables::REFERENCE_NAMES;
45
46fn humdrum_error(message: impl Into<String>) -> Error {
47 Error::humdrum(message.into())
48}
49
50const SPINE_PATHS: [&str; 6] = ["*+", "*-", "*^", "*v", "*x", "*"];
52
53#[derive(Clone, Debug, Default)]
55struct Measure {
56 number: IntegerType,
57 suffix: Option<String>,
58 left: Option<Barline>,
59 right: Option<Barline>,
60}
61
62#[allow(clippy::large_enum_variant)]
66#[derive(Clone, Debug)]
67enum Thing {
68 Element(StreamElement),
69 Measure(Measure),
70 Tandem(String),
72 Word(String),
74}
75
76#[derive(Clone, Debug)]
80struct Placed {
81 offset: FloatType,
82 line: usize,
83 priority: usize,
85 voice: Option<usize>,
86 thing: Thing,
87}
88
89impl Placed {
90 fn length(&self) -> FloatType {
91 match &self.thing {
92 Thing::Element(element) => element.quarter_length(),
93 _ => 0.0,
94 }
95 }
96
97 fn class_order(&self) -> i32 {
98 match &self.thing {
99 Thing::Element(element) => element.class_sort_order(),
100 Thing::Measure(_) => -20,
101 _ => 0,
102 }
103 }
104}
105
106fn sort(things: &mut [Placed]) {
108 things.sort_by(|left, right| {
109 left.offset
110 .total_cmp(&right.offset)
111 .then(left.priority.cmp(&right.priority))
112 .then(left.class_order().cmp(&right.class_order()))
113 });
114}
115
116fn highest(things: &[Placed]) -> FloatType {
117 things
118 .iter()
119 .map(|thing| thing.offset + thing.length())
120 .fold(0.0, FloatType::max)
121}
122
123#[derive(Clone, Debug, Default)]
125struct Spine {
126 events: Vec<(String, usize)>,
127 parent: Option<usize>,
128 first_voice: bool,
129 splits: BTreeMap<usize, Vec<usize>>,
131 kind: String,
132 stream: Vec<Placed>,
133}
134
135fn type_of_number(number: u64) -> Option<DurationType> {
137 ((1..=2048).contains(&number) && number.is_power_of_two())
138 .then(|| {
139 DurationType::ALL
140 .into_iter()
141 .find(|kind| kind.quarter_length() == 4.0 / number as FloatType)
142 })
143 .flatten()
144}
145
146fn first_number(text: &str) -> Option<&str> {
147 let start = text.find(|c: char| c.is_ascii_digit())?;
148 let rest = &text[start..];
149 let end = rest
150 .find(|c: char| !c.is_ascii_digit())
151 .unwrap_or(rest.len());
152 Some(&rest[..end])
153}
154
155fn duration_of(contents: &str) -> Result<Duration> {
158 let dots = contents.matches('.').count() as u32;
159 let has_grace = contents.contains('q') || contents.contains('Q');
160 let number = first_number(contents);
161 let rational = contents.contains('%').then(|| {
162 let at = contents.find('%')?;
163 let before = &contents[..at];
164 let start = before
165 .rfind(|c: char| !c.is_ascii_digit())
166 .map_or(0, |index| index + 1);
167 let first = before[start..].parse::<FloatType>().ok()?;
168 let second = first_number(&contents[at + 1..])
169 .filter(|_| contents[at + 1..].starts_with(|c: char| c.is_ascii_digit()))?
170 .parse::<FloatType>()
171 .ok()?;
172 Some((first, second))
173 });
174 let mut duration = if let Some(Some((first, second))) = rational {
175 let plain = Duration::new(op_frac(4.0 * second / first))?;
176 match plain.type_and_dots() {
177 Some((kind, _)) if dots > 0 => Duration::from_type_with_dots(kind, dots),
178 _ => plain,
179 }
180 } else if let Some(number) = number {
181 let value: u64 = number
182 .parse()
183 .map_err(|_| humdrum_error(format!("Could not find a duration in {contents:?}")))?;
184 if value == 0 {
185 let kind = match number {
186 "000" => DurationType::Maxima,
187 "00" => DurationType::Longa,
188 _ => DurationType::Breve,
189 };
190 Duration::from_type_with_dots(kind, dots)
191 } else if let Some(kind) = type_of_number(value) {
192 Duration::from_type_with_dots(kind, dots)
193 } else {
194 let base = 1_u64 << value.ilog2();
197 let kind = type_of_number(base)
198 .ok_or_else(|| humdrum_error(format!("no note value for {value}")))?;
199 let divisor = gcd(value, base);
200 let mut tuplet =
201 Tuplet::new((value / divisor) as u32, (base / divisor) as u32, kind, 0);
202 if dots > 0 {
203 tuplet.set_duration_normal(Some((kind, dots)));
204 }
205 let mut duration = Duration::from_type(kind);
206 duration.append_tuplet(tuplet);
207 duration
208 }
209 } else if has_grace {
210 Duration::from_type(DurationType::Eighth)
211 } else {
212 return Err(humdrum_error(format!(
213 "Could not find a duration in {contents:?}"
214 )));
215 };
216 if has_grace {
217 duration = duration.grace_duration();
218 let mut grace = Grace::new();
219 if contents.matches('q').count() != 1 {
220 grace.set_slash(false);
221 }
222 duration.set_grace(Some(grace));
223 } else if contents.contains('P') {
224 duration = duration.grace_duration();
225 duration.set_grace(Some(Grace::appoggiatura()));
226 }
227 Ok(duration)
228}
229
230fn gcd(mut left: u64, mut right: u64) -> u64 {
231 while right != 0 {
232 (left, right) = (right, left % right);
233 }
234 left
235}
236
237struct Marks {
239 tie: Option<TieType>,
240 expressions: Vec<Expression>,
241 articulations: Vec<Articulation>,
242 stem: StemDirection,
243 beams: Vec<(BeamType, Option<BeamDirection>)>,
244}
245
246fn marks_of(contents: &str) -> Marks {
247 let has = |mark: char| contents.contains(mark);
248 let tie = if has('[') {
249 Some(TieType::Start)
250 } else if has(']') {
251 Some(TieType::Stop)
252 } else if has('_') {
253 Some(TieType::Continue)
254 } else {
255 None
256 };
257 let ornament = |kind: OrnamentKind| Expression::from(Ornament::of_kind(kind));
258 let mut expressions = Vec::new();
259 if has('t') {
260 expressions.push(ornament(OrnamentKind::HalfStepTrill));
261 } else if has('T') {
262 expressions.push(ornament(OrnamentKind::WholeStepTrill));
263 }
264 if has('w') {
265 expressions.push(ornament(OrnamentKind::HalfStepInvertedMordent));
266 } else if has('W') {
267 expressions.push(ornament(OrnamentKind::WholeStepInvertedMordent));
268 } else if has('m') {
269 expressions.push(ornament(OrnamentKind::HalfStepMordent));
270 } else if has('M') {
271 expressions.push(ornament(OrnamentKind::WholeStepMordent));
272 }
273 if has('S') {
274 expressions.push(ornament(OrnamentKind::Turn));
275 } else if has('$') {
276 expressions.push(ornament(OrnamentKind::InvertedTurn));
277 } else if has('R') {
278 expressions.push(ornament(OrnamentKind::Turn));
279 }
280 if has('O') {
281 expressions.push(ornament(OrnamentKind::Ornament));
282 }
283 let mut articulations = Vec::new();
284 let mut articulation = |class: &str| {
285 if let Some(kind) = ArticulationKind::from_class_name(class) {
286 articulations.push(Articulation::of_kind(kind));
287 }
288 };
289 if has('\'') {
290 articulation("Staccato");
291 }
292 if has('"') {
293 articulation("Pizzicato");
294 }
295 if has('`') {
296 articulation("Staccatissimo");
297 }
298 if has('~') {
299 articulation("Tenuto");
300 }
301 if has('^') {
302 articulation("Accent");
303 }
304 if has(';') {
305 expressions.push(Expression::Fermata(Fermata::new()));
306 }
307 if has('v') {
308 articulation("UpBow");
309 } else if has('u') {
310 articulation("DownBow");
311 }
312 let stem = if has('/') {
313 StemDirection::Up
314 } else if has('\\') {
315 StemDirection::Down
316 } else {
317 StemDirection::Unspecified
318 };
319 let mut beams = Vec::new();
320 for (mark, beam, direction) in [
321 ('L', BeamType::Start, None),
322 ('J', BeamType::Stop, None),
323 ('k', BeamType::PartialBeam, Some(BeamDirection::Left)),
324 ('K', BeamType::PartialBeam, Some(BeamDirection::Right)),
325 ] {
326 for _ in 0..contents.matches(mark).count() {
327 beams.push((beam, direction));
328 }
329 }
330 Marks {
331 tie,
332 expressions,
333 articulations,
334 stem,
335 beams,
336 }
337}
338
339fn note_of(contents: &str, given: Option<&Duration>) -> Result<StreamElement> {
341 let duration = match given {
344 Some(duration) => duration.clone(),
345 None => duration_of(contents).unwrap_or_else(|_| Duration::quarter()),
346 };
347 let marks = marks_of(contents);
348 if contents.contains('r') {
349 let mut rest = Rest::new(duration);
350 rest.set_tie(marks.tie.map(Tie::new));
351 *rest.expressions_mut() = marks.expressions;
352 *rest.articulations_mut() = marks.articulations;
353 return Ok(rest.into());
354 }
355 let letters = |c: char| matches!(c, 'a'..='g' | 'A'..='G');
356 let start = contents
357 .find(letters)
358 .ok_or_else(|| humdrum_error(format!("Could not parse {contents} for note information")))?;
359 let name: Vec<char> = contents[start..]
360 .chars()
361 .take_while(|c| letters(*c))
362 .collect();
363 let first = name[0];
364 let count = name.len() as IntegerType;
365 let octave = if first.is_ascii_lowercase() {
366 3 + count
367 } else {
368 4 - count
369 };
370 let mut pitch = PitchOptions::new()
371 .step(first.to_ascii_uppercase())
372 .octave(octave)
373 .build()?;
374 let sharps = contents.matches('#').count() as IntegerType;
375 let flats = contents.matches('-').count() as IntegerType;
376 if sharps > 0 {
377 pitch.set_written_accidental(Some(Accidental::new(sharps)?));
378 } else if flats > 0 {
379 pitch.set_written_accidental(Some(Accidental::new(-flats)?));
380 } else if contents.contains('n') {
381 pitch.set_written_accidental(Some(Accidental::new("n")?));
382 }
383 let mut note = Note::from_pitch(pitch).with_duration(duration);
384 note.set_tie(marks.tie.map(Tie::new));
385 *note.expressions_mut() = marks.expressions;
386 *note.articulations_mut() = marks.articulations;
387 note.set_stem_direction(marks.stem);
388 let mut beams = note.beams().clone();
389 for (beam, direction) in marks.beams {
390 beams.append(beam, direction);
391 }
392 note.set_beams(beams);
393 Ok(note.into())
394}
395
396fn measure_of(contents: &str, has_previous: bool) -> (Measure, Barline) {
399 let mut measure = Measure::default();
400 if let Some(number) = first_number(contents) {
401 measure.number = number.parse().unwrap_or(0);
402 let after = &contents[contents.find(number).unwrap_or(0) + number.len()..];
403 measure.suffix = after
404 .chars()
405 .next()
406 .filter(char::is_ascii_lowercase)
407 .map(String::from);
408 }
409 let bar_type = if contents.contains('-') {
410 Some(BarlineType::None)
411 } else if contents.contains('\'') {
412 Some(BarlineType::Short)
413 } else if contents.contains('`') {
414 Some(BarlineType::Tick)
415 } else if contents.contains("||") || contents.contains("==") {
416 Some(BarlineType::Double)
417 } else if contents.contains("!!") {
418 Some(BarlineType::HeavyHeavy)
419 } else if contents.contains("|!") {
420 Some(BarlineType::Final)
421 } else if contents.contains("!|") {
422 Some(BarlineType::HeavyLight)
423 } else if contents.contains('|') {
424 Some(BarlineType::Regular)
425 } else {
426 None
427 };
428 let chars: Vec<char> = contents.chars().collect();
429 let beside = |colon_first: bool| {
430 chars.windows(2).any(|pair| {
431 let (colon, bar) = if colon_first {
432 (pair[0], pair[1])
433 } else {
434 (pair[1], pair[0])
435 };
436 colon == ':' && matches!(bar, '|' | '!' | '=')
437 })
438 };
439 let both = contents.matches(':').count() > 1;
440 let end_repeat = both || beside(true);
441 let start_repeat = both || beside(false);
442 let make = |direction: Option<RepeatDirection>| {
443 let mut barline = match direction {
444 Some(direction) => Barline::repeat(direction, None),
445 None => Barline::new(BarlineType::Regular),
446 };
447 if let Some(bar_type) = bar_type {
448 barline.set_bar_type(bar_type);
449 }
450 barline
451 };
452 let right = make(end_repeat.then_some(RepeatDirection::End));
453 if has_previous {
454 if start_repeat {
455 measure.left = Some(make(Some(RepeatDirection::Start)));
456 }
457 } else {
458 measure.left = Some(if start_repeat {
459 make(Some(RepeatDirection::Start))
460 } else {
461 right.clone()
462 });
463 }
464 (measure, right)
465}
466
467fn tandem_of(tandem: &str) -> Result<Option<Thing>> {
470 let element = |element: StreamElement| Ok(Some(Thing::Element(element)));
471 if SPINE_PATHS.contains(&tandem) {
472 return Ok(None);
473 }
474 if let Some(kind) = tandem.strip_prefix("*clef") {
475 let clef = match kind {
476 "-" => Clef::of_kind(ClefKind::NoClef),
477 "X" => Clef::of_kind(ClefKind::PercussionClef),
478 "Gv2" | "Gv" => Clef::of_kind(ClefKind::Treble8vbClef),
479 "G^2" | "G^" => Clef::of_kind(ClefKind::Treble8vaClef),
480 "Fv4" | "Fv" => Clef::of_kind(ClefKind::Bass8vbClef),
481 other => Clef::from_string(other, 0)
482 .map_err(|_| humdrum_error(format!("Unknown clef type {tandem} found")))?,
483 };
484 return element(clef.into());
485 }
486 if let Some(text) = tandem.strip_prefix("*MM") {
487 let mark = match text.parse::<FloatType>() {
488 Ok(number) => MetronomeMark::new(number),
489 Err(_) => {
490 let text = text.strip_prefix('[').unwrap_or(text);
491 let text = text.trim_end();
492 MetronomeMark::from_text(text.strip_suffix(']').unwrap_or(text))
493 }
494 };
495 return element(mark.into());
496 }
497 if let Some(meter) = tandem.strip_prefix("*M") {
498 let digits = |text: &str| text.chars().take_while(char::is_ascii_digit).count();
499 let top = digits(meter);
500 let bottom = meter
501 .get(top + 1..)
502 .filter(|_| top > 0 && meter[top..].starts_with('/'))
503 .map(|rest| &rest[..digits(rest)])
504 .filter(|bottom| !bottom.is_empty())
505 .ok_or_else(|| humdrum_error(format!("Incorrect meter: {tandem} found")))?;
506 let numerator: u32 = meter[..top]
507 .parse()
508 .map_err(|_| humdrum_error(format!("Incorrect meter: {tandem} found")))?;
509 let signature = match bottom {
510 "0" => TimeSignature::new(numerator * 2, 1)?,
511 "00" => TimeSignature::new(numerator * 4, 1)?,
512 "000" => TimeSignature::new(numerator * 8, 1)?,
513 _ => TimeSignature::from_ratio_string(meter)?,
514 };
515 return element(signature.into());
516 }
517 let instrument = |table: &[(&str, &str)], code: &str| -> Option<Instrument> {
518 let class = table.iter().find(|(held, _)| *held == code)?.1;
519 Instrument::of_kind(class).ok()
520 };
521 if let Some(class) = tandem.strip_prefix("*IC") {
522 return Ok(Some(match instrument(INSTRUMENT_CLASSES, class) {
523 Some(instrument) => Thing::Element(instrument.into()),
524 None => Thing::Tandem(class.to_string()),
525 }));
526 }
527 if tandem.starts_with("*IG") || tandem.starts_with("*ITr") {
528 return Ok(Some(Thing::Tandem(tandem.to_string())));
529 }
530 if let Some(code) = tandem.strip_prefix("*I") {
531 return Ok(Some(match instrument(INSTRUMENTS, code) {
532 Some(instrument) => Thing::Element(instrument.into()),
533 None => Thing::Tandem(code.to_string()),
534 }));
535 }
536 if tandem.starts_with("*k") {
537 let mut sharps = tandem.matches('#').count() as IntegerType;
538 if sharps == 0 {
539 sharps = -(tandem.matches('-').count() as IntegerType);
540 }
541 return element(KeySignature::new(sharps).into());
542 }
543 if let Some(key) = tandem.strip_suffix(':') {
544 return element(StreamElement::Key(Key::from_tonic(&key[1..])?));
545 }
546 Ok(Some(Thing::Tandem(tandem.to_string())))
547}
548
549#[derive(Default)]
552struct KernState {
553 tuplet: Option<TupletRun>,
556 last_note: Option<usize>,
558 beams: usize,
560}
561
562struct TupletRun {
565 length: FloatType,
567 wanted: FloatType,
569}
570
571fn with_duration(element: &mut StreamElement, change: impl FnOnce(&mut Duration)) {
572 let Some(mut duration) = element.duration().cloned() else {
573 return;
574 };
575 change(&mut duration);
576 match element {
577 StreamElement::Note(note) => note.set_duration(duration),
578 StreamElement::Chord(chord) => chord.set_duration(duration),
579 StreamElement::Rest(rest) => rest.set_duration(duration),
580 _ => {}
581 }
582}
583
584fn mark_first_tuplet(element: &mut StreamElement, kind: TupletType) {
585 with_duration(element, |duration| {
586 let mut tuplets = duration.tuplets();
587 if let Some(first) = tuplets.first_mut() {
588 first.set_tuplet_type(Some(kind));
589 }
590 duration.set_tuplets(tuplets);
591 });
592}
593
594impl KernState {
595 fn beam(&mut self, element: &mut StreamElement) {
598 let beams = match element {
599 StreamElement::Note(note) => note.beams().clone(),
600 StreamElement::Chord(chord) => chord.beams().clone(),
601 _ => return,
602 };
603 if self.beams != 0 && beams.is_empty() {
604 let mut carried = beams;
605 for _ in 0..self.beams {
606 carried.append(BeamType::Continue, None);
607 }
608 match element {
609 StreamElement::Note(note) => note.set_beams(carried),
610 StreamElement::Chord(chord) => chord.set_beams(carried),
611 _ => {}
612 }
613 } else if let Some(first) = beams.beams().first() {
614 if first.beam_type() == Some(BeamType::Stop) {
615 self.beams = 0;
616 } else {
617 self.beams += beams
618 .beams()
619 .iter()
620 .filter(|beam| beam.beam_type() != Some(BeamType::Stop))
621 .count();
622 }
623 }
624 }
625
626 fn tuplet(&mut self, stream: &mut [Placed], element: &mut StreamElement) {
630 let Some(duration) = element.duration().cloned() else {
631 return;
632 };
633 if duration.is_grace() {
634 return;
635 }
636 let tuplets = duration.tuplets();
637 let Some(run) = &mut self.tuplet else {
638 if !tuplets.is_empty() {
639 self.tuplet = Some(TupletRun {
640 length: duration.quarter_length(),
641 wanted: tuplets[0].total_tuplet_length(),
642 });
643 mark_first_tuplet(element, TupletType::Start);
644 }
645 return;
646 };
647 if tuplets.is_empty() {
648 self.tuplet = None;
649 if let Some(last) = self.last_note
650 && let Thing::Element(last) = &mut stream[last].thing
651 {
652 mark_first_tuplet(last, TupletType::Stop);
653 }
654 return;
655 }
656
657 run.length = op_frac(run.length + duration.quarter_length());
658 if run.length >= run.wanted && run.length % run.wanted == 0.0 {
659 mark_first_tuplet(element, TupletType::Stop);
660 self.tuplet = None;
661 }
662 }
663}
664
665fn chord_of(contents: &str) -> Result<StreamElement> {
667 let mut notes: Vec<Note> = Vec::new();
668 let mut first: Option<(Duration, String)> = None;
669 for token in contents.split_whitespace() {
670 let length: String = token
671 .chars()
672 .filter(|c| c.is_ascii_digit() || matches!(c, '.' | '%'))
673 .collect();
674 let bare: String = token
675 .chars()
676 .filter(|c| !(c.is_ascii_digit() || matches!(c, '.' | '%')))
677 .collect();
678 let element = match &first {
679 Some((duration, written)) if length.is_empty() || length == *written => {
680 note_of(&bare, Some(duration))?
681 }
682 _ => {
683 let element = note_of(token, None)?;
684 if first.is_none()
685 && let Some(duration) = element.duration()
686 {
687 first = Some((duration.clone(), length));
688 }
689 element
690 }
691 };
692 if let StreamElement::Note(note) = element {
693 notes.push(note);
694 }
695 }
696 let last_beams = notes
697 .last()
698 .map(|note| note.beams().clone())
699 .ok_or_else(|| humdrum_error("a chord with no notes in it"))?;
700 let duration = first.map(|(duration, _)| duration).unwrap_or_default();
701 let mut chord = Chord::new(notes)?.with_duration(duration);
702 chord.set_beams(last_beams);
703 Ok(chord.into())
704}
705
706fn append(stream: &mut Vec<Placed>, line: usize, priority: usize, thing: Thing) -> usize {
708 let offset = highest(stream);
709 stream.push(Placed {
710 offset,
711 line,
712 priority,
713 voice: None,
714 thing,
715 });
716 stream.len() - 1
717}
718
719fn parse_kern(events: &[(String, usize)]) -> Vec<Placed> {
721 let mut stream: Vec<Placed> = Vec::new();
722 let mut state = KernState::default();
723 let mut last_measure: Option<usize> = None;
725 for (contents, line) in events {
726 let made: Result<Option<Thing>> = (|| {
727 if contents == "." || contents.is_empty() {
728 return Ok(None);
729 }
730 if contents.starts_with('*') {
731 return tandem_of(contents);
732 }
733 if contents.starts_with('=') {
734 let (measure, right) = measure_of(contents, true);
737 if let Some(previous) = last_measure
738 && let Thing::Measure(previous) = &mut stream[previous].thing
739 {
740 previous.right = Some(right);
741 }
742 return Ok(Some(Thing::Measure(measure)));
743 }
744 if contents.starts_with('!') {
745 let text = contents.trim_start_matches('!');
748 let text = text.strip_prefix(char::is_whitespace).unwrap_or(text);
749 if text.is_empty() {
750 return Ok(None);
751 }
752 return Ok(Some(Thing::Element(SpineComment::new(text).into())));
753 }
754 let mut element = if contents.contains(' ') {
755 chord_of(contents)?
756 } else {
757 note_of(contents, None)?
758 };
759 state.beam(&mut element);
760 state.tuplet(&mut stream, &mut element);
761 Ok(Some(Thing::Element(element)))
762 })();
763 let Ok(Some(thing)) = made else {
766 continue;
767 };
768 let is_measure = matches!(thing, Thing::Measure(_));
769 let sounds = matches!(
770 &thing,
771 Thing::Element(
772 element @ (StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_))
773 ) if !element.duration().is_some_and(Duration::is_grace)
774 );
775 let index = append(&mut stream, *line, *line, thing);
776 if is_measure {
777 last_measure = Some(index);
778 }
779 if sounds {
780 state.last_note = Some(index);
781 }
782 }
783 stream
784}
785
786fn parse_other(events: &[(String, usize)], dynamics: bool) -> Vec<Placed> {
789 let mut stream = Vec::new();
790 for (contents, line) in events {
791 let thing = if contents == "." || contents.is_empty() {
792 continue;
793 } else if contents.starts_with('*') {
794 if SPINE_PATHS.contains(&contents.as_str()) {
795 continue;
796 }
797 Thing::Tandem(contents.clone())
798 } else if contents.starts_with('=') || contents.starts_with('!') {
799 continue;
800 } else if dynamics {
801 if contents.starts_with('<') || contents.starts_with('>') {
802 continue;
803 }
804 Thing::Element(Dynamic::new(contents.as_str()).into())
805 } else {
806 Thing::Word(contents.clone())
807 };
808 append(&mut stream, *line, 0, thing);
809 }
810 stream
811}
812
813fn parse_harm(events: &[(String, usize)]) -> Result<Vec<Placed>> {
822 let mut stream = Vec::new();
823 let mut key = Key::from_tonic("C")?;
826 for (contents, line) in events {
827 if contents == "." || contents.is_empty() {
828 continue;
829 }
830 if contents.starts_with('*') {
831 if SPINE_PATHS.contains(&contents.as_str()) {
832 continue;
833 }
834 match contents.strip_suffix(':') {
835 Some(named) => key = Key::from_tonic(&named[1..])?,
836 None => {
839 append(&mut stream, *line, 0, Thing::Tandem(contents.clone()));
840 }
841 }
842 continue;
843 }
844 if contents.starts_with('=') || contents.starts_with('!') {
845 continue;
846 }
847 let Some(figure) = harm::convert_harm_to_roman(contents) else {
848 continue;
849 };
850 let numeral = RomanNumeral::with_minor_defaults(
851 &figure,
852 key.clone(),
853 Minor67Default::Flat,
854 Minor67Default::Sharp,
855 )?;
856 let chord = numeral.to_chord()?.with_numeral(numeral);
857 append(&mut stream, *line, 0, Thing::Element(chord.into()));
858 }
859 Ok(stream)
860}
861
862fn spines_of(lines: &[(usize, Vec<String>)], width: usize) -> Result<Vec<Spine>> {
865 let mut spines: Vec<Spine> = Vec::new();
866 let mut current: Vec<Option<usize>> = Vec::new();
867 for (line, cells) in lines {
868 if current.len() < width {
869 current.resize(width, None);
870 }
871 for (column, cell) in cells.iter().enumerate().take(width) {
872 let spine = match current[column] {
873 Some(spine) => spine,
874 None => {
875 spines.push(Spine::default());
876 current[column] = Some(spines.len() - 1);
877 spines.len() - 1
878 }
879 };
880 spines[spine].events.push((cell.clone(), *line));
881 }
882 if !cells
883 .iter()
884 .any(|cell| SPINE_PATHS.contains(&cell.as_str()))
885 {
886 continue;
887 }
888 let mut next: Vec<Option<usize>> = Vec::new();
889 let mut merging = false;
890 let mut merge_parent: Option<usize> = None;
891 let mut exchange: Option<usize> = None;
892 for (column, spine) in current.clone().into_iter().enumerate().take(width) {
893 let Some(cell) = cells.get(column) else {
894 if spine.is_some() {
895 next.push(spine);
896 }
897 continue;
898 };
899 let Some(spine) = spine else {
900 return Err(humdrum_error(format!(
901 "spine-path token at line {line} has no active spine"
902 ))
903 .placed(|| Position::line(*line)));
904 };
905 match cell.as_str() {
906 "*-" => {}
907 "*^" => {
908 let mut voices = Vec::new();
909 for first in [true, false] {
910 spines.push(Spine {
911 parent: Some(spine),
912 first_voice: first,
913 ..Spine::default()
914 });
915 voices.push(spines.len() - 1);
916 next.push(Some(spines.len() - 1));
917 }
918 spines[spine].splits.insert(*line, voices);
919 }
920 "*v" => {
921 if merging {
922 let parent = spines[spine].parent.or(merge_parent);
923 match parent {
924 Some(parent) => next.push(Some(parent)),
925 None => {
926 spines.push(Spine::default());
927 next.push(Some(spines.len() - 1));
928 }
929 }
930 merging = false;
931 merge_parent = None;
932 } else {
933 merging = true;
934 merge_parent = spines[spine].parent;
935 }
936 }
937 "*x" => match exchange.take() {
938 None => exchange = Some(spine),
939 Some(waiting) => {
940 next.push(Some(spine));
941 next.push(Some(waiting));
942 }
943 },
944 _ => next.push(Some(spine)),
945 }
946 }
947 if exchange.is_some() {
948 return Err(humdrum_error(format!(
949 "ProtoSpine found with unpaired exchange instruction at line {line}"
950 ))
951 .placed(|| Position::line(*line)));
952 }
953 current = next;
954 }
955 Ok(spines)
956}
957
958fn kind_of(spines: &[Spine], index: usize) -> Result<String> {
960 let mut at = Some(index);
961 while let Some(spine) = at {
962 if let Some(kind) = spines[spine]
963 .events
964 .iter()
965 .find_map(|(contents, _)| contents.strip_prefix("**"))
966 {
967 return Ok(kind.to_string());
968 }
969 at = spines[spine].parent;
970 }
971 Err(humdrum_error(format!(
972 "Could not determine spineType for spine with id {index}"
973 )))
974}
975
976fn insert_voices(spines: &mut [Spine], index: usize) {
979 let mut points: Vec<usize> = spines[index].splits.keys().copied().collect();
980 if points.is_empty() {
981 return;
982 }
983 let old = std::mem::take(&mut spines[index].stream);
984 let mut new: Vec<Placed> = Vec::new();
985 let insert = |new: &mut Vec<Placed>, spines: &[Spine], point: usize| {
986 let start = highest(new);
987 for (number, child) in spines[index].splits[&point].iter().enumerate() {
988 for thing in &spines[*child].stream {
989 if !spines[*child].first_voice && matches!(thing.thing, Thing::Measure(_)) {
990 continue;
991 }
992 let mut thing = thing.clone();
993 thing.offset += start;
994 thing.voice.get_or_insert(number + 1);
995 new.push(thing);
996 }
997 }
998 };
999 let mut last: Option<usize> = None;
1000 for thing in old {
1001 let mut at = 0;
1004 while at < points.len() {
1005 let point = points[at];
1006 if last.is_some_and(|last| last > point) || thing.line < point {
1007 at += 1;
1008 continue;
1009 }
1010 insert(&mut new, spines, point);
1011 points.remove(at);
1012 at += 1;
1013 }
1014 last = Some(thing.line);
1015 let offset = highest(&new);
1016 new.push(Placed { offset, ..thing });
1017 }
1018 for point in points {
1019 insert(&mut new, spines, point);
1020 }
1021 spines[index].stream = new;
1022}
1023
1024struct Gathered {
1026 measure: Measure,
1027 offset: FloatType,
1028 priority: usize,
1030 things: Vec<Placed>,
1031}
1032
1033fn into_measures(mut stream: Vec<Placed>) -> (Vec<Gathered>, Vec<Placed>) {
1036 sort(&mut stream);
1037 let mut measures: Vec<Gathered> = Vec::new();
1038 let mut loose: Vec<Placed> = Vec::new();
1039 let mut current = Gathered {
1040 measure: Measure::default(),
1041 offset: 0.0,
1042 priority: 0,
1043 things: Vec::new(),
1044 };
1045 let mut number = 0;
1046 let mut has_one = false;
1047 for thing in stream {
1048 match thing.thing {
1049 Thing::Measure(measure) => {
1050 if number != 0 || !current.things.is_empty() {
1051 measures.push(current);
1052 }
1053 number = measure.number;
1054 has_one |= number == 1;
1055 current = Gathered {
1056 measure,
1057 offset: thing.offset,
1058 priority: thing.priority,
1059 things: Vec::new(),
1060 };
1061 }
1062 _ => {
1063 if number != 0 || thing.length() != 0.0 {
1064 let offset = thing.offset - current.offset;
1065 current.things.push(Placed { offset, ..thing });
1066 } else {
1067 loose.push(thing);
1068 }
1069 }
1070 }
1071 }
1072 if !current.things.is_empty() {
1073 measures.push(current);
1074 }
1075 if let Some(first) = measures.first_mut() {
1076 if !has_one {
1077 first.measure.number = 1;
1078 }
1079 let (moved, kept): (Vec<Placed>, Vec<Placed>) = loose
1082 .into_iter()
1083 .partition(|thing| thing.offset == 0.0 && matches!(thing.thing, Thing::Element(_)));
1084 first.things.extend(moved);
1085 loose = kept;
1086 }
1087 (measures, loose)
1088}
1089
1090fn fix_tuplets(things: &mut [Placed]) {
1093 let tuplets = |thing: &Placed| match &thing.thing {
1094 Thing::Element(
1095 element @ (StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_)),
1096 ) => Some(
1097 element
1098 .duration()
1099 .map(Duration::tuplets)
1100 .unwrap_or_default(),
1101 ),
1102 _ => None,
1103 };
1104 let mut groups: Vec<Vec<usize>> = Vec::new();
1105 let mut current: Vec<usize> = Vec::new();
1106 for (index, thing) in things.iter().enumerate() {
1107 let Some(held) = tuplets(thing) else {
1108 continue;
1109 };
1110 let Some(first) = held.first() else {
1111 if !current.is_empty() {
1112 groups.push(std::mem::take(&mut current));
1113 }
1114 continue;
1115 };
1116 current.push(index);
1117 if first.tuplet_type() == Some(TupletType::Stop) {
1118 groups.push(std::mem::take(&mut current));
1119 }
1120 }
1121 if !current.is_empty() {
1122 groups.push(current);
1123 }
1124 let ordinal = |kind: DurationType| DurationType::ALL.iter().position(|held| *held == kind);
1125 for group in groups {
1126 let Some(first) = tuplets(&things[group[0]]).and_then(|held| held.into_iter().next())
1127 else {
1128 continue;
1129 };
1130 let total = op_frac(first.total_tuplet_length());
1131 let mut length = 0.0;
1132 let mut smallest: Option<usize> = None;
1133 let mut largest: Option<usize> = None;
1134 for index in &group {
1135 length = op_frac(length + things[*index].length());
1136 let Some(kind) = tuplets(&things[*index])
1137 .and_then(|held| held.into_iter().next())
1138 .and_then(|tuplet| ordinal(tuplet.duration_type()))
1139 else {
1140 continue;
1141 };
1142 smallest = Some(smallest.map_or(kind, |held| held.max(kind)));
1143 largest = Some(largest.map_or(kind, |held| held.min(kind)));
1144 }
1145 if length == total || total == 0.0 {
1146 continue;
1147 }
1148 let excess = op_frac(length / total);
1149 let inverse = op_frac(1.0 / excess);
1150 let chosen = if excess.fract() == 0.0 {
1151 largest
1152 } else if inverse.fract() == 0.0 {
1153 smallest
1154 } else {
1155 None
1156 };
1157 let Some(kind) = chosen.map(|index| DurationType::ALL[index]) else {
1158 continue;
1159 };
1160 for index in group {
1161 if let Thing::Element(element) = &mut things[index].thing {
1162 with_duration(element, |duration| {
1163 let mut held = duration.tuplets();
1164 if let Some(first) = held.first_mut() {
1165 let normal = first.duration_normal().map_or(0, |(_, dots)| dots);
1166 let actual = first.duration_actual().map_or(0, |(_, dots)| dots);
1167 first.set_duration_normal(Some((kind, normal)));
1168 first.set_duration_actual(Some((kind, actual)));
1169 }
1170 duration.set_tuplets(held);
1171 });
1172 }
1173 }
1174 }
1175}
1176
1177fn measure_stream(gathered: Gathered) -> Stream {
1180 let mut things = gathered.things;
1181 sort(&mut things);
1182 fix_tuplets(&mut things);
1183 let lowest = things
1184 .iter()
1185 .find(|thing| thing.voice == Some(1))
1186 .map(|thing| thing.offset);
1187 let mut voices: BTreeMap<usize, Vec<Placed>> = BTreeMap::new();
1188 let mut own: Vec<Placed> = Vec::new();
1189 for thing in things {
1190 match (lowest, thing.voice) {
1191 (Some(lowest), Some(voice)) => voices.entry(voice).or_default().push(Placed {
1192 offset: thing.offset - lowest,
1193 ..thing
1194 }),
1195 _ => own.push(thing),
1196 }
1197 }
1198 let events = |things: Vec<Placed>| -> Vec<(FloatType, usize, i32, StreamElement)> {
1199 things
1200 .into_iter()
1201 .filter_map(|thing| {
1202 let priority = thing.priority;
1203 let element = element_of(thing.thing, priority)?;
1204 Some((thing.offset, priority, element.class_sort_order(), element))
1205 })
1206 .collect()
1207 };
1208 let mut held = events(own);
1209 for (_, things) in voices {
1210 let voice = Stream::with_kind(StreamKind::Voice).with_events(
1211 events(things)
1212 .into_iter()
1213 .map(|(offset, priority, _, element)| placed_event(offset, priority, element))
1214 .collect(),
1215 );
1216 held.push((lowest.unwrap_or(0.0), 0, 5, voice.into()));
1217 }
1218 held.sort_by(|left, right| {
1219 left.0
1220 .total_cmp(&right.0)
1221 .then(left.1.cmp(&right.1))
1222 .then(left.2.cmp(&right.2))
1223 });
1224 let mut measure = Stream::with_kind(StreamKind::Measure).with_events(
1225 held.into_iter()
1226 .map(|(offset, priority, _, element)| placed_event(offset, priority, element))
1227 .collect(),
1228 );
1229 measure.set_number(gathered.measure.number);
1230 measure.set_number_suffix(gathered.measure.suffix);
1231 measure.set_left_barline(gathered.measure.left);
1232 measure.set_right_barline(gathered.measure.right);
1233 measure
1234}
1235
1236fn placed_event(offset: FloatType, priority: usize, element: StreamElement) -> StreamEvent {
1238 StreamEvent::new(offset, element).with_priority(i32::try_from(priority).unwrap_or(i32::MAX))
1239}
1240
1241fn element_of(thing: Thing, priority: usize) -> Option<StreamElement> {
1245 match thing {
1246 Thing::Element(element) => Some(element),
1247 Thing::Tandem(tandem) => Some(MiscTandem::new(tandem, priority as i32).into()),
1248 Thing::Measure(_) | Thing::Word(_) => None,
1249 }
1250}
1251
1252fn pad_pickup(measure: &mut Stream, length: FloatType) {
1256 let opening = measure
1257 .events()
1258 .iter()
1259 .find_map(|event| match event.element() {
1260 StreamElement::TimeSignature(meter) if event.offset() == 0.0 => Some(meter),
1261 _ => None,
1262 });
1263 let Some(meter) = opening else {
1264 return;
1265 };
1266 let bar = meter.bar_duration().quarter_length();
1267 if length < bar {
1268 measure.set_padding_left(op_frac(bar - length));
1269 }
1270}
1271
1272fn staves_of(things: &[Placed]) -> Vec<IntegerType> {
1274 things
1275 .iter()
1276 .find_map(|thing| match &thing.thing {
1277 Thing::Tandem(tandem) => tandem.strip_prefix("*staff"),
1278 _ => None,
1279 })
1280 .map(|staves| {
1281 staves
1282 .split('/')
1283 .filter_map(|staff| staff.parse().ok())
1284 .collect()
1285 })
1286 .unwrap_or_default()
1287}
1288
1289fn reference(metadata: &mut Metadata, line: &str) -> Result<()> {
1292 let text = line.trim_start_matches('!');
1293 let (code, value) = text
1294 .split_once(':')
1295 .ok_or_else(|| humdrum_error(format!("a reference line with no code: {line}")))?;
1296 let code = code.trim().replace("@@", "@");
1297 let code = code.split('@').next().unwrap_or("");
1298 let value = value.trim();
1299 match REFERENCE_NAMES.iter().find(|(held, _)| *held == code) {
1300 Some((_, "")) => metadata.add_text(format!("humdrum:{code}"), value),
1301 Some((_, name)) if is_contributor_unique_name(name) => {
1302 metadata.add_contributor(*name, value);
1303 }
1304 Some((_, name)) => metadata.add_text(*name, value),
1305 None => metadata.add_text(code, value),
1306 }
1307 Ok(())
1308}
1309
1310fn score_of(lines: &[&str]) -> Result<Stream> {
1312 let mut metadata = Metadata::new();
1313 let mut rows: Vec<(usize, Vec<String>)> = Vec::new();
1314 let mut globals: Vec<GlobalLine> = Vec::new();
1315 let mut run = 0;
1316 let mut width = 0;
1317 for (index, line) in lines.iter().enumerate() {
1318 let line = line.trim_end();
1319 if line.is_empty() {
1320 run = 0;
1321 continue;
1322 }
1323 if line.starts_with("!!") {
1324 run += 1;
1325 } else {
1326 run = 0;
1327 }
1328 if line.starts_with("!!!") {
1329 reference(&mut metadata, line)
1330 .map_err(|error| error.placed(|| Position::line(index + 1)))?;
1331 } else if line.starts_with("!!") {
1332 globals.push(GlobalLine {
1333 line: index + 1,
1334 run,
1335 comment: comment_text(line).to_string(),
1336 });
1337 } else {
1338 let cells: Vec<String> = line
1339 .split('\t')
1340 .enumerate()
1341 .filter(|(place, cell)| *place == 0 || !cell.is_empty())
1342 .map(|(_, cell)| cell.to_string())
1343 .collect();
1344 width = width.max(cells.len());
1345 rows.push((index + 1, cells));
1346 }
1347 }
1348 let mut spines = spines_of(&rows, width)?;
1349 for index in 0..spines.len() {
1350 spines[index].kind = kind_of(&spines, index)?;
1351 }
1352 for spine in &mut spines {
1353 spine.stream = match spine.kind.as_str() {
1354 "kern" => parse_kern(&spine.events),
1355 "dynam" => parse_other(&spine.events, true),
1356 "harm" => parse_harm(&spine.events)?,
1357 _ => parse_other(&spine.events, false),
1358 };
1359 }
1360 let places = line_places(&spines);
1361 for index in 0..spines.len() {
1362 if spines[index].parent.is_none() {
1363 insert_voices(&mut spines, index);
1364 }
1365 }
1366
1367 let mut parts: Vec<(usize, Vec<Gathered>, Vec<Placed>)> = Vec::new();
1369 for (index, spine) in spines.iter().enumerate() {
1370 if spine.parent.is_none() && spine.kind == "kern" {
1371 let (measures, loose) = into_measures(spine.stream.clone());
1372 parts.push((index, measures, loose));
1373 }
1374 }
1375 let staff_of = |loose: &[Placed]| -> Option<IntegerType> {
1378 loose.iter().find_map(|thing| match &thing.thing {
1379 Thing::Tandem(tandem) => tandem
1380 .strip_prefix("*staff")
1381 .and_then(|staff| staff.parse().ok()),
1382 _ => None,
1383 })
1384 };
1385 for spine in spines
1386 .iter()
1387 .filter(|spine| spine.parent.is_none() && spine.kind != "kern")
1388 {
1389 let staves = staves_of(&spine.stream);
1390 let is_words = matches!(spine.kind.as_str(), "lyrics" | "text");
1391 let is_marks = matches!(spine.kind.as_str(), "dynam" | "harm");
1392 if !is_marks && !is_words {
1393 continue;
1394 }
1395 for staff in staves {
1396 let Some((_, measures, _)) = parts
1397 .iter_mut()
1398 .find(|(_, _, loose)| staff_of(loose) == Some(staff))
1399 else {
1400 if is_marks {
1401 return Err(humdrum_error(format!(
1402 "no staff {staff} for the {} spine",
1403 spine.kind
1404 )));
1405 }
1406 continue;
1407 };
1408 for thing in &spine.stream {
1409 for measure in measures.iter_mut() {
1410 let found = measure
1411 .things
1412 .iter()
1413 .position(|held| held.priority == thing.line);
1414 match (&thing.thing, found) {
1415 (
1416 Thing::Element(
1417 mark @ (StreamElement::Dynamic(_) | StreamElement::Chord(_)),
1418 ),
1419 Some(found),
1420 ) => {
1421 let offset = measure.things[found].offset;
1422 let priority = match mark {
1428 StreamElement::Chord(_) => thing.line,
1429 _ => 0,
1430 };
1431 measure.things.push(Placed {
1432 offset,
1433 line: thing.line,
1434 priority,
1435 voice: None,
1436 thing: Thing::Element(mark.clone()),
1437 });
1438 break;
1439 }
1440 (Thing::Word(word), Some(_)) => {
1441 for held in &mut measure.things {
1442 if held.priority != thing.line {
1443 continue;
1444 }
1445 match &mut held.thing {
1446 Thing::Element(StreamElement::Note(note)) => {
1447 note.set_lyric(Some(word))?;
1448 }
1449 Thing::Element(StreamElement::Rest(rest)) => {
1450 rest.set_lyric(Some(word));
1451 }
1452 Thing::Element(StreamElement::Chord(chord)) => {
1453 if let Some(singer) = chord.notes_mut().first_mut() {
1454 singer.set_lyric(Some(word))?;
1455 }
1456 }
1457 _ => {}
1458 }
1459 }
1460 }
1461 _ => {}
1462 }
1463 }
1464 }
1465 }
1466 }
1467
1468 let mut events = Vec::new();
1471 for (spine, measures, loose) in parts.into_iter().rev() {
1472 let mut held: Vec<StreamEvent> = loose
1475 .into_iter()
1476 .filter(|thing| matches!(thing.thing, Thing::Tandem(_)))
1477 .filter_map(|thing| {
1478 let element = element_of(thing.thing, thing.priority)?;
1479 Some(placed_event(thing.offset, thing.priority, element))
1480 })
1481 .collect();
1482 let mut offset = 0.0;
1483 for (index, gathered) in measures.into_iter().enumerate() {
1484 let priority = gathered.priority;
1485 let mut measure = measure_stream(gathered);
1486 let length = measure.end_offset();
1487 if index == 0 {
1488 pad_pickup(&mut measure, length);
1489 }
1490 held.push(placed_event(offset, priority, measure.into()));
1491 offset = op_frac(offset + length);
1492 }
1493 let mut part = Stream::with_kind(StreamKind::Part).with_events(held);
1494 part.set_id(Some(format!("spine_{spine}")));
1496 events.push(StreamEvent::new(0.0, part));
1497 }
1498 place_comments(&mut events, &globals, &places);
1499 let mut score = Stream::with_kind(StreamKind::Score).with_events(events);
1500 score.set_metadata(Some(metadata));
1501 Ok(score)
1502}
1503
1504struct GlobalLine {
1507 line: usize,
1508 run: usize,
1509 comment: String,
1510}
1511
1512fn comment_text(line: &str) -> &str {
1515 let text = line.trim_start_matches('!');
1516 text.strip_prefix(char::is_whitespace).unwrap_or(text)
1517}
1518
1519fn line_places(spines: &[Spine]) -> BTreeMap<usize, (FloatType, usize)> {
1523 let mut places = BTreeMap::new();
1524 let tops = spines.iter().filter(|spine| spine.parent.is_none());
1525 for placed in tops.flat_map(|spine| &spine.stream) {
1526 if matches!(placed.thing, Thing::Measure(_)) {
1527 continue;
1528 }
1529 places
1530 .entry(placed.line)
1531 .or_insert((placed.offset, placed.priority));
1532 }
1533 places
1534}
1535
1536fn place_comments(
1547 events: &mut Vec<StreamEvent>,
1548 globals: &[GlobalLine],
1549 places: &BTreeMap<usize, (FloatType, usize)>,
1550) {
1551 const AHEAD: i32 = 40;
1552 let key = |event: &StreamEvent| match event.element() {
1553 StreamElement::GlobalComment(said) => (event.offset(), said.priority()),
1554 _ => (event.offset(), 0),
1555 };
1556 let mut trailing = Vec::new();
1557 for global in globals {
1558 let Some((_, (offset, priority))) = places.range(global.line..).next() else {
1559 trailing.push(GlobalComment::new(global.comment.clone(), 0));
1560 continue;
1561 };
1562 let priority = *priority as i32 - AHEAD + global.run as i32;
1563 let comment = GlobalComment::new(global.comment.clone(), priority);
1564 let index = events
1565 .iter()
1566 .position(|event| {
1567 let (held, ahead) = key(event);
1568 held > *offset || (held == *offset && ahead > priority)
1569 })
1570 .unwrap_or(events.len());
1571 events.insert(index, StreamEvent::new(*offset, comment));
1572 }
1573 let end = events
1574 .iter()
1575 .map(StreamEvent::end_offset)
1576 .fold(0.0, FloatType::max);
1577 for comment in trailing {
1578 events.push(StreamEvent::new(end, comment));
1579 }
1580}
1581
1582pub fn from_humdrum(text: &str) -> Result<Stream> {
1605 let lines: Vec<&str> = text.lines().collect();
1606 if lines.is_empty() {
1607 return Err(humdrum_error("Need a list of lines to parse!"));
1608 }
1609 let all = |line: &str, test: fn(&str) -> bool| {
1612 let line = line.trim_end();
1613 !line.is_empty() && line.split('\t').all(test)
1614 };
1615 let heading: fn(&str) -> bool = |cell| {
1616 cell.strip_prefix("**").is_some_and(|name| {
1617 !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_')
1618 })
1619 };
1620 let starts: Vec<usize> = (0..lines.len())
1621 .filter(|index| all(lines[*index], heading))
1622 .collect();
1623 let mut ends: Vec<usize> = (0..lines.len())
1624 .filter(|index| all(lines[*index], |cell| cell == "*-"))
1625 .collect();
1626 if starts.len() < 2 {
1627 return score_of(&lines);
1628 }
1629 if ends.last().copied().unwrap_or(0) <= starts[starts.len() - 1] {
1630 ends.push(lines.len());
1631 }
1632 let mut pieces: Vec<(usize, &[&str])> = Vec::new();
1634 for (index, end) in ends.iter().enumerate() {
1635 let start = if index == 0 { 0 } else { ends[index - 1] + 1 };
1636 let stop = if index + 1 == ends.len() {
1637 lines.len()
1638 } else {
1639 (*end + 1).min(lines.len())
1640 };
1641 pieces.push((start.min(stop), &lines[start.min(stop)..stop]));
1642 }
1643 if pieces.len() < 2 {
1644 return Err(humdrum_error(
1645 "Malformed Humdrum data: possibly multiple **tags without closing information.",
1646 ));
1647 }
1648 let mut events = Vec::new();
1649 let mut offset = 0.0;
1650 for (index, (before, piece)) in pieces.iter().enumerate() {
1651 let mut score = score_of(piece).map_err(|error| error.lines_down(*before))?;
1652 let mut metadata = score.metadata().cloned().unwrap_or_default();
1653 metadata.set(
1654 "number",
1655 vec![crate::metadata::MetadataValue::new((index + 1).to_string())],
1656 );
1657 score.set_metadata(Some(metadata));
1658 score.set_id(Some(format!("section_{}", index + 1)));
1659 let length = score.end_offset();
1660 events.push(StreamEvent::new(offset, score));
1661 offset = op_frac(offset + length);
1662 }
1663 let mut metadata = events
1665 .first()
1666 .and_then(|event| event.element().as_stream())
1667 .and_then(Stream::metadata)
1668 .cloned()
1669 .unwrap_or_default();
1670 metadata.set(
1671 "number",
1672 vec![crate::metadata::MetadataValue::new("0".to_string())],
1673 );
1674 let mut opus = Stream::with_kind(StreamKind::Opus).with_events(events);
1675 opus.set_metadata(Some(metadata));
1676 Ok(opus)
1677}
1678
1679#[cfg(test)]
1680mod tests {
1681 #[test]
1685 fn global_comments_stand_in_the_score() {
1686 let text = "!!first\n**kern\n*M2/4\n=1\n!!mid\n4c\n!!second\n!!!COM: X\n\
1687 !!third\n4d\n=2\n2e\n==\n*-\n!!last\n";
1688 let score = from_humdrum(text).unwrap();
1689 let seen: Vec<(FloatType, String, i32)> = score
1690 .events()
1691 .iter()
1692 .map(|event| match event.element() {
1693 StreamElement::GlobalComment(said) => {
1694 (event.offset(), said.comment().to_string(), said.priority())
1695 }
1696 _ => (event.offset(), "part".to_string(), 0),
1697 })
1698 .collect();
1699 let expected = [
1700 (0.0, "first", -37),
1701 (0.0, "mid", -33),
1702 (0.0, "part", 0),
1703 (1.0, "second", -29),
1704 (1.0, "third", -27),
1705 (4.0, "last", 0),
1706 ];
1707 let expected: Vec<(FloatType, String, i32)> = expected
1708 .iter()
1709 .map(|(offset, said, priority)| (*offset, said.to_string(), *priority))
1710 .collect();
1711 assert_eq!(seen, expected);
1712 }
1713
1714 #[test]
1719 fn tandems_stand_where_they_were_written() {
1720 let text = "**kern\t**dynam\n*staff1\t*staff1\n*clefG2\t*\n*Xtandy\t*\n\
1721 *M2/4\t*\n=1\t=1\n4c\tp\n*foo\t*\n4d\t.\n==\t==\n*-\t*-\n";
1722 let score = from_humdrum(text).unwrap();
1723 let tandems = |stream: &Stream| -> Vec<(FloatType, String, i32)> {
1724 stream
1725 .events()
1726 .iter()
1727 .filter_map(|event| match event.element() {
1728 StreamElement::MiscTandem(tandem) => Some((
1729 event.offset(),
1730 tandem.tandem().to_string(),
1731 tandem.priority(),
1732 )),
1733 _ => None,
1734 })
1735 .collect()
1736 };
1737 let part = score.events()[0].element().as_stream().unwrap();
1738 let measure = part
1739 .events()
1740 .iter()
1741 .find_map(|event| event.element().as_stream())
1742 .unwrap();
1743 let owned = |held: &[(FloatType, &str, i32)]| -> Vec<(FloatType, String, i32)> {
1744 held.iter()
1745 .map(|(offset, said, priority)| (*offset, said.to_string(), *priority))
1746 .collect()
1747 };
1748 assert_eq!(
1749 tandems(part),
1750 owned(&[(0.0, "**kern", 1), (0.0, "*staff1", 2), (0.0, "*Xtandy", 4)])
1751 );
1752 assert_eq!(tandems(measure), owned(&[(1.0, "*foo", 8)]));
1753 }
1754
1755 #[test]
1758 fn an_opus_takes_its_first_scores_metadata() {
1759 let text = "!!!OTL: Piece\n**kern\n4c\n*-\n!!!OTL: Other\n**kern\n4d\n*-\n";
1760 let opus = from_humdrum(text).unwrap();
1761 let read = |stream: &Stream, name: &str| -> String {
1762 stream.metadata().unwrap().get(name)[0].text().to_string()
1763 };
1764 assert_eq!(read(&opus, "title"), "Piece");
1765 assert_eq!(read(&opus, "number"), "0");
1766 let ids: Vec<Option<&str>> = opus
1767 .events()
1768 .iter()
1769 .filter_map(|event| event.element().as_stream())
1770 .map(Stream::id)
1771 .collect();
1772 assert_eq!(ids, [Some("section_1"), Some("section_2")]);
1773 }
1774
1775 #[test]
1778 fn a_short_first_measure_is_padded_as_a_pickup() {
1779 let padding = |body: &str| {
1780 let text = format!("**kern\n*M2/4\n{body}\n=1\n2d\n=2\n*-\n");
1781 let score = from_humdrum(&text).unwrap();
1782 let part = score.parts()[0].clone();
1783 part.measures()
1784 .iter()
1785 .map(|measure| measure.padding_left())
1786 .collect::<Vec<_>>()
1787 };
1788 assert_eq!(padding("4c"), [1.0, 0.0]);
1789 assert_eq!(
1790 padding("12c"),
1791 [crate::makenotation::op_frac(5.0 / 3.0), 0.0]
1792 );
1793 assert_eq!(padding("2c"), [0.0, 0.0]);
1794 }
1795
1796 use super::*;
1797
1798 #[test]
1799 fn a_length_is_a_number_and_its_dots() {
1800 assert_eq!(duration_of("4c").unwrap().quarter_length(), 1.0);
1801 assert_eq!(duration_of("8.d").unwrap().quarter_length(), 0.75);
1802 assert_eq!(duration_of("0r").unwrap().quarter_length(), 8.0);
1803 let triplet = duration_of("12e").unwrap();
1805 assert!((triplet.quarter_length() - 1.0 / 3.0).abs() < 1e-9);
1806 assert_eq!(triplet.tuplets()[0].actual(), 3);
1807 assert!(duration_of("qf").unwrap().is_grace());
1808 assert!(duration_of("c").is_err());
1809 }
1810
1811 #[test]
1812 fn a_refusal_names_its_line_in_the_whole_document() {
1813 let unpaired = "**kern\t**kern\n*x\t*\n4c\t4d\n*-\t*-\n";
1814 let error = from_humdrum(unpaired).unwrap_err();
1815 assert_eq!(
1816 error.to_string(),
1817 "Humdrum error: ProtoSpine found with unpaired exchange instruction at line 2"
1818 );
1819 assert_eq!(error.position(), Some(&Position::line(2)));
1820
1821 let opus = format!("**kern\n4c\n*-\n{unpaired}");
1822 let error = from_humdrum(&opus).unwrap_err();
1823 assert_eq!(error.position(), Some(&Position::line(5)));
1824 }
1825
1826 #[test]
1827 fn letters_say_the_octave() {
1828 let name = |token: &str| match note_of(token, None).unwrap() {
1829 StreamElement::Note(note) => note.pitch().name_with_octave(),
1830 _ => "rest".to_string(),
1831 };
1832 assert_eq!(name("4c"), "C4");
1833 assert_eq!(name("4cc#"), "C#5");
1834 assert_eq!(name("4C"), "C3");
1835 assert_eq!(name("4BB-"), "Bb2");
1836 assert_eq!(name("4r"), "rest");
1837 }
1838
1839 #[test]
1840 fn a_grace_note_leaves_a_tuplet_whole() {
1841 let types = |cells: &[&str]| -> Vec<Option<&'static str>> {
1843 let events: Vec<(String, usize)> = cells
1844 .iter()
1845 .enumerate()
1846 .map(|(line, cell)| (cell.to_string(), line))
1847 .collect();
1848 parse_kern(&events)
1849 .iter()
1850 .filter_map(|placed| match &placed.thing {
1851 Thing::Element(element) => Some(
1852 element
1853 .duration()?
1854 .tuplets()
1855 .first()
1856 .and_then(|tuplet| tuplet.tuplet_type())
1857 .map(TupletType::as_str),
1858 ),
1859 _ => None,
1860 })
1861 .collect()
1862 };
1863 assert_eq!(
1864 types(&["12c", "12qd", "12e", "12f", "4g"]),
1865 [Some("start"), None, None, Some("stop"), None]
1866 );
1867 assert_eq!(
1868 types(&["12c", "12e", "12qd", "12f", "4g"]),
1869 [Some("start"), None, None, Some("stop"), None]
1870 );
1871 assert_eq!(
1873 types(&["12c", "12e", "16qd", "4g"]),
1874 [Some("start"), Some("stop"), None, None]
1875 );
1876 }
1877
1878 #[test]
1879 fn a_barline_says_how_the_measure_before_ends() {
1880 let (measure, right) = measure_of("=12a:|!", true);
1881 assert_eq!(measure.number, 12);
1882 assert_eq!(measure.suffix.as_deref(), Some("a"));
1883 assert_eq!(right.repeat_direction(), Some(RepeatDirection::End));
1884 let (_, double) = measure_of("==", true);
1885 assert_eq!(double.bar_type(), BarlineType::Double);
1886 }
1887
1888 #[test]
1889 fn spines_are_parts_and_the_last_comes_first() {
1890 let score = from_humdrum(
1891 "**kern\t**kern\n*clefF4\t*clefG2\n*M2/4\t*M2/4\n=1\t=1\n2C\t4c\n.\t4d\n=2\t=2\n\
1892 2D\t2e\n==\t==\n*-\t*-\n",
1893 )
1894 .unwrap();
1895 let parts = score.parts();
1896 assert_eq!(parts.len(), 2);
1897 assert_eq!(parts[0].measures()[0].pitches().len(), 2);
1898 assert_eq!(parts[1].measures()[0].pitches()[0].name_with_octave(), "C3");
1899 }
1900
1901 #[test]
1905 fn what_is_read_keeps_its_line_and_its_spine() {
1906 let score = from_humdrum(
1907 "**kern\t**kern\n*clefF4\t*clefG2\n*M2/4\t*M2/4\n=1\t=1\n2C\t4c\n.\t4d\n=2\t=2\n\
1908 2D\t2e\n==\t==\n*-\t*-\n",
1909 )
1910 .unwrap();
1911 let parts = score.parts();
1912 assert_eq!(parts[0].id(), Some("spine_1"));
1913 assert_eq!(parts[1].id(), Some("spine_0"));
1914 let measures: Vec<i32> = parts[0]
1915 .events()
1916 .iter()
1917 .filter(|event| event.element().as_stream().is_some())
1918 .map(StreamEvent::priority)
1919 .collect();
1920 assert_eq!(measures, [4, 7]);
1921 let first = parts[0].measures()[0];
1922 let lines: Vec<i32> = first.events().iter().map(StreamEvent::priority).collect();
1923 assert_eq!(lines, [2, 3, 5, 6]);
1925 }
1926
1927 #[test]
1930 fn a_spine_comment_stands_where_it_was_written() {
1931 let score = from_humdrum("**kern\n*M2/4\n!Cantus\n!\n=1\n2c\n==\n*-\n").unwrap();
1932 let said: Vec<(&str, i32)> = score.parts()[0].measures()[0]
1933 .events()
1934 .iter()
1935 .filter_map(|event| match event.element() {
1936 StreamElement::SpineComment(said) => Some((said.comment(), event.priority())),
1937 _ => None,
1938 })
1939 .collect();
1940 assert_eq!(said, [("Cantus", 3)]);
1941 }
1942
1943 #[test]
1944 fn a_split_spine_is_two_voices() {
1945 let score = from_humdrum("**kern\n*M2/4\n=1\n*^\n4c\t4e\n4d\t4f\n*v\t*v\n=2\n2g\n==\n*-\n")
1946 .unwrap();
1947 let part = score.parts()[0];
1948 let measures = part.measures();
1949 assert_eq!(measures[0].voices().len(), 2);
1950 assert!(measures[1].voices().is_empty());
1951 }
1952}