1use std::collections::BTreeMap;
9use std::fmt::Write as _;
10
11use super::read::class_sort_order;
12use crate::articulations::{Articulation, ArticulationKind, Finger};
13use crate::bar::{Barline, BarlineType, Ending, RepeatDirection};
14use crate::chord::Chord;
15use crate::chordsymbol::ChordSymbol;
16use crate::clef::Clef;
17use crate::defaults::{FloatType, IntegerType, UnsignedIntegerType};
18use crate::duration::{Duration, TupletType, limited_fraction};
19use crate::error::{Error, Result};
20use crate::expressions::{ArpeggioType, Expression, FermataType, OrnamentKind};
21use crate::key::KeySignature;
22use crate::metadata::Metadata;
23use crate::meter::TimeSignature;
24use crate::notation::{BeamType, Beams, Lyric, Placement, TieType};
25use crate::note::Note;
26use crate::pitch::Pitch;
27use crate::repeat::RepeatExpressionKind;
28use crate::rest::Rest;
29use crate::spanner::SpannerKind;
30use crate::stream::{Stream, StreamElement, StreamKind};
31use crate::tempo::MetronomeMark;
32
33fn abc_error(message: impl Into<String>) -> Error {
34 Error::Abc(message.into())
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct ExportOptions {
40 pub unit_length: Option<(UnsignedIntegerType, UnsignedIntegerType)>,
44 pub measures_per_line: usize,
46}
47
48impl Default for ExportOptions {
49 fn default() -> Self {
50 Self {
51 unit_length: None,
52 measures_per_line: 4,
53 }
54 }
55}
56
57pub fn to_abc(stream: &Stream, options: &ExportOptions) -> Result<String> {
101 if stream.kind() == StreamKind::Opus {
102 let mut tunes = Vec::new();
103 for (index, event) in stream.events().iter().enumerate() {
104 let score = event
105 .element()
106 .as_stream()
107 .ok_or_else(|| abc_error("an opus holds something that is not a score"))?;
108 tunes.push(Tune::new(score, index as IntegerType + 1)?.write(options)?);
109 }
110 return Ok(tunes.join("\n"));
111 }
112 Tune::new(stream, 1)?.write(options)
113}
114
115const EPSILON: FloatType = 1e-6;
116
117fn fraction(value: FloatType) -> Result<(i128, i128)> {
119 limited_fraction(value, 4096)
120 .filter(|(numerator, denominator)| {
121 (*numerator as FloatType / *denominator as FloatType - value).abs() < 1e-7
122 })
123 .ok_or_else(|| {
124 abc_error(format!(
125 "ABC cannot write a length of {value} quarter notes"
126 ))
127 })
128}
129
130fn length_text(written: FloatType, unit: FloatType) -> Result<String> {
132 let (numerator, denominator) = fraction(written / unit)?;
133 Ok(match (numerator, denominator) {
134 (1, 1) => String::new(),
135 (numerator, 1) => numerator.to_string(),
136 (1, 2) => "/".to_string(),
137 (1, denominator) => format!("/{denominator}"),
138 (numerator, denominator) => format!("{numerator}/{denominator}"),
139 })
140}
141
142fn plain(text: &str) -> String {
144 text.replace(['\n', '\r'], " ").trim().to_string()
145}
146
147fn quoted(text: &str) -> String {
149 plain(text).replace('"', "'")
150}
151
152type Alters = [IntegerType; 7];
154
155fn letter_index(letter: char) -> usize {
156 match letter {
157 'C' => 0,
158 'D' => 1,
159 'E' => 2,
160 'F' => 3,
161 'G' => 4,
162 'A' => 5,
163 _ => 6,
164 }
165}
166
167fn alters_of(signature: &KeySignature) -> Alters {
168 let mut alters = [0; 7];
169 for pitch in signature.altered_pitches().unwrap_or_default() {
170 alters[letter_index(pitch.step().as_char())] = pitch.alter().round() as IntegerType;
171 }
172 alters
173}
174
175fn signature_of(element: &StreamElement) -> Option<KeySignature> {
177 match element {
178 StreamElement::Key(key) => Some(key.key_signature()),
179 StreamElement::KeySignature(signature) => Some(signature.clone()),
180 _ => None,
181 }
182}
183
184fn abc_tonic(pitch: &Pitch) -> String {
185 pitch.name().replace('-', "b")
186}
187
188fn clef_name(clef: &Clef) -> Result<String> {
190 let mut name = match (clef.sign(), clef.line()) {
191 (Some("G"), Some(2) | None) => "treble".to_string(),
192 (Some("G"), Some(line)) => format!("treble{line}"),
193 (Some("F"), Some(4) | None) => "bass".to_string(),
194 (Some("F"), Some(line)) => format!("bass{line}"),
195 (Some("C"), Some(3) | None) => "alto".to_string(),
196 (Some("C"), Some(4)) => "tenor".to_string(),
197 (Some("C"), Some(line)) => format!("alto{line}"),
198 (Some("percussion"), _) => "perc".to_string(),
199 (Some("none"), _) | (None, _) => "none".to_string(),
200 (Some(sign), _) => {
201 return Err(abc_error(format!("ABC has no {sign} clef")));
202 }
203 };
204 match clef.octave_change() {
205 0 => {}
206 1 => name.push_str("+8"),
207 -1 => name.push_str("-8"),
208 other => {
209 return Err(abc_error(format!(
210 "ABC has no clef moved by {other} octaves"
211 )));
212 }
213 }
214 Ok(name)
215}
216
217fn key_field(element: &StreamElement, clef: Option<&Clef>) -> Result<String> {
220 let clef = clef.map(clef_name).transpose()?;
221 let mode = match element {
222 StreamElement::Key(key) => match key.mode() {
223 "major" => Some(""),
224 "minor" => Some("m"),
225 "dorian" => Some("dor"),
226 "phrygian" => Some("phr"),
227 "lydian" => Some("lyd"),
228 "mixolydian" => Some("mix"),
229 "aeolian" => Some("aeo"),
230 "ionian" => Some("ion"),
231 "locrian" => Some("loc"),
232 _ => None,
233 },
234 _ => None,
235 };
236 let mut text = match (element, mode) {
237 (StreamElement::Key(key), Some(mode)) => {
238 let mode = if mode.is_empty() && clef.is_some() {
240 " maj"
241 } else {
242 mode
243 };
244 format!("{}{mode}", abc_tonic(key.tonic_pitch()))
245 }
246 _ => {
247 let signature = signature_of(element)
250 .ok_or_else(|| abc_error("a key field written for what is no key"))?;
251 let tonic = match signature.sharps() {
252 Some(sharps) => {
253 let key = KeySignature::new(sharps).as_key("major");
254 abc_tonic(key.tonic_pitch())
255 }
256 None => "C".to_string(),
257 };
258 let mut text = format!("{tonic} exp");
259 for pitch in signature.altered_pitches()? {
260 let _ = write!(
261 text,
262 " {}{}",
263 accidental_marks(pitch.alter().round() as IntegerType)?,
264 pitch.step().as_char().to_ascii_lowercase()
265 );
266 }
267 text
268 }
269 };
270 if let Some(clef) = clef {
271 let _ = write!(text, " clef={clef}");
272 }
273 Ok(text)
274}
275
276fn accidental_marks(alter: IntegerType) -> Result<&'static str> {
277 Ok(match alter {
278 -2 => "__",
279 -1 => "_",
280 0 => "=",
281 1 => "^",
282 2 => "^^",
283 other => {
284 return Err(abc_error(format!(
285 "ABC has no accidental moving a note by {other} semitones"
286 )));
287 }
288 })
289}
290
291fn meter_field(meter: &TimeSignature) -> String {
292 match meter.symbol() {
293 Some("common") => "C".to_string(),
294 Some("cut") => "C|".to_string(),
295 _ => meter.ratio_string(),
296 }
297}
298
299fn number_text(number: FloatType) -> String {
300 if number.fract() == 0.0 {
301 format!("{number:.0}")
302 } else {
303 number.to_string()
304 }
305}
306
307fn tempo_field(mark: &MetronomeMark) -> Result<Option<String>> {
309 let mut parts = Vec::new();
310 if let Some(text) = mark.text().filter(|_| !mark.text_implicit()) {
311 parts.push(format!("\"{}\"", quoted(text)));
312 }
313 if let Some(number) = mark.number().filter(|_| !mark.number_implicit()) {
314 let (numerator, denominator) = fraction(mark.referent().quarter_length() / 4.0)?;
315 parts.push(format!("{numerator}/{denominator}={}", number_text(number)));
316 }
317 Ok((!parts.is_empty()).then(|| parts.join(" ")))
318}
319
320#[derive(Clone, Copy, Debug)]
323struct Held<'a> {
324 offset: FloatType,
325 element: &'a StreamElement,
326 leaf: usize,
327}
328
329#[derive(Clone, Debug, Default)]
331struct Bar<'a> {
332 measure: Option<&'a Stream>,
333 held: Vec<Held<'a>>,
334 filler: Option<FloatType>,
336}
337
338#[derive(Clone, Debug)]
340struct Voice<'a> {
341 part: &'a Stream,
342 bars: Vec<Bar<'a>>,
343 measured: bool,
344 within: usize,
346}
347
348fn program_of(part: &Stream) -> Option<u8> {
351 let leaves = part.leaves();
352 let first_note = leaves
353 .iter()
354 .filter(|(_, element)| is_sounding(element))
355 .map(|(offset, _)| *offset)
356 .fold(FloatType::INFINITY, FloatType::min);
357 leaves.iter().find_map(|(offset, element)| match element {
358 StreamElement::Instrument(instrument) if *offset <= first_note + EPSILON => {
359 instrument.midi_program()
360 }
361 _ => None,
362 })
363}
364
365fn is_sounding(element: &StreamElement) -> bool {
366 matches!(
367 element,
368 StreamElement::Note(_)
369 | StreamElement::Chord(_)
370 | StreamElement::Rest(_)
371 | StreamElement::Unpitched(_)
372 | StreamElement::PercussionChord(_)
373 )
374}
375
376fn is_grace(element: &StreamElement) -> bool {
377 is_sounding(element) && element.duration().is_some_and(Duration::is_grace)
378}
379
380fn gather<'a>(stream: &'a Stream, base: FloatType, leaf: &mut usize, out: &mut Vec<Held<'a>>) {
383 for event in stream.events() {
384 match event.element() {
385 StreamElement::Stream(inner) => gather(inner, base + event.offset(), leaf, out),
386 element => {
387 out.push(Held {
388 offset: base + event.offset(),
389 element,
390 leaf: *leaf,
391 });
392 *leaf += 1;
393 }
394 }
395 }
396}
397
398fn sort_held(held: &mut [Held<'_>]) {
399 held.sort_by(|left, right| {
400 left.offset
401 .total_cmp(&right.offset)
402 .then(class_sort_order(left.element).cmp(&class_sort_order(right.element)))
403 .then(is_grace(right.element).cmp(&is_grace(left.element)))
404 .then(left.leaf.cmp(&right.leaf))
405 });
406}
407
408fn voices_of<'a>(part: &'a Stream, leaf: &mut usize) -> Result<Vec<Voice<'a>>> {
410 let has_measures = part.events().iter().any(|event| {
411 event
412 .element()
413 .as_stream()
414 .is_some_and(|inner| inner.kind() == StreamKind::Measure)
415 });
416 if !has_measures {
417 let mut held = Vec::new();
418 gather(part, 0.0, leaf, &mut held);
419 return Ok(vec![Voice {
420 part,
421 bars: vec![Bar {
422 measure: None,
423 held,
424 filler: None,
425 }],
426 measured: false,
427 within: 0,
428 }]);
429 }
430
431 type Split<'a> = (&'a Stream, Vec<Held<'a>>, Vec<Vec<Held<'a>>>);
433 let mut measures: Vec<Split<'a>> = Vec::new();
434 let mut loose: Vec<Held<'a>> = Vec::new();
435 for event in part.events() {
436 match event.element() {
437 StreamElement::Stream(measure) => {
438 let mut common = Vec::new();
439 let mut voices = Vec::new();
440 for inner in measure.events() {
441 match inner.element() {
442 StreamElement::Stream(voice) => {
443 let mut held = Vec::new();
444 gather(voice, inner.offset(), leaf, &mut held);
445 voices.push(held);
446 }
447 element => {
448 common.push(Held {
449 offset: inner.offset(),
450 element,
451 leaf: *leaf,
452 });
453 *leaf += 1;
454 }
455 }
456 }
457 measures.push((measure, common, voices));
458 }
459 element => {
460 loose.push(Held {
461 offset: event.offset(),
462 element,
463 leaf: *leaf,
464 });
465 *leaf += 1;
466 }
467 }
468 }
469 if loose.iter().any(|held| is_sounding(held.element)) {
470 return Err(abc_error(
471 "a part holds notes outside its measures as well as inside them",
472 ));
473 }
474
475 let count = measures
476 .iter()
477 .map(|(_, _, voices)| voices.len())
478 .max()
479 .unwrap_or(0)
480 .max(1);
481 let mut voices: Vec<Voice<'a>> = (0..count)
482 .map(|within| Voice {
483 part,
484 bars: Vec::new(),
485 measured: true,
486 within,
487 })
488 .collect();
489 for (index, (measure, common, inner)) in measures.into_iter().enumerate() {
490 let length = common
491 .iter()
492 .chain(inner.iter().flatten())
493 .map(|held| held.offset + held.element.quarter_length())
494 .fold(0.0, FloatType::max);
495 let plain_measure = inner.is_empty();
496 let mut inner = inner.into_iter();
497 for (within, voice) in voices.iter_mut().enumerate() {
498 let own = inner.next();
499 let mut held = Vec::new();
500 if within == 0 {
501 held.extend(common.iter().copied());
502 if index == 0 {
505 held.extend(loose.iter().filter(|held| held.offset < EPSILON).copied());
506 }
507 } else {
508 let shared = |held: &&Held<'a>| {
511 matches!(
512 held.element,
513 StreamElement::Clef(_)
514 | StreamElement::Key(_)
515 | StreamElement::KeySignature(_)
516 | StreamElement::TimeSignature(_)
517 )
518 };
519 held.extend(common.iter().filter(shared).copied());
520 if index == 0 {
521 held.extend(
522 loose
523 .iter()
524 .filter(|held| held.offset < EPSILON)
525 .filter(shared)
526 .copied(),
527 );
528 }
529 }
530 let filler = match own {
531 Some(own) => {
532 held.extend(own);
533 None
534 }
535 None if within == 0 && plain_measure => None,
536 None => Some(length),
537 };
538 if within > 0 || !plain_measure || index == 0 {
539 sort_held(&mut held);
540 }
541 voice.bars.push(Bar {
542 measure: Some(measure),
543 held,
544 filler,
545 });
546 }
547 }
548 Ok(voices)
549}
550
551fn meter_of<'a>(bar: &Bar<'a>) -> Option<&'a TimeSignature> {
553 bar.held.iter().find_map(|held| match held.element {
554 StreamElement::TimeSignature(meter) if held.offset < EPSILON => Some(meter),
555 _ => None,
556 })
557}
558
559fn length_of(bar: &Bar<'_>) -> FloatType {
561 bar.held
562 .iter()
563 .map(|held| held.offset + held.element.quarter_length())
564 .fold(0.0, FloatType::max)
565}
566
567#[derive(Clone, Debug, Default)]
570struct Tail {
571 extra: FloatType,
573 tied: bool,
575 closes: String,
577}
578
579#[derive(Clone, Debug, Default)]
581struct Plan {
582 drop_meter: bool,
584 tail: Option<Tail>,
586 skip_head: bool,
588}
589
590fn first_sounding(bar: &Bar<'_>) -> Option<usize> {
591 bar.held.iter().position(|held| is_sounding(held.element))
592}
593
594fn last_sounding(bar: &Bar<'_>) -> Option<usize> {
595 bar.held
596 .iter()
597 .rposition(|held| is_sounding(held.element) && !is_grace(held.element))
598}
599
600fn notes_of(element: &StreamElement) -> Option<&[Note]> {
602 match element {
603 StreamElement::Note(note) => Some(std::slice::from_ref(note)),
604 StreamElement::Chord(chord) => Some(chord.notes()),
605 _ => None,
606 }
607}
608
609fn cut_note(first: &Bar<'_>, second: &Bar<'_>) -> Option<bool> {
615 let before = first.held[last_sounding(first)?];
616 let after = second.held[first_sounding(second)?];
617 if is_grace(after.element)
618 || after.offset > EPSILON
619 || (before.offset + before.element.quarter_length() - length_of(first)).abs() > EPSILON
620 {
621 return None;
622 }
623 let is_chord = matches!(before.element, StreamElement::Chord(_));
624 if is_chord != matches!(after.element, StreamElement::Chord(_)) {
625 return None;
626 }
627 let (ending, starting) = (notes_of(before.element)?, notes_of(after.element)?);
628 let said = |element: &StreamElement| {
629 element
630 .duration()
631 .and_then(Duration::said_tuplets)
632 .is_some_and(|tuplets| !tuplets.is_empty())
633 };
634 if ending.len() != starting.len() || said(before.element) || said(after.element) {
635 return None;
636 }
637 let tie = |note: &Note| note.tie().map(crate::notation::Tie::tie_type);
638 let halves = ending.iter().zip(starting).all(|(one, other)| {
639 one.pitch().name_with_octave() == other.pitch().name_with_octave()
640 && matches!(tie(one), Some(TieType::Start | TieType::Continue))
641 && matches!(tie(other), Some(TieType::Stop | TieType::Continue))
642 && other.lyrics().is_empty()
643 });
644 if !halves {
645 return None;
646 }
647 let hidden = ending.iter().zip(starting).any(|(one, other)| {
648 shows_accidental(one.pitch())
649 && other
650 .pitch()
651 .written_accidental()
652 .is_some_and(|accidental| accidental.display_status() == Some(false))
653 });
654 Some(is_chord || hidden)
655}
656
657struct Sounding<'a> {
659 duration: Duration,
660 written: FloatType,
662 tuplet: Option<(UnsignedIntegerType, UnsignedIntegerType)>,
664 bracket: Option<TupletType>,
665 grace: bool,
666 held: Held<'a>,
667}
668
669fn sounding_of<'a>(held: Held<'a>) -> Result<Option<Sounding<'a>>> {
670 let duration = match held.element {
671 StreamElement::Note(note) => note.duration().cloned().unwrap_or_else(Duration::quarter),
672 StreamElement::Chord(chord) => chord.duration().cloned().unwrap_or_else(Duration::quarter),
673 StreamElement::Rest(rest) => rest.duration().clone(),
674 StreamElement::Unpitched(_) | StreamElement::PercussionChord(_) => {
675 return Err(abc_error("ABC has no unpitched notes"));
676 }
677 _ => return Ok(None),
678 };
679 let grace = duration.is_grace();
680 let said = duration.said_tuplets().unwrap_or(&[]);
681 if said.len() > 1 {
682 return Err(abc_error("ABC cannot write a tuplet inside a tuplet"));
683 }
684 let (tuplet, bracket) = match said.first().filter(|_| !grace) {
685 Some(tuplet) => {
686 let multiplier = tuplet.multiplier();
687 let numerator = *multiplier.numer().unwrap_or(&0) as FloatType;
688 let denominator = *multiplier.denom().unwrap_or(&1) as FloatType;
689 let normal = FloatType::from(tuplet.actual()) * numerator / denominator;
690 if normal < 1.0 || (normal - normal.round()).abs() > EPSILON {
691 return Err(abc_error(format!(
692 "ABC cannot write {} notes in the time of {normal}",
693 tuplet.actual()
694 )));
695 }
696 (
697 Some((tuplet.actual(), normal.round() as UnsignedIntegerType)),
698 tuplet.tuplet_type(),
699 )
700 }
701 None => (None, None),
702 };
703 let written = if grace {
704 duration
705 .written_values()
706 .iter()
707 .filter_map(|value| {
708 Some(
709 value
710 .duration_type()?
711 .quarter_length_with_dots(value.dots()),
712 )
713 })
714 .sum()
715 } else if tuplet.is_some() {
716 duration.quarter_length_no_tuplets()
717 } else {
718 duration.quarter_length()
719 };
720 Ok(Some(Sounding {
721 duration,
722 written,
723 tuplet,
724 bracket,
725 grace,
726 held,
727 }))
728}
729
730fn tuplet_marker(actual: u32, normal: u32, count: usize) -> String {
733 let usual = match actual {
734 2 | 4 | 8 => Some(3),
735 3 | 6 => Some(2),
736 _ => None,
737 };
738 let mut marker = format!("({actual}");
739 let says_normal = usual != Some(normal);
740 if says_normal {
741 let _ = write!(marker, ":{normal}");
742 }
743 if count != actual as usize {
744 if !says_normal {
745 marker.push(':');
746 }
747 let _ = write!(marker, ":{count}");
748 }
749 marker
750}
751
752fn tuplet_markers(
755 tokens: &[Sounding<'_>],
756 opens_on: &dyn Fn(&Sounding<'_>) -> bool,
757) -> BTreeMap<usize, String> {
758 let mut markers = BTreeMap::new();
759 let mut index = 0;
760 while index < tokens.len() {
761 let Some((actual, normal)) = tokens[index].tuplet else {
762 index += 1;
763 continue;
764 };
765 let mut start = index;
770 while start > 0 && tokens[start - 1].grace {
771 start -= 1;
772 }
773 if !tokens[start..index].iter().any(opens_on) {
774 start = index;
775 }
776 let mut last = index;
777 let mut filled = tokens[index].written;
778 loop {
779 let whole = filled / FloatType::from(actual);
783 let filled_up = whole > 0.0 && (whole.log2() - whole.log2().round()).abs() < 1e-9;
784 let complete = last - start + 1 >= actual as usize || (filled_up && last > index);
785 if complete
786 || matches!(
787 tokens[last].bracket,
788 Some(TupletType::Stop | TupletType::StartStop)
789 )
790 {
791 break;
792 }
793 let mut next = last + 1;
794 while next < tokens.len() && tokens[next].grace {
795 next += 1;
796 }
797 if next >= tokens.len()
799 || tokens[next].tuplet != Some((actual, normal))
800 || matches!(
801 tokens[next].bracket,
802 Some(TupletType::Start | TupletType::StartStop)
803 )
804 || next - start + 1 > 9
805 {
806 break;
807 }
808 filled += tokens[next].written;
809 last = next;
810 }
811 markers.insert(start, tuplet_marker(actual, normal, last - start + 1));
812 index = last + 1;
813 }
814 markers
815}
816
817fn articulations_of(element: &StreamElement) -> &[Articulation] {
819 match element {
820 StreamElement::Note(note) => note.articulations(),
821 StreamElement::Chord(chord) => chord.articulations(),
822 StreamElement::Rest(rest) => rest.articulations(),
823 _ => &[],
824 }
825}
826
827fn is_lettered(articulation: &Articulation) -> bool {
830 matches!(
831 articulation.kind(),
832 ArticulationKind::Staccato
833 | ArticulationKind::UpBow
834 | ArticulationKind::DownBow
835 | ArticulationKind::Accent
836 | ArticulationKind::StrongAccent
837 | ArticulationKind::Tenuto
838 )
839}
840
841fn articulation_text(articulation: &Articulation) -> Result<String> {
843 Ok(match articulation.kind() {
844 ArticulationKind::Staccato => ".".to_string(),
845 ArticulationKind::UpBow => "u".to_string(),
846 ArticulationKind::DownBow => "v".to_string(),
847 ArticulationKind::Accent => "K".to_string(),
849 ArticulationKind::StrongAccent => "k".to_string(),
850 ArticulationKind::Tenuto => "M".to_string(),
851 ArticulationKind::Staccatissimo => "!wedge!".to_string(),
852 ArticulationKind::BreathMark => "!breath!".to_string(),
853 ArticulationKind::OpenString => "!open!".to_string(),
854 ArticulationKind::Stopped => "!+!".to_string(),
855 ArticulationKind::SnapPizzicato => "!snap!".to_string(),
856 ArticulationKind::Fingering => match articulation.finger() {
857 Some(Finger::Number(number)) if (0..=5).contains(number) => format!("!{number}!"),
858 _ => return Err(abc_error("ABC writes fingers nought to five and no others")),
859 },
860 other => {
861 return Err(abc_error(format!(
862 "ABC has no decoration for {}",
863 other.class_name()
864 )));
865 }
866 })
867}
868
869fn expression_text(expression: &Expression) -> Result<&'static str> {
870 Ok(match expression {
871 Expression::Fermata(fermata) => match fermata.fermata_type() {
872 FermataType::Inverted => "!fermata!",
873 FermataType::Upright => "!invertedfermata!",
874 },
875 Expression::Arpeggio(ArpeggioType::Normal) => "!arpeggio!",
876 Expression::Arpeggio(_) => {
877 return Err(abc_error("ABC has one arpeggio sign, the plain one"));
878 }
879 Expression::Ornament(ornament) => match ornament.kind() {
880 OrnamentKind::Trill | OrnamentKind::HalfStepTrill | OrnamentKind::WholeStepTrill => {
881 "!trill!"
882 }
883 OrnamentKind::Mordent
884 | OrnamentKind::HalfStepMordent
885 | OrnamentKind::WholeStepMordent => "!lowermordent!",
886 OrnamentKind::InvertedMordent
887 | OrnamentKind::HalfStepInvertedMordent
888 | OrnamentKind::WholeStepInvertedMordent => "!uppermordent!",
889 OrnamentKind::Turn => "!turn!",
890 OrnamentKind::InvertedTurn => "!invertedturn!",
891 other => {
892 return Err(abc_error(format!(
893 "ABC has no decoration for {}",
894 other.class_name()
895 )));
896 }
897 },
898 })
899}
900
901fn decorations(articulations: &[Articulation], expressions: &[Expression]) -> Result<String> {
902 let mut text = String::new();
903 for expression in expressions {
904 text.push_str(expression_text(expression)?);
905 }
906 let mut written = articulations
908 .iter()
909 .map(articulation_text)
910 .collect::<Result<Vec<_>>>()?;
911 written.sort_by_key(|text| std::cmp::Reverse(text.len()));
912 text.push_str(&written.concat());
913 Ok(text)
914}
915
916fn spanner_open(kind: SpannerKind) -> Result<&'static str> {
918 Ok(match kind {
919 SpannerKind::Slur => "(",
920 SpannerKind::Crescendo => "!crescendo(!",
921 SpannerKind::Diminuendo => "!diminuendo(!",
922 other => {
923 return Err(abc_error(format!("ABC has no {}", other.class_name())));
924 }
925 })
926}
927
928fn spanner_close(kind: SpannerKind) -> &'static str {
929 match kind {
930 SpannerKind::Crescendo => "!crescendo)!",
931 SpannerKind::Diminuendo => "!diminuendo)!",
932 _ => ")",
933 }
934}
935
936struct Reading {
938 alters: Alters,
940}
941
942#[derive(Default)]
945struct Written<'a> {
946 opening: Vec<String>,
948 music: String,
949 closing: Vec<String>,
951 sung: Vec<&'a [Lyric]>,
953 starts_with_note: bool,
956}
957
958struct Tune<'a> {
960 metadata: Option<&'a Metadata>,
961 voices: Vec<Voice<'a>>,
962 parts: Vec<(&'a Stream, usize)>,
964 opens: BTreeMap<usize, Vec<SpannerKind>>,
965 closes: BTreeMap<usize, Vec<SpannerKind>>,
966 number: IntegerType,
967}
968
969impl<'a> Tune<'a> {
970 fn new(stream: &'a Stream, number: IntegerType) -> Result<Self> {
971 let mut leaf = 0;
972 let mut voices = Vec::new();
973 let mut parts = Vec::new();
974 let holds_parts = stream.events().iter().any(|event| {
975 event.element().as_stream().is_some_and(|inner| {
976 matches!(inner.kind(), StreamKind::Part | StreamKind::PartStaff)
977 })
978 });
979 if holds_parts {
980 for event in stream.events() {
981 match event.element() {
982 StreamElement::Stream(part) => {
983 let made = voices_of(part, &mut leaf)?;
984 parts.push((part.as_ref(), made.len()));
985 voices.extend(made);
986 }
987 element if is_sounding(element) => {
988 return Err(abc_error(
989 "a score holds notes outside its parts as well as inside them",
990 ));
991 }
992 _ => leaf += 1,
993 }
994 }
995 } else if stream.kind() == StreamKind::Measure {
996 let mut held = Vec::new();
998 gather(stream, 0.0, &mut leaf, &mut held);
999 parts.push((stream, 1));
1000 voices.push(Voice {
1001 part: stream,
1002 bars: vec![Bar {
1003 measure: None,
1004 held,
1005 filler: None,
1006 }],
1007 measured: false,
1008 within: 0,
1009 });
1010 } else {
1011 let made = voices_of(stream, &mut leaf)?;
1012 parts.push((stream, made.len()));
1013 voices.extend(made);
1014 }
1015
1016 let leaves = stream.leaves();
1018 let graced = |leaf: usize| {
1019 leaves
1020 .get(leaf)
1021 .is_some_and(|(_, element)| is_grace(element))
1022 };
1023 let mut opens: BTreeMap<usize, Vec<(usize, SpannerKind)>> = BTreeMap::new();
1024 let mut closes: BTreeMap<usize, Vec<(usize, SpannerKind)>> = BTreeMap::new();
1025 for spanner in stream.spanners() {
1026 spanner_open(spanner.kind())?;
1027 let spanned = spanner.spanned();
1028 let placed: Vec<usize> = spanned.iter().flatten().copied().collect();
1029 let (Some(first), Some(last)) = (placed.iter().min(), placed.iter().max()) else {
1030 continue;
1031 };
1032 let mut start = *first;
1035 for _ in spanned.iter().take_while(|place| place.is_none()) {
1036 if start > 0 && graced(start - 1) {
1037 start -= 1;
1038 }
1039 }
1040 let mut end = *last;
1041 for _ in spanned.iter().rev().take_while(|place| place.is_none()) {
1042 if graced(end + 1) {
1043 end += 1;
1044 }
1045 }
1046 opens.entry(start).or_default().push((end, spanner.kind()));
1047 closes.entry(end).or_default().push((start, spanner.kind()));
1048 }
1049 let ordered = |mut list: Vec<(usize, SpannerKind)>| {
1052 list.sort_by_key(|(other, _)| std::cmp::Reverse(*other));
1053 list.into_iter().map(|(_, kind)| kind).collect::<Vec<_>>()
1054 };
1055 Ok(Self {
1056 metadata: stream.metadata(),
1057 voices,
1058 parts,
1059 opens: opens
1060 .into_iter()
1061 .map(|(leaf, list)| (leaf, ordered(list)))
1062 .collect(),
1063 closes: closes
1064 .into_iter()
1065 .map(|(leaf, list)| (leaf, ordered(list)))
1066 .collect(),
1067 number,
1068 })
1069 }
1070
1071 fn unit(&self, options: &ExportOptions) -> Result<(FloatType, String)> {
1074 if let Some((numerator, denominator)) = options.unit_length {
1075 if numerator == 0 || denominator == 0 {
1076 return Err(abc_error("the unit note length cannot be nought"));
1077 }
1078 return Ok((
1079 4.0 * FloatType::from(numerator) / FloatType::from(denominator),
1080 format!("{numerator}/{denominator}"),
1081 ));
1082 }
1083 const UNITS: [(FloatType, &str); 6] = [
1084 (0.5, "1/8"),
1085 (1.0, "1/4"),
1086 (0.25, "1/16"),
1087 (2.0, "1/2"),
1088 (0.125, "1/32"),
1089 (4.0, "1/1"),
1090 ];
1091 let mut written = Vec::new();
1093 for held in self
1094 .voices
1095 .iter()
1096 .flat_map(|voice| &voice.bars)
1097 .flat_map(|bar| &bar.held)
1098 {
1099 if let Some(sounding) = sounding_of(*held)? {
1100 written.push(sounding.written);
1101 }
1102 }
1103 let mut place = 0;
1104 let mut least = usize::MAX;
1105 for (index, (unit, _)) in UNITS.iter().enumerate() {
1106 let cost: usize = written
1107 .iter()
1108 .map(|length| length_text(*length, *unit).map_or(8, |text| text.len()))
1109 .sum();
1110 if cost < least {
1111 least = cost;
1112 place = index;
1113 }
1114 }
1115 Ok((UNITS[place].0, UNITS[place].1.to_string()))
1116 }
1117
1118 fn write(&self, options: &ExportOptions) -> Result<String> {
1119 let (unit, unit_text) = self.unit(options)?;
1120 let mut out = String::new();
1121 let number = self
1122 .metadata
1123 .and_then(|metadata| metadata.first_text("number"))
1124 .and_then(|text| text.trim().parse::<IntegerType>().ok())
1125 .unwrap_or(self.number);
1126 let _ = writeln!(out, "X:{number}");
1127 if let Some(metadata) = self.metadata {
1128 for (name, tag) in [
1129 ("title", 'T'),
1130 ("alternativeTitle", 'T'),
1131 ("composer", 'C'),
1132 ("localeOfComposition", 'O'),
1133 ] {
1134 for value in metadata.get(name) {
1135 let _ = writeln!(out, "{tag}:{}", plain(value.text()));
1136 }
1137 }
1138 }
1139
1140 for (kind, field) in [
1143 (ArticulationKind::Accent, "U:K=!accent!"),
1144 (ArticulationKind::StrongAccent, "U:k=!marcato!"),
1145 (ArticulationKind::Tenuto, "U:M=!tenuto!"),
1146 ] {
1147 let used = self
1148 .voices
1149 .iter()
1150 .flat_map(|voice| &voice.bars)
1151 .flat_map(|bar| &bar.held)
1152 .flat_map(|held| articulations_of(held.element))
1153 .any(|articulation| articulation.kind() == kind);
1154 if used {
1155 let _ = writeln!(out, "{field}");
1156 }
1157 }
1158
1159 let first = self.voices.first();
1162 let opening = |voice: &Voice<'a>| -> Opening<'a> {
1163 Opening::of(voice.bars.first().map_or(&[], |bar| bar.held.as_slice()))
1164 };
1165 let shared = first.map(opening).unwrap_or_default();
1166 let openings: Vec<Opening<'a>> = self.voices.iter().map(opening).collect();
1167 let all = |same: &dyn Fn(&Opening<'a>) -> bool| openings.iter().all(same);
1168 let shared_meter = all(&|other| other.meter == shared.meter);
1169 let shared_tempo = all(&|other| other.tempos == shared.tempos);
1170
1171 if shared_meter {
1172 let _ = writeln!(
1173 out,
1174 "M:{}",
1175 shared.meter.map_or("none".to_string(), meter_field)
1176 );
1177 }
1178 let _ = writeln!(out, "L:{unit_text}");
1179 if shared_tempo {
1180 for mark in &shared.tempos {
1181 if let Some(field) = tempo_field(mark)? {
1182 let _ = writeln!(out, "Q:{field}");
1183 }
1184 }
1185 }
1186 let clef_of = |voice: &Voice<'a>, opening: &Opening<'a>| -> Option<&'a Clef> {
1187 let clef = opening.clef?;
1188 let pitches: Vec<Pitch> = voice
1189 .bars
1190 .iter()
1191 .flat_map(|bar| &bar.held)
1192 .flat_map(|held| match held.element {
1193 StreamElement::ChordSymbol(symbol) => symbol.pitches().unwrap_or_default(),
1194 element => element.pitches(),
1195 })
1196 .collect();
1197 let unsaid = clef.kind() == crate::clef::ClefKind::TrebleClef
1201 && *clef == Clef::best_for(&pitches, false);
1202 (!unsaid).then_some(clef)
1203 };
1204 let key_line = |opening: &Opening<'a>| -> Result<String> {
1208 match opening.key {
1209 Some(key) => key_field(key, None),
1210 None => Ok("none".to_string()),
1211 }
1212 };
1213 let voice_line =
1214 |index: usize, voice: &Voice<'a>, opening: &Opening<'a>| -> Result<String> {
1215 let mut line = format!("V:{}", index + 1);
1216 if let Some(clef) = clef_of(voice, opening) {
1217 let _ = write!(line, " clef={}", clef_name(clef)?);
1218 }
1219 if let Some(name) = voice.part.name().filter(|_| voice.within == 0) {
1220 let _ = write!(line, " name=\"{}\"", quoted(name));
1221 }
1222 Ok(line)
1223 };
1224 let mut reading = Reading { alters: [0; 7] };
1225 let several = self.voices.len() > 1;
1226 let key_heads = !several || shared.key.is_some();
1229 if !key_heads {
1230 } else if first.is_some() {
1231 let _ = writeln!(out, "K:{}", key_line(&shared)?);
1232 reading.alters = shared
1233 .key
1234 .and_then(signature_of)
1235 .map_or([0; 7], |signature| alters_of(&signature));
1236 } else {
1237 let _ = writeln!(out, "K:none");
1238 }
1239
1240 if self.parts.iter().any(|(_, voices)| *voices > 1) {
1241 let mut next = 1;
1243 let groups: Vec<String> = self
1244 .parts
1245 .iter()
1246 .map(|(_, voices)| {
1247 let ids: Vec<String> = (next..next + voices).map(|id| id.to_string()).collect();
1248 next += voices;
1249 if ids.len() > 1 {
1250 format!("({})", ids.join(" "))
1251 } else {
1252 ids.concat()
1253 }
1254 })
1255 .collect();
1256 let _ = writeln!(out, "%%score {}", groups.join(" "));
1257 }
1258 for (index, voice) in self.voices.iter().enumerate() {
1259 let opening = &openings[index];
1260 let line = voice_line(index, voice, opening)?;
1264 if several || line != "V:1" {
1265 let _ = writeln!(out, "{line}");
1266 }
1267 if several {
1268 if !shared_meter {
1269 let _ = writeln!(
1270 out,
1271 "M:{}",
1272 opening.meter.map_or("none".to_string(), meter_field)
1273 );
1274 }
1275 if !shared_tempo {
1276 for mark in &opening.tempos {
1277 if let Some(field) = tempo_field(mark)? {
1278 let _ = writeln!(out, "Q:{field}");
1279 }
1280 }
1281 }
1282 let alters = opening
1285 .key
1286 .and_then(signature_of)
1287 .map_or([0; 7], |signature| alters_of(&signature));
1288 let line = key_line(opening)?;
1289 let stated = opening.key.is_some();
1290 let differs = !key_heads || alters != reading.alters || line != key_line(&shared)?;
1291 if stated && differs && (index > 0 || !key_heads) {
1292 let _ = writeln!(out, "K:{line}");
1293 }
1294 reading.alters = alters;
1295 }
1296 if let Some(program) = program_of(voice.part) {
1299 let _ = writeln!(out, "%%MIDI program {program}");
1300 }
1301 self.write_voice(voice, unit, &unit_text, options, &mut reading, &mut out)?;
1302 }
1303 Ok(out)
1304 }
1305
1306 fn write_voice(
1307 &self,
1308 voice: &Voice<'a>,
1309 unit: FloatType,
1310 unit_text: &str,
1311 options: &ExportOptions,
1312 reading: &mut Reading,
1313 out: &mut String,
1314 ) -> Result<()> {
1315 let mut tokens = Vec::new();
1318 let mut places = Vec::new();
1319 for (bar_index, bar) in voice.bars.iter().enumerate() {
1320 for (held_index, held) in bar.held.iter().enumerate() {
1321 if let Some(sounding) = sounding_of(*held)? {
1322 places.push((bar_index, held_index));
1323 tokens.push(sounding);
1324 }
1325 }
1326 }
1327 let opens_on = |sounding: &Sounding<'_>| self.opens.contains_key(&sounding.held.leaf);
1328 let markers: BTreeMap<(usize, usize), String> = tuplet_markers(&tokens, &opens_on)
1329 .into_iter()
1330 .map(|(token, marker)| (places[token], marker))
1331 .collect();
1332
1333 let count = voice.bars.len();
1339 let mut joined = vec![false; count];
1340 let mut plans = vec![Plan::default(); count];
1341 if voice.measured {
1342 let mut meter: Option<&TimeSignature> = None;
1343 for index in 0..count {
1344 let before = meter;
1345 if let Some(own) = meter_of(&voice.bars[index]) {
1346 meter = Some(own);
1347 }
1348 if index == 0 || joined[index - 1] {
1349 continue;
1350 }
1351 let (first, second) = (&voice.bars[index - 1], &voice.bars[index]);
1352 let moved = second
1353 .measure
1354 .and_then(Stream::left_barline)
1355 .is_some_and(|barline| {
1356 barline.repeat_direction() != Some(RepeatDirection::Start)
1357 });
1358 let closed = first.measure.and_then(Stream::right_barline).is_some()
1359 || second.measure.and_then(Stream::right_barline).is_some();
1360 let bar_length = before.map(TimeSignature::bar_quarter_length);
1361 let full = bar_length.is_some_and(|bar| (length_of(first) - bar).abs() < EPSILON);
1362 let remainder = meter_of(second).is_some()
1365 || bar_length.is_some_and(|bar| (length_of(second) - bar).abs() < EPSILON);
1366 let cut = cut_note(first, second);
1367 if (moved || (cut == Some(true) && remainder))
1368 && !closed
1369 && full
1370 && first.filler.is_none()
1371 && second.filler.is_none()
1372 {
1373 joined[index] = true;
1374 plans[index].drop_meter = true;
1375 if cut.is_some()
1376 && let Some(head) = first_sounding(second)
1377 {
1378 let head = second.held[head];
1379 plans[index].skip_head = true;
1380 plans[index - 1].tail = Some(Tail {
1381 extra: head.element.quarter_length(),
1382 tied: notes_of(head.element).is_some_and(|notes| {
1383 notes.iter().any(|note| {
1384 note.tie().map(crate::notation::Tie::tie_type)
1385 == Some(TieType::Continue)
1386 })
1387 }),
1388 closes: self
1389 .closes
1390 .get(&head.leaf)
1391 .map_or(&[][..], Vec::as_slice)
1392 .iter()
1393 .map(|kind| spanner_close(*kind))
1394 .collect(),
1395 });
1396 }
1397 if let (Some(next), Some(old)) = (voice.bars.get(index + 1), before)
1399 && meter_of(next)
1400 .is_some_and(|given| given.ratio_string() == old.ratio_string())
1401 {
1402 plans[index + 1].drop_meter = true;
1403 }
1404 meter = before;
1405 }
1406 }
1407 }
1408
1409 let mut written = Vec::new();
1410 for (index, bar) in voice.bars.iter().enumerate() {
1411 written.push(self.write_bar(
1412 voice,
1413 index,
1414 bar,
1415 unit,
1416 unit_text,
1417 &markers,
1418 reading,
1419 &plans[index],
1420 )?);
1421 }
1422 let barline_after = |index: usize| -> (String, bool) {
1424 if !voice.measured {
1425 return (String::new(), false);
1426 }
1427 let bar = &voice.bars[index];
1428 let right = if joined[index] {
1429 bar.measure.and_then(Stream::left_barline)
1430 } else {
1431 bar.measure.and_then(Stream::right_barline)
1432 };
1433 let next = voice.bars.get(index + 1).and_then(|bar| bar.measure);
1434 let field_can_follow = written
1436 .get(index + 1)
1437 .is_none_or(|next| next.opening.is_empty() && next.starts_with_note);
1438 barline_text(
1439 right,
1440 next.and_then(Stream::left_barline),
1441 next.and_then(Stream::ending),
1442 false,
1443 field_can_follow,
1444 )
1445 };
1446
1447 let has_lyrics = written
1448 .iter()
1449 .any(|bar| bar.sung.iter().any(|lyrics| !lyrics.is_empty()));
1450 let per_line = options.measures_per_line.max(1);
1451 let last = written.len().saturating_sub(1);
1452 let mut line = String::new();
1453 let mut on_line = 0;
1454 let mut sung: Vec<&[Lyric]> = Vec::new();
1455 if voice.measured {
1456 let first = voice.bars.first().and_then(|bar| bar.measure);
1457 line.push_str(
1458 &barline_text(
1459 None,
1460 first.and_then(Stream::left_barline),
1461 first.and_then(Stream::ending),
1462 true,
1463 false,
1464 )
1465 .0,
1466 );
1467 }
1468 for (index, bar) in written.iter().enumerate() {
1469 if !line.is_empty() && !line.ends_with(' ') && !bar.music.is_empty() {
1470 line.push(' ');
1471 }
1472 line.push_str(&bar.music);
1473 sung.extend(bar.sung.iter().copied());
1474 if joined.get(index + 1) == Some(&true) {
1475 continue;
1476 }
1477 on_line += 1;
1478 let next = written.get(index + 1);
1479 let (barline, keep_off) = barline_after(index);
1480 let said_again = [format!("L:{unit_text}")];
1484 let opening: &[String] = match next {
1485 Some(next) if !next.opening.is_empty() => &next.opening,
1486 Some(_) if keep_off => &said_again,
1487 _ => &[],
1488 };
1489 let fields_follow = !opening.is_empty();
1490 let mid_word = sung
1492 .iter()
1493 .rev()
1494 .find_map(|lyrics| lyrics.first())
1495 .is_some_and(|lyric| lyric.raw_text().ends_with('-'));
1496 let breaks = index == last
1497 || !bar.closing.is_empty()
1498 || fields_follow
1499 || (on_line >= per_line && !(has_lyrics && mid_word));
1500 if !breaks {
1501 if !barline.is_empty() {
1502 let _ = write!(line, " {barline}");
1503 }
1504 continue;
1505 }
1506 let words = if has_lyrics {
1507 lyric_lines(&sung)
1508 } else {
1509 Vec::new()
1510 };
1511 sung.clear();
1512 on_line = 0;
1513 let before_bar = index != last && !words.is_empty() || !bar.closing.is_empty();
1516 if !before_bar && !barline.is_empty() {
1517 let _ = write!(line, " {barline}");
1518 }
1519 push_line(out, &line);
1520 line.clear();
1521 for words in words {
1522 let _ = writeln!(out, "w:{words}");
1523 }
1524 for field in &bar.closing {
1525 let _ = writeln!(out, "{field}");
1526 }
1527 if before_bar {
1528 line.push_str(&barline);
1529 }
1530 if !opening.is_empty() {
1531 push_line(out, &line);
1532 line.clear();
1533 for field in opening {
1534 let _ = writeln!(out, "{field}");
1535 }
1536 }
1537 }
1538 push_line(out, &line);
1539 Ok(())
1540 }
1541
1542 #[allow(clippy::too_many_arguments)]
1543 fn write_bar(
1544 &self,
1545 voice: &Voice<'a>,
1546 index: usize,
1547 bar: &Bar<'a>,
1548 unit: FloatType,
1549 unit_text: &str,
1550 markers: &BTreeMap<(usize, usize), String>,
1551 reading: &mut Reading,
1552 plan: &Plan,
1553 ) -> Result<Written<'a>> {
1554 let mut written = Written::default();
1555 if let Some(length) = bar.filler {
1556 if length > EPSILON {
1557 written.music = format!("x{}", length_text(length, unit)?);
1558 }
1559 return Ok(written);
1560 }
1561 let end = bar
1562 .held
1563 .iter()
1564 .map(|held| held.offset + held.element.quarter_length())
1565 .fold(0.0, FloatType::max);
1566 let first = index == 0;
1567 let skipped = first_sounding(bar).filter(|_| plan.skip_head);
1568 let tailed = last_sounding(bar).filter(|_| plan.tail.is_some());
1569
1570 let stated = bar
1574 .held
1575 .iter()
1576 .find(|held| held.offset < EPSILON && signature_of(held.element).is_some());
1577 let mut key_closes = false;
1578 let mut silent_key = None;
1579 if voice.measured && !first {
1580 let unmarked = unmarked_notes(bar, skipped);
1581 let fits = |alters: &Alters| {
1582 unmarked
1583 .iter()
1584 .all(|(letter, alter)| alters[*letter] == *alter)
1585 };
1586 match stated.and_then(|held| signature_of(held.element)) {
1587 Some(signature) => {
1588 let alters = alters_of(&signature);
1589 if !fits(&alters) && fits(&reading.alters) {
1590 key_closes = true;
1591 } else {
1592 reading.alters = alters;
1593 }
1594 }
1595 None if !fits(&reading.alters) => {
1596 let in_force = self.key_in_force(voice, index);
1600 let candidates = in_force.into_iter().chain((-7..=7).map(KeySignature::new));
1601 silent_key = candidates
1602 .map(|signature| (alters_of(&signature), signature))
1603 .find(|(alters, _)| fits(alters));
1604 }
1605 None => {}
1606 }
1607 }
1608
1609 let mut opening_clef = None;
1612 for held in &bar.held {
1613 if held.offset > EPSILON || first || !voice.measured {
1614 continue;
1615 }
1616 match held.element {
1617 StreamElement::TimeSignature(meter) if !plan.drop_meter => {
1618 written.opening.push(format!("M:{}", meter_field(meter)));
1619 }
1620 StreamElement::MetronomeMark(mark) if end > EPSILON => {
1621 if let Some(field) = tempo_field(mark)? {
1622 written.opening.push(format!("Q:{field}"));
1623 }
1624 }
1625 StreamElement::Clef(clef) => opening_clef = Some(clef),
1626 StreamElement::Key(_) | StreamElement::KeySignature(_) => {
1627 let field = format!("K:{}", key_field(held.element, None)?);
1628 if key_closes {
1629 written.closing.push(field);
1630 } else {
1631 written.opening.push(field);
1632 }
1633 }
1634 _ => {}
1635 }
1636 }
1637
1638 let mut music = Vec::new();
1639 let mut cursor = 0.0;
1640 let mut symbol: Option<String> = None;
1643 let mut before = String::new();
1644 let mut waiting_since: Option<FloatType> = None;
1645 let mut graces = String::new();
1646 let mut grace_slash = false;
1647 let mut grace_closes = String::new();
1648 let mut lead = String::new();
1649 let mut first_token = true;
1650 if let Some(clef) = opening_clef {
1651 lead.push_str(&format!("[K:clef={}]", clef_name(clef)?));
1652 }
1653
1654 for (held_index, held) in bar.held.iter().enumerate() {
1655 let at_start = held.offset < EPSILON;
1656 let header = at_start && (first || voice.measured);
1657 match held.element {
1658 StreamElement::Stream(_)
1659 | StreamElement::Instrument(_)
1660 | StreamElement::Variant(_) => {}
1661 StreamElement::Clef(clef) => {
1662 if !header {
1663 lead.push_str(&format!("[K:clef={}]", clef_name(clef)?));
1664 }
1665 }
1666 StreamElement::TimeSignature(meter) => {
1667 if !header {
1668 self.field(
1669 voice,
1670 &mut music,
1671 &mut lead,
1672 format!("M:{}", meter_field(meter)),
1673 );
1674 }
1675 }
1676 StreamElement::Key(_) | StreamElement::KeySignature(_) => {
1677 if !header {
1678 let field = format!("K:{}", key_field(held.element, None)?);
1679 self.field(voice, &mut music, &mut lead, field);
1680 if !voice.measured
1681 && let Some(signature) = signature_of(held.element)
1682 {
1683 reading.alters = alters_of(&signature);
1684 }
1685 }
1686 }
1687 StreamElement::MetronomeMark(mark) => {
1688 let Some(field) = tempo_field(mark)? else {
1689 continue;
1690 };
1691 if header && (first || end > EPSILON) {
1692 } else if voice.measured && (held.offset - end).abs() < EPSILON {
1695 written.closing.push(format!("Q:{field}"));
1696 } else {
1697 self.field(voice, &mut music, &mut lead, format!("Q:{field}"));
1698 }
1699 }
1700 StreamElement::TempoText(text) => {
1701 let field = format!("Q:\"{}\"", quoted(text.text()));
1702 self.field(voice, &mut music, &mut lead, field);
1703 }
1704 StreamElement::ChordSymbol(chord_symbol) => {
1705 if symbol.is_some() {
1706 return Err(abc_error("ABC writes one chord symbol on a note"));
1707 }
1708 symbol = Some(format!("\"{}\"", symbol_text(chord_symbol)));
1709 waiting_since.get_or_insert(held.offset);
1710 }
1711 StreamElement::Dynamic(dynamic) => {
1712 let value = dynamic.value();
1713 if value.is_empty() || !value.chars().all(|c| c.is_ascii_alphabetic()) {
1714 return Err(abc_error(format!("ABC has no dynamic {value:?}")));
1715 }
1716 let _ = write!(before, "!{value}!");
1717 waiting_since.get_or_insert(held.offset);
1718 }
1719 StreamElement::TextExpression(expression) => {
1720 let side = if expression.placement() == Some(Placement::Below) {
1721 '_'
1722 } else {
1723 '^'
1724 };
1725 let _ = write!(before, "\"{side}{}\"", quoted(expression.content()));
1726 waiting_since.get_or_insert(held.offset);
1727 }
1728 StreamElement::RepeatExpression(mark) => {
1729 let sign = match mark.kind() {
1730 RepeatExpressionKind::Coda => "!coda!".to_string(),
1731 RepeatExpressionKind::Segno => "!segno!".to_string(),
1732 RepeatExpressionKind::Fine => "!fine!".to_string(),
1733 RepeatExpressionKind::DaCapo => "!D.C.!".to_string(),
1734 RepeatExpressionKind::DalSegno => "!D.S.!".to_string(),
1735 _ => format!("\"^{}\"", quoted(mark.text())),
1736 };
1737 before.push_str(&sign);
1738 waiting_since.get_or_insert(held.offset);
1739 }
1740 StreamElement::Barline(_)
1744 | StreamElement::Layout(_)
1745 | StreamElement::TextBox(_) => {}
1746 StreamElement::PedalObject(_) => {
1747 return Err(abc_error("ABC has no pedal bounces or gaps"));
1748 }
1749 StreamElement::RehearsalMark(_) => {
1750 return Err(abc_error("ABC has no rehearsal marks"));
1751 }
1752 StreamElement::Break(_) => {
1753 return Err(abc_error(
1754 "ABC writes no manuscript's line, page or column breaks",
1755 ));
1756 }
1757 StreamElement::MetricModulation(_) => {
1758 return Err(abc_error("ABC has no metric modulations"));
1759 }
1760 StreamElement::Unpitched(_) | StreamElement::PercussionChord(_) => {
1761 return Err(abc_error("ABC has no unpitched notes"));
1762 }
1763 StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_) => {
1764 if skipped == Some(held_index) {
1765 cursor = held.offset + held.element.quarter_length();
1766 continue;
1767 }
1768 let tail = plan.tail.as_ref().filter(|_| tailed == Some(held_index));
1769 let sounding = sounding_of(*held)?
1770 .ok_or_else(|| abc_error("a note with nothing to write it from"))?;
1771 let opens = self.opens.get(&held.leaf).map_or(&[][..], Vec::as_slice);
1772 let closes: String = self
1773 .closes
1774 .get(&held.leaf)
1775 .map_or(&[][..], Vec::as_slice)
1776 .iter()
1777 .map(|kind| spanner_close(*kind))
1778 .collect();
1779 let marker = markers.get(&(index, held_index));
1780 if marker.is_some() || !opens.is_empty() {
1781 first_token = false;
1782 }
1783 let mut opening = String::new();
1784 for kind in opens {
1785 opening.push_str(spanner_open(*kind)?);
1786 }
1787 if sounding.grace {
1788 if graces.is_empty() {
1789 lead.push_str(marker.map_or("", String::as_str));
1790 lead.push_str(&opening);
1791 grace_slash = sounding
1792 .duration
1793 .grace()
1794 .is_some_and(crate::duration::Grace::slash);
1795 } else if marker.is_some() || !opening.is_empty() {
1796 lead.push_str(&opening);
1799 }
1800 graces.push_str(&self.note_text(&sounding, unit, &reading.alters, None)?);
1801 grace_closes.push_str(&closes);
1802 first_token = false;
1803 continue;
1804 }
1805
1806 if held.offset > cursor + EPSILON {
1808 music.push(format!("x{}", length_text(held.offset - cursor, unit)?));
1809 first_token = false;
1810 } else if held.offset < cursor - EPSILON {
1811 return Err(abc_error(
1812 "notes sound together outside a chord; ABC writes one line to a voice",
1813 ));
1814 }
1815 if waiting_since.is_some_and(|since| since < held.offset - EPSILON) {
1816 return Err(abc_error(
1817 "a chord symbol, dynamic or text stands where no note starts",
1818 ));
1819 }
1820 waiting_since = None;
1821
1822 let mut token = std::mem::take(&mut lead);
1823 if !graces.is_empty() {
1824 let _ = write!(
1825 token,
1826 "{{{}{}}}{}",
1827 if grace_slash { "/" } else { "" },
1828 std::mem::take(&mut graces),
1829 std::mem::take(&mut grace_closes)
1830 );
1831 token.push_str(marker.map_or("", String::as_str));
1832 } else if let Some(marker) = marker {
1833 token.push_str(marker);
1834 }
1835 token.push_str(&opening);
1836 if first_token
1837 && token.is_empty()
1838 && !articulations_of(held.element).iter().any(is_lettered)
1839 {
1840 written.starts_with_note = true;
1841 }
1842 first_token = false;
1843 token.push_str(symbol.take().as_deref().unwrap_or(""));
1844 token.push_str(&std::mem::take(&mut before));
1845 token.push_str(&self.note_text(&sounding, unit, &reading.alters, tail)?);
1846 token.push_str(&closes);
1847 token.push_str(tail.map_or("", |tail| tail.closes.as_str()));
1848 music.push(token);
1849 cursor = held.offset + held.element.quarter_length();
1850
1851 match held.element {
1852 StreamElement::Note(note) => written.sung.push(note.lyrics()),
1853 StreamElement::Chord(chord) => written.sung.push(chord.lyrics()),
1854 _ => {}
1855 }
1856 let beams = match held.element {
1858 StreamElement::Note(note) => Some(note.beams()),
1859 StreamElement::Chord(chord) => Some(chord.beams()),
1860 _ => None,
1861 };
1862 if !beams.is_some_and(beamed_on) {
1863 music.push(" ".to_string());
1864 }
1865 }
1866 }
1867 }
1868 if symbol.is_some() || !before.is_empty() {
1869 return Err(abc_error(
1870 "a chord symbol, dynamic or text stands where no note starts",
1871 ));
1872 }
1873 if !graces.is_empty() {
1875 lead.push_str(&format!(
1876 "{{{}{graces}}}{grace_closes}",
1877 if grace_slash { "/" } else { "" }
1878 ));
1879 }
1880 if !lead.is_empty() {
1881 music.push(lead);
1882 }
1883 written.music = music.concat().trim().to_string();
1884 if written.music.is_empty() && voice.measured {
1885 written.music = "y".to_string();
1887 }
1888
1889 if let Some((alters, signature)) =
1893 silent_key.filter(|_| written.opening.is_empty() && written.starts_with_note)
1894 {
1895 {
1896 written
1897 .opening
1898 .push(format!("K:{}", key_field(&signature.into(), None)?));
1899 reading.alters = alters;
1900 let again =
1903 self.write_bar(voice, index, bar, unit, unit_text, markers, reading, plan)?;
1904 written.music = again.music;
1905 }
1906 } else if written.opening.len() == 1 {
1907 written.opening.push(format!("L:{unit_text}"));
1908 }
1909 if key_closes && let Some(signature) = stated.and_then(|held| signature_of(held.element)) {
1910 reading.alters = alters_of(&signature);
1911 }
1912 Ok(written)
1913 }
1914
1915 fn field(&self, voice: &Voice<'a>, music: &mut Vec<String>, lead: &mut String, field: String) {
1918 if voice.measured {
1919 let _ = write!(lead, "[{field}]");
1920 } else {
1921 music.push(format!("\n{field}\n"));
1922 }
1923 }
1924
1925 fn key_in_force(&self, voice: &Voice<'a>, index: usize) -> Option<KeySignature> {
1927 voice.bars[..=index]
1928 .iter()
1929 .rev()
1930 .flat_map(|bar| bar.held.iter().rev())
1931 .find_map(|held| signature_of(held.element))
1932 }
1933
1934 fn note_text(
1937 &self,
1938 sounding: &Sounding<'a>,
1939 unit: FloatType,
1940 alters: &Alters,
1941 tail: Option<&Tail>,
1942 ) -> Result<String> {
1943 let length = length_text(sounding.written + tail.map_or(0.0, |tail| tail.extra), unit)?;
1944 let tied = |note: &Note| match tail {
1945 Some(tail) => tail.tied,
1946 None => matches!(
1947 note.tie().map(crate::notation::Tie::tie_type),
1948 Some(TieType::Start | TieType::Continue)
1949 ),
1950 };
1951 Ok(match sounding.held.element {
1952 StreamElement::Note(note) => {
1953 let marks = decorations(note.articulations(), note.expressions())?;
1956 format!(
1957 "{marks}{}{length}{}",
1958 pitch_text(note.pitch(), alters)?,
1959 if tied(note) { "-" } else { "" }
1960 )
1961 }
1962 StreamElement::Chord(chord) => chord_text(chord, alters, &length, tied)?,
1963 StreamElement::Rest(rest) => rest_text(rest, &length)?,
1964 _ => String::new(),
1965 })
1966 }
1967}
1968
1969#[derive(Clone, Default)]
1971struct Opening<'a> {
1972 meter: Option<&'a TimeSignature>,
1973 key: Option<&'a StreamElement>,
1974 clef: Option<&'a Clef>,
1975 tempos: Vec<&'a MetronomeMark>,
1976}
1977
1978impl<'a> Opening<'a> {
1979 fn of(held: &[Held<'a>]) -> Self {
1980 let mut opening = Self::default();
1981 for held in held.iter().filter(|held| held.offset < EPSILON) {
1982 match held.element {
1983 StreamElement::TimeSignature(meter) => opening.meter = Some(meter),
1984 StreamElement::Key(_) | StreamElement::KeySignature(_) => {
1985 opening.key = Some(held.element);
1986 }
1987 StreamElement::Clef(clef) => opening.clef = Some(clef),
1988 StreamElement::MetronomeMark(mark) => opening.tempos.push(mark),
1989 _ => {}
1990 }
1991 }
1992 opening
1993 }
1994}
1995
1996fn beamed_on(beams: &Beams) -> bool {
1998 matches!(
1999 beams
2000 .beams()
2001 .first()
2002 .and_then(crate::notation::Beam::beam_type),
2003 Some(BeamType::Start | BeamType::Continue)
2004 )
2005}
2006
2007fn unmarked_notes(bar: &Bar<'_>, skipped: Option<usize>) -> Vec<(usize, IntegerType)> {
2010 let mut notes = Vec::new();
2011 let mut take = |pitch: &Pitch| {
2012 if !shows_accidental(pitch) {
2013 notes.push((
2014 letter_index(pitch.step().as_char()),
2015 pitch.alter().round() as IntegerType,
2016 ));
2017 }
2018 };
2019 for (index, held) in bar.held.iter().enumerate() {
2020 if skipped == Some(index) {
2021 continue;
2022 }
2023 match held.element {
2024 StreamElement::Note(note) => take(note.pitch()),
2025 StreamElement::Chord(chord) => {
2026 chord.notes().iter().map(Note::pitch).for_each(&mut take)
2027 }
2028 _ => {}
2029 }
2030 }
2031 notes
2032}
2033
2034fn shows_accidental(pitch: &Pitch) -> bool {
2036 pitch
2037 .written_accidental()
2038 .is_some_and(|accidental| accidental.display_status() != Some(false))
2039}
2040
2041fn pitch_text(pitch: &Pitch, alters: &Alters) -> Result<String> {
2044 if pitch
2045 .microtone()
2046 .is_some_and(|microtone| microtone.alter() != 0.0)
2047 {
2048 return Err(abc_error("ABC has no microtones"));
2049 }
2050 let alter = pitch.alter();
2051 if alter.fract() != 0.0 {
2052 return Err(abc_error("ABC has no quarter-tone accidentals"));
2053 }
2054 let alter = alter as IntegerType;
2055 let letter = pitch.step().as_char();
2056 let marks = if shows_accidental(pitch) || alters[letter_index(letter)] != alter {
2057 accidental_marks(alter)?
2058 } else {
2059 ""
2060 };
2061 let octave = pitch.octave().unwrap_or(4);
2062 Ok(if octave >= 5 {
2063 format!(
2064 "{marks}{}{}",
2065 letter.to_ascii_lowercase(),
2066 "'".repeat((octave - 5) as usize)
2067 )
2068 } else {
2069 format!("{marks}{letter}{}", ",".repeat((4 - octave) as usize))
2070 })
2071}
2072
2073fn chord_text(
2074 chord: &Chord,
2075 alters: &Alters,
2076 length: &str,
2077 tied: impl Fn(&Note) -> bool,
2078) -> Result<String> {
2079 let mut text = decorations(chord.articulations(), chord.expressions())?;
2080 text.push('[');
2081 for note in chord.notes() {
2082 text.push_str(&pitch_text(note.pitch(), alters)?);
2083 if tied(note) {
2085 text.push('-');
2086 }
2087 }
2088 text.push(']');
2089 text.push_str(length);
2090 Ok(text)
2091}
2092
2093fn rest_text(rest: &Rest, length: &str) -> Result<String> {
2094 let marks = decorations(rest.articulations(), rest.expressions())?;
2095 let tied = matches!(
2096 rest.tie().map(crate::notation::Tie::tie_type),
2097 Some(TieType::Start | TieType::Continue)
2098 );
2099 Ok(format!(
2100 "{marks}{}{length}{}",
2101 if rest.hidden() { 'x' } else { 'z' },
2102 if tied { "-" } else { "" }
2103 ))
2104}
2105
2106fn symbol_text(symbol: &ChordSymbol) -> String {
2108 if symbol.is_no_chord() {
2109 return quoted(symbol.kind_text().unwrap_or("N.C."));
2110 }
2111 let figure: Vec<char> = symbol.figure().chars().collect();
2112 let mut text = String::new();
2113 for (index, character) in figure.iter().enumerate() {
2114 let after_letter = index > 0 && matches!(figure[index - 1], 'A'..='G');
2115 text.push(if *character == '-' && after_letter {
2116 'b'
2117 } else {
2118 *character
2119 });
2120 }
2121 quoted(&text)
2122}
2123
2124fn barline_text(
2133 right: Option<&Barline>,
2134 left: Option<&Barline>,
2135 ending: Option<&Ending>,
2136 opens_tune: bool,
2137 field_can_follow: bool,
2138) -> (String, bool) {
2139 let token = |barline: Option<&Barline>| -> Option<&'static str> {
2140 let barline = barline?;
2141 match barline.repeat_direction() {
2142 Some(RepeatDirection::End) => Some(":|"),
2143 Some(RepeatDirection::Start) => Some("|:"),
2144 None => match barline.bar_type() {
2145 BarlineType::Double => Some("||"),
2146 BarlineType::Final => Some("|]"),
2147 BarlineType::HeavyLight => Some("[|"),
2148 BarlineType::Dotted | BarlineType::Dashed => Some(":"),
2149 _ => None,
2150 },
2151 }
2152 };
2153 let repeats = |barline: Option<&Barline>| {
2154 barline.is_some_and(|barline| barline.repeat_direction().is_some())
2155 };
2156 let ending = ending.filter(|ending| ending.starts() && !ending.numbers().is_empty());
2157 let mut keep_off = false;
2158 let mut text = match (token(right), token(left)) {
2159 (Some(":|"), Some("|:")) => "::".to_string(),
2160 (Some(closing), Some(opening)) if closing == opening => closing.to_string(),
2161 (Some(":"), Some(opening)) => format!(": {opening}"),
2163 (Some(closing), Some(opening)) => format!("{closing}{opening}"),
2164 (Some(closing), None) if repeats(right) || opens_tune => closing.to_string(),
2165 (Some(closing), None) if field_can_follow && ending.is_none() => {
2168 keep_off = true;
2169 closing.to_string()
2170 }
2171 (Some(closing), None) => format!("{closing} |"),
2172 (None, Some(opening)) if opens_tune || repeats(left) => opening.to_string(),
2173 (None, Some(opening)) => format!("| {opening}"),
2176 (None, None) if opens_tune => String::new(),
2179 (None, None) => "|".to_string(),
2180 };
2181 if let Some(ending) = ending {
2182 let numbers = ending
2183 .numbers()
2184 .iter()
2185 .map(u32::to_string)
2186 .collect::<Vec<_>>()
2187 .join(",");
2188 if !numbers.is_empty() {
2189 if matches!(text.as_str(), "|" | ":|") && matches!(numbers.as_str(), "1" | "2") {
2192 text.push_str(&numbers);
2193 } else if matches!(numbers.as_str(), "1" | "2") {
2194 let _ = write!(text, "[{numbers}");
2195 } else if text.is_empty() {
2196 let _ = write!(text, "|{numbers}");
2197 } else {
2198 text.push_str(&numbers);
2199 }
2200 }
2201 }
2202 (text, keep_off)
2203}
2204
2205fn lyric_lines(sung: &[&[Lyric]]) -> Vec<String> {
2207 let mut numbers: Vec<IntegerType> = sung
2208 .iter()
2209 .flat_map(|lyrics| lyrics.iter().map(Lyric::number))
2210 .collect();
2211 numbers.sort_unstable();
2212 numbers.dedup();
2213 if numbers.is_empty() {
2214 numbers.push(1);
2217 }
2218 numbers
2219 .into_iter()
2220 .map(|number| {
2221 let mut words: Vec<String> = Vec::new();
2222 let mut word: Option<Vec<String>> = None;
2225 for lyrics in sung {
2226 let lyric = lyrics.iter().find(|lyric| lyric.number() == number);
2227 match lyric {
2228 None => match &mut word {
2229 Some(syllables) => syllables.push(String::new()),
2230 None => words.push("*".to_string()),
2231 },
2232 Some(lyric) => {
2233 let raw = lyric.raw_text();
2234 let continues = raw.len() > 1 && raw.ends_with('-');
2235 let syllable = lyric.text().replace(' ', "~").replace('-', "\\-");
2236 let syllable = if syllable.is_empty() {
2237 "*".to_string()
2238 } else {
2239 syllable
2240 };
2241 let joins = raw.len() > 1 && raw.starts_with('-');
2242 match &mut word {
2243 Some(syllables) if joins => syllables.push(syllable),
2244 _ => {
2245 if let Some(syllables) = word.take() {
2246 words.push(syllables.join("-"));
2247 }
2248 word = Some(vec![syllable]);
2249 }
2250 }
2251 if !continues && let Some(syllables) = word.take() {
2252 words.push(syllables.join("-"));
2253 }
2254 }
2255 }
2256 }
2257 if let Some(mut syllables) = word.take() {
2258 syllables.push(String::new());
2260 while syllables.len() > 2 && syllables[syllables.len() - 2].is_empty() {
2261 syllables.pop();
2262 }
2263 words.push(syllables.join("-"));
2264 }
2265 words.join(" ")
2266 })
2267 .collect()
2268}
2269
2270fn push_line(out: &mut String, line: &str) {
2271 for piece in line.split('\n') {
2274 let piece = piece.trim();
2275 if !piece.is_empty() {
2276 out.push_str(piece);
2277 out.push('\n');
2278 }
2279 }
2280}
2281
2282#[cfg(test)]
2283mod tests {
2284 use super::*;
2285 use crate::abc::from_abc;
2286
2287 fn round_trip(tune: &str) -> String {
2290 let score = from_abc(tune).unwrap();
2291 let written = to_abc(&score, &ExportOptions::default()).unwrap();
2292 assert_eq!(
2293 format!("{:#?}", from_abc(&written).unwrap()),
2294 format!("{score:#?}"),
2295 "read back differently:\n{written}"
2296 );
2297 written
2298 }
2299
2300 #[test]
2301 fn a_tune_is_written_with_its_header() {
2302 let written = round_trip(
2303 "X:7\nT:Tune\nT:Other\nC:Anon\nO:Here\nM:6/8\nL:1/8\nQ:3/8=120\nK:Ador\nABc def|gfe dcB|A6|]\n",
2304 );
2305 assert_eq!(
2306 written,
2307 "X:7\nT:Tune\nT:Other\nC:Anon\nO:Here\nM:6/8\nL:1/8\nQ:3/8=120\nK:Ador\nABc def | gfe dcB | A6 |]\n"
2308 );
2309 }
2310
2311 #[test]
2312 fn accidentals_octaves_and_lengths_are_written() {
2313 let written = round_trip("X:1\nM:4/4\nL:1/4\nK:G\n^C,/ _e'3/2 =F f/4 z3/4|c4|C4|]\n");
2314 assert!(
2315 written.contains("L:1/8\nK:G\n^C, _e'3 =F2 f/ z3/2 | c8 | C8 |]"),
2316 "{written}"
2317 );
2318 }
2319
2320 fn part_of(names: &[&str], opening: Vec<StreamElement>) -> Stream {
2323 let mut part = Stream::with_kind(StreamKind::Part);
2324 for (index, name) in names.iter().enumerate() {
2325 let mut measure = Stream::with_kind(StreamKind::Measure);
2326 if index == 0 {
2327 for element in &opening {
2328 measure.insert(0.0, element.clone());
2329 }
2330 }
2331 measure.insert(
2332 0.0,
2333 Note::from_name(name)
2334 .unwrap()
2335 .with_duration(Duration::whole()),
2336 );
2337 part.insert(index as FloatType * 4.0, measure);
2338 }
2339 part
2340 }
2341
2342 #[test]
2343 fn a_part_says_its_name_and_its_instrument_s_program() {
2344 let mut part = part_of(
2345 &["C4", "D4"],
2346 vec![StreamElement::TimeSignature(
2347 TimeSignature::new(4, 4).unwrap(),
2348 )],
2349 );
2350 part.set_name(Some("Guitar".to_string()));
2351 let mut guitar = crate::instrument::Instrument::new();
2352 guitar.set_midi_program(Some(24));
2353 part.insert(0.0, StreamElement::Instrument(Box::new(guitar)));
2354 let written = to_abc(&part, &ExportOptions::default()).unwrap();
2355 assert!(
2356 written.contains(
2357 "V:1 name=\"Guitar\"
2358%%MIDI program 24
2359"
2360 ),
2361 "{written}"
2362 );
2363 }
2364
2365 #[test]
2366 fn a_clef_other_than_treble_is_said_even_where_the_notes_imply_it() {
2367 let low = part_of(
2368 &["C2", "G2"],
2369 vec![
2370 StreamElement::Clef(Clef::of_kind(crate::clef::ClefKind::BassClef)),
2371 StreamElement::TimeSignature(TimeSignature::new(4, 4).unwrap()),
2372 ],
2373 );
2374 let written = to_abc(&low, &ExportOptions::default()).unwrap();
2375 assert!(written.contains("clef=bass"), "{written}");
2376 let high = part_of(
2377 &["C5", "G5"],
2378 vec![
2379 StreamElement::Clef(Clef::of_kind(crate::clef::ClefKind::TrebleClef)),
2380 StreamElement::TimeSignature(TimeSignature::new(4, 4).unwrap()),
2381 ],
2382 );
2383 let written = to_abc(&high, &ExportOptions::default()).unwrap();
2384 assert!(!written.contains("clef="), "{written}");
2385 }
2386}