abc_parser/datatypes/
writer.rs

1//! Turns datatype representations of ABC back into text.
2
3use super::*;
4
5pub trait ToABC {
6    // Using the &mut writer trick causes a recursion error in the compiler:
7    // https://github.com/rust-lang/rust/issues/37748#issuecomment-1368984423
8    //
9    // We work around it by using a dedicated function
10    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result;
11
12    fn write_abc(&self, mut writer: impl std::fmt::Write) -> std::fmt::Result {
13        self.write_mut_abc(&mut writer)
14    }
15
16    /// Creates a valid ABC text representation of the object.
17    fn to_abc(&self) -> String {
18        let mut s = String::new();
19        self.write_abc(&mut s).unwrap();
20        s
21    }
22}
23
24impl<T: ToABC> ToABC for Option<T> {
25    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
26        match self {
27            Some(h) => h.write_mut_abc(writer),
28            None => Ok(()),
29        }
30    }
31}
32
33impl<T: ToABC> ToABC for Vec<T> {
34    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
35        self.iter().try_for_each(|i| i.write_mut_abc(writer))
36    }
37}
38
39impl ToABC for Length {
40    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
41        #[allow(clippy::redundant_guards)]
42        match self.0 {
43            l if l < 1.0 && l > 0.0 => {
44                let inv = 1.0 / l;
45                let num_halves = inv.log2();
46                if num_halves.fract() == 0.0 {
47                    (0..(num_halves as u64)).try_for_each(|_| writer.write_str("/"))
48                } else {
49                    write!(writer, "/{}", inv as usize)
50                }
51            }
52            l if l >= 0.0 => write!(writer, "{}", fraction::Fraction::from(l)),
53            _ => panic!("Note lengths must be greater than 0!"),
54        }
55    }
56}
57
58impl ToABC for TuneBook {
59    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
60        self.comments.iter().try_for_each(|c| {
61            c.write_mut_abc(writer)?;
62            writer.write_str("\n")
63        })?;
64
65        if let Some(ref header) = self.header {
66            header.write_mut_abc(writer)?;
67            writer.write_str("\n")?;
68        }
69
70        self.tunes.iter().try_for_each(|t| {
71            t.write_mut_abc(writer)?;
72            writer.write_str("\n")
73        })
74    }
75}
76
77impl ToABC for IgnoredLine {
78    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
79        match self {
80            IgnoredLine::Comment(comment) => comment.write_mut_abc(writer),
81            IgnoredLine::EmptyLine => Ok(()),
82        }
83    }
84}
85
86impl ToABC for FileHeader {
87    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
88        join_header_lines(&self.lines, writer)
89    }
90}
91
92impl ToABC for InfoField {
93    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
94        write!(writer, "{}:{}", self.0, self.1)
95    }
96}
97
98impl ToABC for Tune {
99    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
100        self.header.write_mut_abc(writer)?;
101        self.body.write_mut_abc(writer)
102    }
103}
104
105impl ToABC for TuneHeader {
106    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
107        join_header_lines(&self.lines, writer)
108    }
109}
110
111impl ToABC for HeaderLine {
112    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
113        match self {
114            HeaderLine::Field(field, comment) => {
115                field.write_mut_abc(writer)?;
116                comment.write_mut_abc(writer)
117            }
118            HeaderLine::Comment(comment) => comment.write_mut_abc(writer),
119        }
120    }
121}
122
123fn join_header_lines(lines: &[HeaderLine], writer: &mut impl std::fmt::Write) -> std::fmt::Result {
124    for line in lines {
125        line.write_mut_abc(writer)?;
126        writer.write_str("\n")?;
127    }
128
129    Ok(())
130}
131
132impl ToABC for TuneBody {
133    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
134        for (i, line) in self.lines.iter().enumerate() {
135            line.write_mut_abc(writer)?;
136            if i + 1 != self.lines.len() {
137                writer.write_str("\n")?;
138            }
139        }
140
141        Ok(())
142    }
143}
144
145impl ToABC for TuneLine {
146    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
147        match self {
148            TuneLine::Comment(comment) => comment.write_mut_abc(writer),
149            TuneLine::Music(line) => line.write_mut_abc(writer),
150            TuneLine::Symbol(line) => line.write_mut_abc(writer),
151            TuneLine::Lyric(line) => line.write_mut_abc(writer),
152        }
153    }
154}
155
156impl ToABC for MusicLine {
157    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
158        self.symbols
159            .iter()
160            .try_for_each(|symbol| symbol.write_mut_abc(writer))
161    }
162}
163
164impl ToABC for SymbolLine {
165    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
166        writer.write_str("s:")?;
167        self.symbols
168            .iter()
169            .try_for_each(|symbol| symbol.write_mut_abc(writer))
170    }
171}
172
173impl ToABC for LyricLine {
174    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
175        writer.write_str("w:")?;
176        self.symbols
177            .iter()
178            .try_for_each(|symbol| symbol.write_mut_abc(writer))
179    }
180}
181
182impl ToABC for MusicSymbol {
183    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
184        use super::MusicSymbol::*;
185        match self {
186            Note {
187                accidental,
188                note,
189                octave,
190                length,
191                tie,
192            } => {
193                let (note, octave) = denormalise_octave(*note, *octave);
194                accidental.write_mut_abc(writer)?;
195                writer.write_char(note)?;
196                write_octave(octave, writer)?;
197                length.write_mut_abc(writer)?;
198                tie.write_mut_abc(writer)
199            }
200            GraceNotes {
201                acciaccatura,
202                notes,
203            } => {
204                write!(writer, "{{{}", acciaccatura.map_or("", |_| "/"))?;
205                notes.write_mut_abc(writer)?;
206                writer.write_str("}")
207            }
208            Chord { notes, length, tie } => {
209                writer.write_str("[")?;
210                notes.write_mut_abc(writer)?;
211                writer.write_str("]")?;
212                length.write_mut_abc(writer)?;
213                tie.write_mut_abc(writer)
214            }
215            Tuplet { p, q, r } => match (q, r) {
216                (None, None) => write!(writer, "({}", p),
217                (Some(q), None) => write!(writer, "({}:{}", p, q),
218                (None, Some(r)) => write!(writer, "({}::{}", p, r),
219                (Some(q), Some(r)) => write!(writer, "({}:{}:{}", p, q, r),
220            },
221            BrokenRhythm {
222                rhythm,
223                before,
224                after,
225            } => {
226                before.write_mut_abc(writer)?;
227                writer.write_str(rhythm)?;
228                after.write_mut_abc(writer)
229            }
230            Decoration(decoration) => decoration.write_mut_abc(writer),
231            Annotation(annotation) => annotation.write_mut_abc(writer),
232            Bar(bar, ending) => {
233                writer.write_str(bar)?;
234                if let Some(ending) = ending {
235                    writer.write_str(ending)?;
236                }
237                Ok(())
238            }
239            Beam(beam) => writer.write_str(beam),
240            Slur(slur) => slur.write_mut_abc(writer),
241            Comment(comment) => comment.write_mut_abc(writer),
242            Rest(rest) => rest.write_mut_abc(writer),
243            Spacer => writer.write_str("y"),
244            Ending(n) => write!(writer, "[{}", n),
245            InlineField(info_field, bracketed) => {
246                if *bracketed {
247                    writer.write_str("[")?;
248                }
249                info_field.write_mut_abc(writer)?;
250                if *bracketed {
251                    writer.write_str("]")?;
252                }
253                Ok(())
254            }
255            Space(s) => writer.write_str(s),
256            Reserved(s) => writer.write_str(s),
257        }
258    }
259}
260
261impl ToABC for SymbolLineSymbol {
262    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
263        use super::SymbolLineSymbol::*;
264        match self {
265            Annotation(annotation) => annotation.write_mut_abc(writer),
266            Decoration(decoration) => decoration.write_mut_abc(writer),
267            SymbolAlignment(alignment) => alignment.write_mut_abc(writer),
268            Space(s) => writer.write_str(s),
269        }
270    }
271}
272
273impl ToABC for LyricSymbol {
274    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
275        use super::LyricSymbol::*;
276        match self {
277            Syllable(syllable) => writer.write_str(syllable),
278            SymbolAlignment(alignment) => alignment.write_mut_abc(writer),
279            Space(s) => writer.write_str(s),
280        }
281    }
282}
283
284impl ToABC for Annotation {
285    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
286        writer.write_char('"')?;
287        self.placement.write_mut_abc(writer)?;
288        write!(writer, "{}\"", self.text)
289    }
290}
291
292impl ToABC for Placement {
293    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
294        use super::Placement::*;
295        writer.write_str(match self {
296            Above => "^",
297            Below => "_",
298            Left => "<",
299            Right => ">",
300            Auto => "@",
301        })
302    }
303}
304
305impl ToABC for Tie {
306    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
307        use super::Tie::*;
308        writer.write_str(match self {
309            Solid => "-",
310            Dotted => ".-",
311        })
312    }
313}
314
315impl ToABC for Slur {
316    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
317        use super::Slur::*;
318        writer.write_str(match self {
319            Begin => "(",
320            BeginDotted => ".(",
321            End => ")",
322        })
323    }
324}
325
326/// Given a note and an octave, returns the uppercase or lowercase form of the note which makes the
327/// octave closest to 1, and the octave adjusted accordingly.
328fn denormalise_octave(note: Note, octave: i8) -> (char, i8) {
329    let note_char: char = note.into();
330    if octave > 1 {
331        (note_char.to_ascii_lowercase(), octave - 1)
332    } else {
333        (note_char, octave)
334    }
335}
336
337impl ToABC for Decoration {
338    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
339        use super::Decoration::*;
340        match self {
341            Staccato => writer.write_str("."),
342            Roll => writer.write_str("~"),
343            Fermata => writer.write_str("H"),
344            Accent => writer.write_str("L"),
345            LowerMordent => writer.write_str("M"),
346            Coda => writer.write_str("O"),
347            UpperMordent => writer.write_str("P"),
348            Segno => writer.write_str("S"),
349            Trill => writer.write_str("T"),
350            UpBow => writer.write_str("u"),
351            DownBow => writer.write_str("v"),
352            Unresolved(s) => write!(writer, "!{}!", s),
353        }
354    }
355}
356
357impl ToABC for Accidental {
358    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
359        use super::Accidental::*;
360        writer.write_str(match self {
361            Natural => "=",
362            Sharp => "^",
363            Flat => "_",
364            DoubleSharp => "^^",
365            DoubleFlat => "__",
366        })
367    }
368}
369
370impl ToABC for SymbolAlignment {
371    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
372        use super::SymbolAlignment::*;
373        writer.write_str(match self {
374            Break => "-",
375            Extend => "_",
376            Skip => "*",
377            Space => "~",
378            Hyphen => "\\-",
379            Bar => "|",
380        })
381    }
382}
383
384fn write_octave(octave: i8, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
385    match octave {
386        o if o > 1 => (0..(octave - 1) as usize).try_for_each(|_| write!(writer, "'")),
387        o if o < 1 => (0..(-octave + 1) as usize).try_for_each(|_| write!(writer, ",")),
388        _ => Ok(()),
389    }
390}
391
392impl ToABC for Rest {
393    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
394        use super::Rest::*;
395        match self {
396            Note(l) => {
397                writer.write_str("z")?;
398                l.write_mut_abc(writer)
399            }
400            Measure(l) => {
401                writer.write_str("Z")?;
402                l.write_mut_abc(writer)
403            }
404            NoteHidden(l) => {
405                writer.write_str("x")?;
406                l.write_mut_abc(writer)
407            }
408            MeasureHidden(l) => {
409                writer.write_str("X")?;
410                l.write_mut_abc(writer)
411            }
412        }
413    }
414}
415
416impl ToABC for Comment {
417    fn write_mut_abc(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result {
418        use super::Comment::*;
419        match self {
420            Comment(comment) => write!(writer, "%{comment}"),
421            CommentLine(space, comment) => write!(writer, "{space}%{comment}"),
422            StylesheetDirective(directive) => write!(writer, "%%{directive}"),
423        }
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use pretty_assertions::assert_eq;
430
431    use super::*;
432    use crate::datatypes::builder::NoteBuilder;
433
434    #[test]
435    fn info_field() {
436        let f = InfoField('X', "1".to_string());
437        let s = f.to_abc();
438        assert_eq!(s, "X:1")
439    }
440
441    #[test]
442    fn file_header() {
443        let h = FileHeader {
444            lines: vec![
445                HeaderLine::Field(InfoField('X', "1".to_string()), None),
446                HeaderLine::Field(InfoField('T', "Untitled".to_string()), None),
447                HeaderLine::Field(InfoField('K', "G".to_string()), None),
448            ],
449        };
450        let s = h.to_abc();
451        assert_eq!(s, "X:1\nT:Untitled\nK:G\n")
452    }
453
454    #[test]
455    fn file_header_with_comments() {
456        let h = FileHeader {
457            lines: vec![
458                HeaderLine::Field(InfoField('X', "1".to_string()), None),
459                HeaderLine::Field(
460                    InfoField('T', "Untitled   ".to_string()),
461                    Some(Comment::Comment(" Inline comment".to_string())),
462                ),
463                HeaderLine::Field(InfoField('K', "G".to_string()), None),
464            ],
465        };
466        let s = h.to_abc();
467        assert_eq!(s, "X:1\nT:Untitled   % Inline comment\nK:G\n")
468    }
469
470    #[test]
471    fn file_header_with_stylesheet_directive() {
472        let h = FileHeader {
473            lines: vec![
474                HeaderLine::Field(InfoField('X', "1".to_string()), None),
475                HeaderLine::Field(InfoField('T', "Untitled".to_string()), None),
476                HeaderLine::Comment(Comment::StylesheetDirective("papersize A4".to_string())),
477                HeaderLine::Field(InfoField('K', "G".to_string()), None),
478            ],
479        };
480        let s = h.to_abc();
481        assert_eq!(s, "X:1\nT:Untitled\n%%papersize A4\nK:G\n")
482    }
483
484    #[test]
485    fn accidental_1() {
486        assert_eq!(Accidental::Natural.to_abc(), "=")
487    }
488    #[test]
489    fn accidental_2() {
490        assert_eq!(Accidental::Sharp.to_abc(), "^")
491    }
492    #[test]
493    fn accidental_3() {
494        assert_eq!(Accidental::Flat.to_abc(), "_")
495    }
496    #[test]
497    fn accidental_4() {
498        assert_eq!(Accidental::DoubleSharp.to_abc(), "^^")
499    }
500    #[test]
501    fn accidental_5() {
502        assert_eq!(Accidental::DoubleFlat.to_abc(), "__")
503    }
504
505    #[test]
506    fn note_with_accidental() {
507        assert_eq!(
508            NoteBuilder::new(Note::C)
509                .accidental(Accidental::Sharp)
510                .build()
511                .to_abc(),
512            "^C"
513        );
514    }
515
516    #[test]
517    fn decoration_1() {
518        assert_eq!(Decoration::Staccato.to_abc(), ".")
519    }
520    #[test]
521    fn decoration_2() {
522        assert_eq!(Decoration::Roll.to_abc(), "~")
523    }
524    #[test]
525    fn decoration_3() {
526        assert_eq!(Decoration::Fermata.to_abc(), "H")
527    }
528    #[test]
529    fn decoration_4() {
530        assert_eq!(Decoration::Accent.to_abc(), "L")
531    }
532    #[test]
533    fn decoration_5() {
534        assert_eq!(Decoration::LowerMordent.to_abc(), "M")
535    }
536    #[test]
537    fn decoration_6() {
538        assert_eq!(Decoration::Coda.to_abc(), "O")
539    }
540    #[test]
541    fn decoration_7() {
542        assert_eq!(Decoration::UpperMordent.to_abc(), "P")
543    }
544    #[test]
545    fn decoration_8() {
546        assert_eq!(Decoration::Segno.to_abc(), "S")
547    }
548    #[test]
549    fn decoration_9() {
550        assert_eq!(Decoration::Trill.to_abc(), "T")
551    }
552    #[test]
553    fn decoration_10() {
554        assert_eq!(Decoration::UpBow.to_abc(), "u")
555    }
556    #[test]
557    fn decoration_11() {
558        assert_eq!(Decoration::DownBow.to_abc(), "v")
559    }
560    #[test]
561    fn decoration_12() {
562        assert_eq!(
563            Decoration::Unresolved("asdf".to_string()).to_abc(),
564            "!asdf!"
565        )
566    }
567
568    #[test]
569    fn rest_1() {
570        assert_eq!(Rest::Note(None).to_abc(), "z")
571    }
572    #[test]
573    fn rest_2() {
574        assert_eq!(Rest::Note(Some(Length::new(2.0))).to_abc(), "z2")
575    }
576    #[test]
577    fn rest_3() {
578        assert_eq!(Rest::Measure(None).to_abc(), "Z")
579    }
580    #[test]
581    fn rest_4() {
582        assert_eq!(Rest::Measure(Some(Length::new(2.0))).to_abc(), "Z2")
583    }
584    #[test]
585    fn rest_5() {
586        assert_eq!(Rest::NoteHidden(None).to_abc(), "x")
587    }
588    #[test]
589    fn rest_6() {
590        assert_eq!(Rest::NoteHidden(Some(Length::new(4.0))).to_abc(), "x4")
591    }
592    #[test]
593    fn rest_7() {
594        assert_eq!(Rest::MeasureHidden(None).to_abc(), "X")
595    }
596    #[test]
597    fn rest_8() {
598        assert_eq!(Rest::MeasureHidden(Some(Length::new(3.0))).to_abc(), "X3")
599    }
600
601    #[test]
602    fn spacer() {
603        assert_eq!(MusicSymbol::Spacer.to_abc(), "y")
604    }
605
606    #[test]
607    fn ending_1() {
608        assert_eq!(MusicSymbol::Ending("1".to_string()).to_abc(), "[1")
609    }
610    #[test]
611    fn ending_2() {
612        assert_eq!(MusicSymbol::Ending("2".to_string()).to_abc(), "[2")
613    }
614    #[test]
615    fn ending_3() {
616        assert_eq!(
617            MusicSymbol::Ending("1,3,5-7,8-10".to_string()).to_abc(),
618            "[1,3,5-7,8-10"
619        )
620    }
621
622    #[test]
623    fn bar_ending() {
624        assert_eq!(
625            MusicSymbol::Bar("|".to_string(), Some("2".to_string())).to_abc(),
626            "|2"
627        )
628    }
629
630    #[test]
631    fn octave_1() {
632        assert_eq!(NoteBuilder::new(Note::C).octave(2).build().to_abc(), "c");
633    }
634    #[test]
635    fn octave_2() {
636        assert_eq!(NoteBuilder::new(Note::C).octave(3).build().to_abc(), "c'");
637    }
638    #[test]
639    fn octave_3() {
640        assert_eq!(NoteBuilder::new(Note::C).octave(4).build().to_abc(), "c''");
641    }
642    #[test]
643    fn octave_4() {
644        assert_eq!(NoteBuilder::new(Note::C).octave(0).build().to_abc(), "C,");
645    }
646    #[test]
647    fn octave_5() {
648        assert_eq!(NoteBuilder::new(Note::C).octave(-1).build().to_abc(), "C,,");
649    }
650
651    #[test]
652    fn length_1() {
653        assert_eq!(NoteBuilder::new(Note::C).build().to_abc(), "C");
654    }
655    #[test]
656    fn length_2() {
657        assert_eq!(NoteBuilder::new(Note::C).length(2.0).build().to_abc(), "C2");
658    }
659    #[test]
660    fn length_3() {
661        assert_eq!(NoteBuilder::new(Note::C).length(0.5).build().to_abc(), "C/");
662    }
663    #[test]
664    fn length_4() {
665        assert_eq!(NoteBuilder::new(Note::C).length(1.0).build().to_abc(), "C1");
666    }
667    #[test]
668    fn length_5() {
669        assert_eq!(
670            NoteBuilder::new(Note::C).length(1.5).build().to_abc(),
671            "C3/2"
672        );
673    }
674    #[test]
675    fn length_6() {
676        assert_eq!(
677            NoteBuilder::new(Note::C).length(0.125).build().to_abc(),
678            "C///"
679        );
680    }
681    #[test]
682    fn length_7() {
683        assert_eq!(
684            NoteBuilder::new(Note::C).length(1.0 / 3.0).build().to_abc(),
685            "C/3"
686        );
687    }
688    #[test]
689    fn length_8() {
690        assert_eq!(
691            NoteBuilder::new(Note::C).length(0.2).build().to_abc(),
692            "C/5"
693        );
694    }
695    #[ignore]
696    #[test]
697    fn length_9() {
698        assert_eq!(
699            NoteBuilder::new(Note::C).length(2.0 / 3.0).build().to_abc(),
700            "C2/3"
701        );
702    }
703
704    #[test]
705    fn tie() {
706        assert_eq!(
707            NoteBuilder::new(Note::C).tie(Tie::Solid).build().to_abc(),
708            "C-"
709        );
710    }
711
712    #[test]
713    fn slur_1() {
714        assert_eq!(MusicSymbol::Slur(Slur::Begin).to_abc(), "(");
715    }
716    #[test]
717    fn slur_2() {
718        assert_eq!(MusicSymbol::Slur(Slur::BeginDotted).to_abc(), ".(");
719    }
720    #[test]
721    fn slur_3() {
722        assert_eq!(MusicSymbol::Slur(Slur::End).to_abc(), ")");
723    }
724
725    #[test]
726    fn beam() {
727        assert_eq!(MusicSymbol::Beam("````".to_string()).to_abc(), "````")
728    }
729
730    #[test]
731    fn grace_notes_1() {
732        assert_eq!(
733            MusicSymbol::GraceNotes {
734                acciaccatura: None,
735                notes: vec![
736                    NoteBuilder::new(Note::A)
737                        .accidental(Accidental::DoubleSharp)
738                        .build(),
739                    NoteBuilder::new(Note::B).build(),
740                    NoteBuilder::new(Note::C)
741                        .accidental(Accidental::Flat)
742                        .octave(2)
743                        .length(2.0)
744                        .build(),
745                ]
746            }
747            .to_abc(),
748            "{^^AB_c2}"
749        );
750    }
751
752    #[test]
753    fn grace_notes_2() {
754        assert_eq!(
755            MusicSymbol::GraceNotes {
756                acciaccatura: Some(()),
757                notes: vec![
758                    NoteBuilder::new(Note::A)
759                        .accidental(Accidental::DoubleSharp)
760                        .build()
761                ]
762            }
763            .to_abc(),
764            "{/^^A}"
765        );
766    }
767
768    #[test]
769    fn tuplets_1() {
770        assert_eq!(
771            MusicSymbol::Tuplet {
772                p: 3,
773                q: None,
774                r: None,
775            }
776            .to_abc(),
777            "(3"
778        );
779    }
780
781    #[test]
782    fn tuplets_2() {
783        assert_eq!(
784            MusicSymbol::Tuplet {
785                p: 3,
786                q: Some(2),
787                r: None,
788            }
789            .to_abc(),
790            "(3:2"
791        );
792    }
793
794    #[test]
795    fn tuplets_3() {
796        assert_eq!(
797            MusicSymbol::Tuplet {
798                p: 3,
799                q: None,
800                r: Some(2),
801            }
802            .to_abc(),
803            "(3::2"
804        );
805    }
806
807    #[test]
808    fn broken_rhythm_1() {
809        assert_eq!(
810            MusicSymbol::BrokenRhythm {
811                rhythm: "<".to_string(),
812                before: vec![NoteBuilder::new(Note::A).build()],
813                after: vec![NoteBuilder::new(Note::B).build()]
814            }
815            .to_abc(),
816            "A<B"
817        );
818    }
819
820    #[test]
821    fn broken_rhythm_2() {
822        assert_eq!(
823            MusicSymbol::BrokenRhythm {
824                rhythm: ">".to_string(),
825                before: vec![NoteBuilder::new(Note::A).build()],
826                after: vec![NoteBuilder::new(Note::B).build()]
827            }
828            .to_abc(),
829            "A>B"
830        );
831    }
832
833    #[test]
834    fn broken_rhythm_3() {
835        assert_eq!(
836            MusicSymbol::BrokenRhythm {
837                rhythm: "<<".to_string(),
838                before: vec![NoteBuilder::new(Note::A).build()],
839                after: vec![NoteBuilder::new(Note::B).build()]
840            }
841            .to_abc(),
842            "A<<B"
843        );
844    }
845
846    #[test]
847    fn broken_rhythm_4() {
848        assert_eq!(
849            MusicSymbol::BrokenRhythm {
850                rhythm: ">>>".to_string(),
851                before: vec![NoteBuilder::new(Note::A).build()],
852                after: vec![NoteBuilder::new(Note::B).build()]
853            }
854            .to_abc(),
855            "A>>>B"
856        );
857    }
858
859    #[test]
860    fn broken_rhythm_5() {
861        assert_eq!(
862            MusicSymbol::BrokenRhythm {
863                rhythm: ">".to_string(),
864                before: vec![
865                    NoteBuilder::new(Note::A).build(),
866                    MusicSymbol::Space(" ".to_string()),
867                ],
868                after: vec![NoteBuilder::new(Note::B).build()]
869            }
870            .to_abc(),
871            "A >B"
872        );
873    }
874
875    #[test]
876    fn chord_1() {
877        assert_eq!(
878            MusicSymbol::Chord {
879                notes: vec![
880                    NoteBuilder::new(Note::C).build(),
881                    NoteBuilder::new(Note::E).build(),
882                    NoteBuilder::new(Note::G).build(),
883                    NoteBuilder::new(Note::C).octave(2).build(),
884                ],
885                length: None,
886                tie: None,
887            }
888            .to_abc(),
889            "[CEGc]"
890        );
891    }
892
893    #[test]
894    fn chord_2() {
895        assert_eq!(
896            MusicSymbol::Chord {
897                notes: vec![
898                    NoteBuilder::new(Note::C).build(),
899                    NoteBuilder::new(Note::E).length(2.0).build(),
900                    NoteBuilder::new(Note::G)
901                        .accidental(Accidental::Sharp)
902                        .build(),
903                    NoteBuilder::new(Note::C).octave(2).build(),
904                ],
905                length: Some(Length::new(3.0)),
906                tie: Some(Tie::Solid),
907            }
908            .to_abc(),
909            "[CE2^Gc]3-"
910        );
911    }
912
913    #[test]
914    fn annotation_1() {
915        assert_eq!(
916            Annotation::new(Some(Placement::Above), "foobar".to_string()).to_abc(),
917            "\"^foobar\""
918        );
919    }
920    #[test]
921    fn annotation_2() {
922        assert_eq!(
923            Annotation::new(Some(Placement::Below), "foobar".to_string()).to_abc(),
924            "\"_foobar\""
925        );
926    }
927    #[test]
928    fn annotation_3() {
929        assert_eq!(
930            Annotation::new(Some(Placement::Left), "foobar".to_string()).to_abc(),
931            "\"<foobar\""
932        );
933    }
934    #[test]
935    fn annotation_4() {
936        assert_eq!(
937            Annotation::new(Some(Placement::Right), "foobar".to_string()).to_abc(),
938            "\">foobar\""
939        );
940    }
941    #[test]
942    fn annotation_5() {
943        assert_eq!(
944            Annotation::new(Some(Placement::Auto), "foobar".to_string()).to_abc(),
945            "\"@foobar\""
946        );
947    }
948    #[test]
949    fn annotation_6() {
950        assert_eq!(
951            Annotation::new(None, "foobar".to_string()).to_abc(),
952            "\"foobar\""
953        );
954    }
955}