Skip to main content

music21/braille/
basic.rs

1//! One element of music in braille: music21's `braille.basic`.
2//!
3//! Each function answers a [`Transcription`]: the braille, and the English
4//! music21 writes beside it, one line a sign, which a segment's debugging
5//! listing shows.
6
7use super::lookup::{self, symbol};
8use crate::{
9    bar::Barline,
10    chord::Chord,
11    chordsymbol::ChordSymbol,
12    clef::Clef,
13    defaults::{FloatType, IntegerType},
14    duration::Duration,
15    dynamics::Dynamic,
16    error::{Error, Result},
17    expressions::{Expression, TextExpression},
18    instrument::Instrument,
19    meter::TimeSignature,
20    notation::TieType,
21    note::Note,
22    pitch::Pitch,
23    rest::Rest,
24    tempo::{MetronomeMark, TempoText},
25};
26
27/// An element in braille, and the English music21 writes for it.
28#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct Transcription {
30    /// The braille.
31    pub braille: String,
32    /// What each sign says, a line each: music21's `brailleEnglish`.
33    pub english: Vec<String>,
34}
35
36impl Transcription {
37    fn exception(english: Vec<String>) -> Self {
38        Self {
39            braille: symbol("basic_exception"),
40            english,
41        }
42    }
43}
44
45/// How a note stands among its neighbours, which a segment works out and
46/// the note's braille shows: the attributes music21 sets on a note for the
47/// time it is transcribed.
48#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
49pub struct NoteContext {
50    /// The note begins a slur long enough to be written with brackets.
51    pub begin_long_bracket_slur: bool,
52    /// The note ends such a slur.
53    pub end_long_bracket_slur: bool,
54    /// The note begins a long slur written doubled.
55    pub begin_long_double_slur: bool,
56    /// The note ends such a slur.
57    pub end_long_double_slur: bool,
58    /// The note is under a short slur, which is written after each note.
59    pub short_slur: bool,
60    /// The note begins a beamed group, which shows a tuplet's sign.
61    pub beam_start: bool,
62    /// The note continues a beamed group, written as an eighth.
63    pub beam_continue: bool,
64}
65
66/// music21's name for a duration's value: `complex` for one of several
67/// values and `zero` for none.
68pub(crate) fn duration_type(duration: &Duration) -> String {
69    let values = duration.written_values();
70    match values.as_slice() {
71        [] => "zero".to_string(),
72        [one] => one.duration_type().map_or_else(
73            || "inexpressible".to_string(),
74            |value| value.music21_name().to_string(),
75        ),
76        _ => "complex".to_string(),
77    }
78}
79
80/// The dots of a duration's first value: music21's `dots`.
81pub(crate) fn duration_dots(duration: &Duration) -> u32 {
82    duration
83        .written_values()
84        .first()
85        .map_or(0, |value| value.dots())
86}
87
88/// A length as music21's `repr` writes it: a float, or a fraction where
89/// music21 keeps one.
90pub(crate) fn quarter_length_repr(length: FloatType) -> String {
91    match crate::duration::limited_fraction(length, 65535) {
92        Some((numerator, denominator))
93            if denominator != 0
94                && !(denominator as u64).is_power_of_two()
95                && ((numerator as FloatType / denominator as FloatType) - length).abs() < 1e-9 =>
96        {
97            format!("{numerator}/{denominator}")
98        }
99        _ => crate::statistics::python_repr(length),
100    }
101}
102
103/// Text centred in so many characters as Python's `str.center` centres it,
104/// the odd one of padding to the left only where the width and the padding
105/// are both odd.
106pub(crate) fn center(text: &str, width: usize, fill: &str) -> String {
107    let length = text.chars().count();
108    if width <= length {
109        return text.to_string();
110    }
111    let margin = width - length;
112    let left = margin / 2 + (margin & width & 1);
113    format!("{}{text}{}", fill.repeat(left), fill.repeat(margin - left))
114}
115
116/// A barline: music21's `barlineToBraille`. A barline of no braille sign
117/// is the sign of an untranscribable element.
118pub fn barline_to_braille(barline: &Barline) -> Transcription {
119    let name = barline.bar_type().as_str();
120    match lookup::barline(name) {
121        Some(braille) => Transcription {
122            english: vec![format!("Barline {name} {braille}")],
123            braille,
124        },
125        None => Transcription::exception(vec![format!("Barline {name} None")]),
126    }
127}
128
129/// The octave sign of an octave, repeated past the ends: music21's
130/// `pitchToOctave`.
131pub fn pitch_to_octave(octave: IntegerType) -> String {
132    let octave = i64::from(octave);
133    if let Some(sign) = lookup::octave(octave) {
134        sign
135    } else if octave < 1 {
136        lookup::octave(1)
137            .unwrap_or_default()
138            .repeat((2 - octave) as usize)
139    } else {
140        lookup::octave(7)
141            .unwrap_or_default()
142            .repeat((octave - 6) as usize)
143    }
144}
145
146/// Whether braille writes this pitch's accidental: one music21 has not
147/// hidden.
148fn handle_pitch_with_accidental(pitch: &Pitch, braille: &mut String, english: &mut Vec<String>) {
149    let Some(accidental) = pitch.written_accidental() else {
150        return;
151    };
152    if accidental.display_status() == Some(false) {
153        return;
154    }
155    let parenthesis = accidental.display_style() == "parentheses";
156    if parenthesis {
157        let sign = symbol("braille-music-parenthesis");
158        braille.push_str(&sign);
159        english.push(format!("Parenthesis {sign}"));
160    }
161    // An accidental braille has no sign for is left out, as music21 leaves
162    // it out after warning.
163    let Some(sign) = lookup::accidental(accidental.name()) else {
164        return;
165    };
166    braille.push_str(&sign);
167    english.push(format!("Accidental {} {sign}", accidental.name()));
168    if parenthesis {
169        let sign = symbol("braille-music-parenthesis");
170        braille.push_str(&sign);
171        english.push(format!("Parenthesis {sign}"));
172    }
173}
174
175/// A chord symbol written as the chord it spells, for its own length:
176/// music21's `chordToBraille` given a `ChordSymbol`. A symbol as written
177/// takes no time and so gives the exception sign, its English naming the
178/// symbol by its figure. Nothing for a symbol that spells no pitches.
179pub fn chord_symbol_to_braille(
180    chord_symbol: &ChordSymbol,
181    descending: bool,
182    show_octave: bool,
183) -> Option<Transcription> {
184    let mut chord = Chord::new(chord_symbol.pitches().ok()?.as_slice()).ok()?;
185    chord.set_duration(chord_symbol.duration().clone());
186    let mut written = chord_to_braille(&chord, descending, show_octave);
187    if written.braille == symbol("basic_exception") {
188        written.english = vec![format!(
189            "<music21.harmony.ChordSymbol {}> None",
190            music21_figure(chord_symbol)
191        )];
192    }
193    Some(written)
194}
195
196/// The figure music21's `repr` shows for a chord symbol. A symbol whose
197/// figure is its root, its kind's abbreviation and its bass, as one given
198/// by root and kind is, shows music21's `findFigure`: the root and bass
199/// spelled as music21 spells them, a flat as `-`, and each chord-step
200/// modification as ` add 2` before the bass. Any other shows its figure.
201fn music21_figure(chord_symbol: &ChordSymbol) -> String {
202    use super::segment::music21_pitch_name;
203    use crate::chordsymbol::{alter_to_chord_symbol_string, current_abbreviation_for_kind};
204    let root = chord_symbol.root();
205    let bass = chord_symbol
206        .bass()
207        .filter(|bass| bass.name() != root.name());
208    let Some(abbreviation) = chord_symbol.kind().and_then(current_abbreviation_for_kind) else {
209        return chord_symbol.figure().to_string();
210    };
211    let mut given = format!("{}{abbreviation}", root.name());
212    if let Some(bass) = bass {
213        given.push('/');
214        given.push_str(&bass.name());
215    }
216    if chord_symbol.figure() != given {
217        return chord_symbol.figure().to_string();
218    }
219    let mut figure = format!("{}{abbreviation}", music21_pitch_name(root));
220    for modification in chord_symbol.chord_step_modifications() {
221        figure.push_str(&format!(
222            " {} {}{}",
223            modification.modification_type().music21_name(),
224            alter_to_chord_symbol_string(modification.interval().semitones().round() as IntegerType),
225            modification.degree()
226        ));
227    }
228    if let Some(bass) = bass {
229        figure.push('/');
230        figure.push_str(&music21_pitch_name(bass));
231    }
232    figure
233}
234
235/// The distance in steps between two pitches, counting both: music21's
236/// undirected generic interval.
237fn generic_distance(from: &Pitch, to: &Pitch) -> i64 {
238    (i64::from(to.diatonic_note_number()) - i64::from(from.diatonic_note_number())).abs() + 1
239}
240
241/// A chord, its lowest or highest note written in full and each other as
242/// the interval from it, octaves marked where they are needed: music21's
243/// `chordToBraille`. The written note is a note of the chord's length and
244/// nothing else, as music21 writes it.
245pub fn chord_to_braille(chord: &Chord, descending: bool, show_octave: bool) -> Transcription {
246    let mut english = Vec::new();
247    let mut pitches = chord.pitches();
248    pitches.sort_by(|left, right| left.ps().total_cmp(&right.ps()));
249    let direction = if descending {
250        pitches.reverse();
251        "Descending"
252    } else {
253        "Ascending"
254    };
255    let Some(base) = pitches.first().cloned() else {
256        return Transcription::exception(vec![format!("{direction} Chord None")]);
257    };
258    let mut first = Note::from_pitch(base.clone());
259    if let Some(duration) = chord.duration() {
260        first.set_duration(duration.clone());
261    }
262    let written = note_to_braille(&first, show_octave, true, NoteContext::default());
263    if written.braille == symbol("basic_exception") {
264        let names: Vec<String> = chord
265            .pitches()
266            .iter()
267            .map(Pitch::name_with_octave)
268            .collect();
269        return Transcription::exception(vec![format!(
270            "<music21.chord.Chord {}> None",
271            names.join(" ")
272        )]);
273    }
274    let mut braille = written.braille;
275    english.push(format!(
276        "{direction} Chord:\n{}",
277        written.english.join("\n")
278    ));
279    for index in 1..pitches.len() {
280        let current = &pitches[index];
281        handle_pitch_with_accidental(current, &mut braille, &mut english);
282        let mut distance = generic_distance(&base, current);
283        let octave = current.octave().unwrap_or(4);
284        if distance > 8 {
285            distance = (distance - 1) % 7 + 1;
286            let marked = index == 1 || generic_distance(&pitches[index - 1], current) >= 8;
287            if marked {
288                let sign = pitch_to_octave(octave);
289                braille.push_str(&sign);
290                english.push(format!("Octave {octave} {sign}"));
291            }
292        } else if distance == 1 {
293            let sign = pitch_to_octave(octave);
294            braille.push_str(&sign);
295            english.push(format!("Octave {octave} {sign}"));
296        }
297        if distance == 1 {
298            distance = 8;
299        }
300        let sign = lookup::interval(distance).unwrap_or_default();
301        braille.push(sign);
302        english.push(format!("Interval {distance} {sign}"));
303    }
304    Transcription { braille, english }
305}
306
307/// The names music21 gives its clefs in braille's English.
308fn clef_english_name(class_name: &str) -> Option<&'static str> {
309    Some(match class_name {
310        "FrenchViolinClef" => "French Violin",
311        "TrebleClef" => "Treble",
312        "GSopranoClef" => "G-soprano",
313        "SopranoClef" => "Soprano",
314        "MezzoSopranoClef" => "Mezzo-soprano",
315        "AltoClef" => "Alto",
316        "TenorClef" => "Tenor",
317        "CBaritoneClef" => "C-baritone",
318        "FBaritoneClef" => "F-baritone",
319        "BassClef" => "Bass",
320        "SubBassClef" => "Sub-bass",
321        _ => return None,
322    })
323}
324
325/// A clef: music21's `clefToBraille`. A treble or bass clef, and the clefs
326/// music21 derives from them, is written for the other hand when the
327/// keyboard's hands are switched.
328pub fn clef_to_braille(clef: &Clef, keyboard_hand_switched: bool) -> Transcription {
329    let class_name = clef.kind().class_name();
330    let repr = format!("<music21.clef.{class_name}>");
331    if class_name == "NoClef" {
332        return Transcription {
333            braille: String::new(),
334            english: vec![format!("No Clef {repr}")],
335        };
336    }
337    let sign = clef
338        .sign()
339        .zip(clef.line())
340        .and_then(|(sign, line)| lookup::clef(sign, i64::from(line)));
341    let Some(sign) = sign else {
342        return Transcription::exception(vec![format!("{repr} None")]);
343    };
344    let mut braille = format!("{}{sign}", lookup::clef_prefix());
345    let mut english = vec![match clef_english_name(class_name) {
346        Some(name) => format!("{name} Clef {braille}"),
347        None => format!("Unnamed Clef {repr}"),
348    }];
349    let treble_or_bass = matches!(
350        class_name,
351        "TrebleClef"
352            | "Treble8vbClef"
353            | "Treble8vaClef"
354            | "BassClef"
355            | "Bass8vbClef"
356            | "Bass8vaClef"
357    );
358    if treble_or_bass {
359        braille.push(lookup::clef_suffix(keyboard_hand_switched));
360        if keyboard_hand_switched {
361            english.push(" Keyboard hand switched".to_string());
362        }
363    } else {
364        braille.push(lookup::clef_suffix(false));
365    }
366    Transcription { braille, english }
367}
368
369/// A dynamic as a word, after the word sign where asked: music21's
370/// `dynamicToBraille`.
371pub fn dynamic_to_braille(dynamic: &Dynamic, precede_by_word_sign: bool) -> Transcription {
372    let mut english = Vec::new();
373    let mut braille = String::new();
374    if precede_by_word_sign {
375        let word = symbol("word");
376        english.push(format!("Word: {word}"));
377        braille.push_str(&word);
378    }
379    match word_to_braille(dynamic.value(), true) {
380        Ok(word) => {
381            english.push(format!("Dynamic {} {word}", dynamic.value()));
382            braille.push_str(&word);
383            Transcription { braille, english }
384        }
385        Err(_) => {
386            english.push(format!("Dynamic {} None", dynamic.value()));
387            Transcription::exception(english)
388        }
389    }
390}
391
392/// An instrument's best name, word by word: music21's
393/// `instrumentToBraille`.
394///
395/// # Errors
396///
397/// An instrument with no name at all, which music21 cannot split.
398pub fn instrument_to_braille(instrument: &Instrument) -> Result<Transcription> {
399    let name = instrument.best_name().ok_or_else(|| {
400        Error::Instrument("an instrument with no name cannot be written in braille".to_string())
401    })?;
402    let words: Result<Vec<String>> = name
403        .split_whitespace()
404        .map(|word| word_to_braille(word, false))
405        .collect();
406    Ok(match words {
407        Ok(words) => {
408            let braille = words.join(&symbol("space"));
409            Transcription {
410                english: vec![format!("Instrument {name} {braille}")],
411                braille,
412            }
413        }
414        Err(_) => Transcription::exception(vec![format!("Instrument {name} None")]),
415    })
416}
417
418/// music21's `repr` of a key signature.
419fn key_signature_repr(sharps: Option<IntegerType>) -> String {
420    match sharps {
421        Some(0) => "<music21.key.KeySignature of no sharps or flats>".to_string(),
422        Some(1) => "<music21.key.KeySignature of 1 sharp>".to_string(),
423        Some(-1) => "<music21.key.KeySignature of 1 flat>".to_string(),
424        Some(sharps) if sharps > 0 => format!("<music21.key.KeySignature of {sharps} sharps>"),
425        Some(sharps) => format!("<music21.key.KeySignature of {} flats>", -sharps),
426        None => "<music21.key.KeySignature of pitches: []>".to_string(),
427    }
428}
429
430/// A key signature of so many sharps, after the naturals cancelling the
431/// one before it where one is given: music21's `keySigToBraille`.
432pub fn key_sig_to_braille(
433    sharps: Option<IntegerType>,
434    outgoing: Option<Option<IntegerType>>,
435) -> Transcription {
436    let mut english = Vec::new();
437    let Some(incoming) = sharps else {
438        return Transcription::exception(vec![format!(
439            "Key Signature {} cannot be transcribed",
440            key_signature_repr(sharps)
441        )]);
442    };
443    let Some(signature) = lookup::key_signature(i64::from(incoming)) else {
444        return Transcription::exception(vec![format!(
445            "Key Signature {} cannot be transcribed",
446            key_signature_repr(sharps)
447        )]);
448    };
449    if incoming > 0 {
450        english.push(format!("Key Signature {incoming} sharp(s) {signature}"));
451    } else {
452        english.push(format!(
453            "Key Signature {} flat(s) {signature}",
454            incoming.abs()
455        ));
456    }
457    let Some(outgoing) = outgoing else {
458        return Transcription {
459            braille: signature,
460            english,
461        };
462    };
463    let Some(out) = outgoing else {
464        english.push(format!("{} naturals=None", key_signature_repr(outgoing)));
465        return Transcription {
466            braille: signature,
467            english,
468        };
469    };
470    let (absolute_out, absolute_in) = (i64::from(out).abs(), i64::from(incoming).abs());
471    let mut braille = String::new();
472    let cancel = if incoming == 0 || out == 0 || out.signum() != incoming.signum() {
473        Some(absolute_out)
474    } else if absolute_out >= absolute_in {
475        Some(i64::from(out - incoming).abs())
476    } else {
477        None
478    };
479    if let Some(count) = cancel {
480        let Some(naturals) = lookup::naturals(count) else {
481            english.push(format!("{} naturals=None", key_signature_repr(outgoing)));
482            return Transcription {
483                braille: signature,
484                english,
485            };
486        };
487        braille.push_str(&naturals);
488        english.insert(0, format!("Key Signature {out} naturals {naturals}"));
489    }
490    braille.push_str(&signature);
491    Transcription { braille, english }
492}
493
494/// A metronome mark: its note value, the metronome sign and its number:
495/// music21's `metronomeMarkToBraille`. A mark with no number is nothing,
496/// and says nothing in English.
497pub fn metronome_mark_to_braille(mark: &MetronomeMark) -> Option<Transcription> {
498    let number = mark.number()?;
499    let mut english = Vec::new();
500    let mut note = Note::from_pitch(Pitch::from_name("C4").expect("C4 is a pitch"));
501    let length = mark.referent().quarter_length();
502    if length != 0.0
503        && let Ok(duration) = Duration::new(length)
504    {
505        note.set_duration(duration);
506    }
507    let written = note_to_braille(&note, false, true, NoteContext::default());
508    let mut braille = written.braille;
509    english.push(format!("Metronome Note {}", written.english.join(" ")));
510    let metronome = symbol("metronome");
511    braille.push_str(&metronome);
512    english.push(format!("Metronome symbol {metronome}"));
513    let text = crate::statistics::python_repr(number);
514    let text = text.strip_suffix(".0").unwrap_or(&text);
515    match number_to_braille(text, true, false) {
516        Ok(digits) => {
517            braille.push_str(&digits);
518            english.push(format!("Metronome number {text} {digits}"));
519            Some(Transcription { braille, english })
520        }
521        Err(_) => Some(Transcription::exception(vec![format!(
522            "{} None",
523            metronome_mark_repr(mark)
524        )])),
525    }
526}
527
528/// music21's `repr` of a metronome mark, as far as its number shows.
529fn metronome_mark_repr(mark: &MetronomeMark) -> String {
530    let number = mark
531        .number()
532        .map(crate::statistics::python_repr)
533        .unwrap_or_default();
534    format!("<music21.tempo.MetronomeMark {number}>")
535}
536
537/// The articulations braille writes before a note, bowings first, then
538/// staccatos, then the rest by name: music21's
539/// `yieldBrailleArticulations`.
540pub fn braille_articulations(note: &Note) -> Vec<(String, String)> {
541    let mut articulations: Vec<_> = note.articulations().iter().collect();
542    articulations.sort_by_key(|articulation| {
543        (
544            !articulation.is_a("Bowing"),
545            !articulation.is_a("Staccato"),
546            articulation.name(),
547        )
548    });
549    articulations
550        .into_iter()
551        .filter_map(|articulation| {
552            let name = articulation.name();
553            lookup::bowing(&name)
554                .or_else(|| lookup::before_note_expression(&name))
555                .map(|sign| (name, sign))
556        })
557        .collect()
558}
559
560/// A note: its slurs and tuplet opening, articulations, accidental,
561/// octave where shown, name and value, dots, fingering, fermata, slur and
562/// tie: music21's `noteToBraille`.
563pub fn note_to_braille(
564    note: &Note,
565    show_octave: bool,
566    upper_first_in_fingering: bool,
567    context: NoteContext,
568) -> Transcription {
569    let mut context = context;
570    let mut english: Vec<String> = Vec::new();
571    let mut braille = String::new();
572    let push = |braille: &mut String, english: &mut Vec<String>, sign: String, text: String| {
573        english.push(text);
574        braille.push_str(&sign);
575    };
576    if context.begin_long_bracket_slur {
577        let sign = symbol("opening_bracket_slur");
578        push(
579            &mut braille,
580            &mut english,
581            sign.clone(),
582            format!("Opening bracket slur {sign}"),
583        );
584    } else if context.begin_long_double_slur {
585        let sign = symbol("opening_double_slur");
586        push(
587            &mut braille,
588            &mut english,
589            sign.clone(),
590            format!("Opening double slur {sign}"),
591        );
592    }
593    if context.end_long_bracket_slur && context.begin_long_bracket_slur {
594        let sign = symbol("closing_bracket_slur");
595        push(
596            &mut braille,
597            &mut english,
598            sign.clone(),
599            format!("Closing bracket slur {sign}"),
600        );
601    }
602    let duration = note.duration().cloned().unwrap_or_default();
603    let tuplets = duration.tuplets();
604    if let Some(tuplet) = tuplets.first() {
605        if context.beam_start {
606            let hidden = tuplet.actual_show().is_none();
607            let last = if tuplet.full_name() == "Triplet" && !hidden {
608                symbol("triplet")
609            } else {
610                if hidden {
611                    let sign = symbol("transcriber-added_sign");
612                    push(
613                        &mut braille,
614                        &mut english,
615                        sign.clone(),
616                        format!("transcriber-added {sign}"),
617                    );
618                }
619                let number = number_to_braille(&tuplet.actual().to_string(), false, true)
620                    .unwrap_or_default();
621                format!("{}{number}{}", symbol("tuplet_prefix"), symbol("dot"))
622            };
623            braille.push_str(&last);
624            english.push(format!("{} {last}", tuplet.full_name()));
625        } else if context.beam_continue {
626            context.beam_continue = false;
627        }
628    }
629    for (name, sign) in braille_articulations(note) {
630        push(
631            &mut braille,
632            &mut english,
633            sign.clone(),
634            format!("Articulation {name} {sign}"),
635        );
636    }
637    handle_pitch_with_accidental(note.pitch(), &mut braille, &mut english);
638    let octave = note.pitch().octave();
639    if show_octave {
640        let sign = pitch_to_octave(octave.unwrap_or(4));
641        let shown = octave.map_or_else(|| "None".to_string(), |octave| octave.to_string());
642        push(
643            &mut braille,
644            &mut english,
645            sign.clone(),
646            format!("Octave {shown} {sign}"),
647        );
648    }
649    let step = note.pitch().step().as_char();
650    if duration.is_grace() {
651        let sign = lookup::pitch_name_to_note(step, "eighth").unwrap_or_default();
652        push(
653            &mut braille,
654            &mut english,
655            sign.clone(),
656            format!("{step} eighth Gracenote--not supported {sign}"),
657        );
658    } else {
659        let value = duration_type(&duration);
660        let (sign, text) = if context.beam_continue {
661            let sign = lookup::pitch_name_to_note(step, "eighth").unwrap_or_default();
662            (Some(sign.clone()), format!("{step} beam {sign}"))
663        } else {
664            match lookup::pitch_name_to_note(step, &value) {
665                Some(sign) => (Some(sign.clone()), format!("{step} {value} {sign}")),
666                None => (None, String::new()),
667            }
668        };
669        let Some(sign) = sign else {
670            english.push(format!(
671                "Duration <music21.duration.Duration {}> None",
672                quarter_length_repr(duration.quarter_length())
673            ));
674            return Transcription::exception(english);
675        };
676        push(&mut braille, &mut english, sign, text);
677        for _ in 0..duration_dots(&duration) {
678            let dot = symbol("dot");
679            push(
680                &mut braille,
681                &mut english,
682                dot.clone(),
683                format!("Dot {dot}"),
684            );
685        }
686    }
687    for articulation in note.articulations() {
688        if !articulation.is_a("Fingering") {
689            continue;
690        }
691        let finger = match articulation.finger() {
692            Some(crate::articulations::Finger::Number(number)) => number.to_string(),
693            Some(crate::articulations::Finger::Written(text)) => text.clone(),
694            None => continue,
695        };
696        if let Ok(sign) = transcribe_note_fingering(&finger, upper_first_in_fingering) {
697            braille.push_str(&sign);
698        }
699    }
700    handle_expressions(note.expressions(), &mut braille, &mut english);
701    if context.short_slur {
702        let sign = symbol("opening_single_slur");
703        push(
704            &mut braille,
705            &mut english,
706            sign.clone(),
707            format!("Opening single slur {sign}"),
708        );
709    }
710    if !(context.end_long_bracket_slur && context.begin_long_bracket_slur) {
711        if context.end_long_double_slur {
712            let sign = symbol("closing_double_slur");
713            push(
714                &mut braille,
715                &mut english,
716                sign.clone(),
717                format!("Closing bracket slur {sign}"),
718            );
719        } else if context.end_long_bracket_slur {
720            let sign = symbol("closing_bracket_slur");
721            push(
722                &mut braille,
723                &mut english,
724                sign.clone(),
725                format!("Closing bracket slur {sign}"),
726            );
727        }
728    }
729    if note
730        .tie()
731        .is_some_and(|tie| tie.tie_type() != TieType::Stop)
732    {
733        let sign = symbol("tie");
734        push(
735            &mut braille,
736            &mut english,
737            sign.clone(),
738            format!("Tie {sign}"),
739        );
740    }
741    Transcription { braille, english }
742}
743
744/// The fermatas of a note or rest: music21's `handleExpressions`.
745fn handle_expressions(expressions: &[Expression], braille: &mut String, english: &mut Vec<String>) {
746    for expression in expressions {
747        if let Expression::Fermata(fermata) = expression {
748            let shape = fermata.shape().unwrap_or("normal");
749            if let Some(sign) = lookup::fermata(shape) {
750                english.push(format!("Note-fermata: Shape {shape}: {sign}"));
751                braille.push_str(&sign);
752            }
753        }
754    }
755}
756
757/// A rest, a whole rest where it stands for its measure: music21's
758/// `restToBraille`.
759pub fn rest_to_braille(rest: &Rest) -> Transcription {
760    let mut english = Vec::new();
761    let (value, dots) = if rest.full_measure() == crate::rest::FullMeasure::Yes {
762        ("whole".to_string(), 0)
763    } else {
764        (
765            duration_type(rest.duration()),
766            duration_dots(rest.duration()),
767        )
768    };
769    let Some(sign) = lookup::rest(&value) else {
770        return Transcription::exception(vec![format!(
771            "Rest {} None",
772            duration_type(rest.duration())
773        )]);
774    };
775    let mut braille = sign.clone();
776    english.push(format!("Rest {value} {sign}"));
777    for _ in 0..dots {
778        let dot = symbol("dot");
779        braille.push_str(&dot);
780        english.push(format!("Dot {dot}"));
781    }
782    handle_expressions(rest.expressions(), &mut braille, &mut english);
783    Transcription { braille, english }
784}
785
786/// Tempo words, phrase by comma-separated phrase, a line broken before a
787/// word that would run past the line less six, ending with a full stop:
788/// music21's `tempoTextToBraille`.
789pub fn tempo_text_to_braille(text: &str, max_line_length: usize) -> Transcription {
790    let space = symbol("space");
791    let mut phrases = Vec::new();
792    for phrase in text.split(',') {
793        let mut pieces: Vec<String> = Vec::new();
794        for word in phrase.split_whitespace() {
795            let Ok(braille) = word_to_braille(word, false) else {
796                return Transcription::exception(vec![format!("Tempo Text {text} None")]);
797            };
798            let mut width = 0;
799            for piece in &pieces {
800                if piece == "\n" {
801                    width = 0;
802                } else {
803                    width += piece.chars().count();
804                }
805            }
806            if width + braille.chars().count() + 1 > max_line_length.saturating_sub(6) {
807                pieces.push("\n".to_string());
808            }
809            pieces.push(braille);
810            pieces.push(space.clone());
811        }
812        pieces.pop();
813        phrases.push(pieces.concat());
814    }
815    let joiner = format!("{}\n", lookup::alphabet(',').unwrap_or_default());
816    let braille = phrases.join(&joiner) + &lookup::alphabet('.').unwrap_or_default();
817    Transcription {
818        english: vec![format!("Tempo Text {text} {braille}")],
819        braille,
820    }
821}
822
823/// A tempo text: [`tempo_text_to_braille`] of its text.
824pub fn tempo_to_braille(tempo: &TempoText, max_line_length: usize) -> Transcription {
825    tempo_text_to_braille(tempo.text(), max_line_length)
826}
827
828/// Words written as text, after the word sign where asked and before
829/// another where there are several: music21's `textExpressionToBraille`.
830/// The hairpin words have signs of their own.
831pub fn text_expression_to_braille(
832    expression: &TextExpression,
833    precede_by_word_sign: bool,
834) -> Transcription {
835    let words = expression.content();
836    if let Some(sign) = lookup::text_expression(words) {
837        return Transcription {
838            english: vec![format!("Text Expression {words} {sign}")],
839            braille: sign,
840        };
841    }
842    let all: Vec<&str> = words.split_whitespace().collect();
843    let written: Result<Vec<String>> = all.iter().map(|word| word_to_braille(word, true)).collect();
844    let Ok(written) = written else {
845        return Transcription::exception(vec![format!("Text Expression {words} None")]);
846    };
847    let mut braille = written.join(&symbol("space"));
848    let mut english = vec![format!("Text Expression {words} {braille}")];
849    let word = symbol("word");
850    if precede_by_word_sign {
851        braille = format!("{word}{braille}");
852        english.insert(0, format!("Word {word}"));
853    }
854    if all.len() > 1 {
855        braille.push_str(&word);
856        english.push(format!("Word {word}"));
857    }
858    Transcription { braille, english }
859}
860
861/// A time signature, its numerator high and denominator low, or its common
862/// or cut sign: music21's `timeSigToBraille`.
863pub fn time_sig_to_braille(meter: &TimeSignature) -> Transcription {
864    if let Some(sign @ ("common" | "cut")) = meter.symbol() {
865        let braille = symbol(sign);
866        return Transcription {
867            english: vec![format!("Time Signature {sign} {braille}")],
868            braille,
869        };
870    }
871    let (numerator, denominator) = (meter.numerator(), meter.denominator());
872    let written = number_to_braille(&numerator.to_string(), true, false).and_then(|high| {
873        number_to_braille(&denominator.to_string(), false, true).map(|low| high + &low)
874    });
875    match written {
876        Ok(braille) => Transcription {
877            english: vec![format!(
878                "Time Signature {numerator}/{denominator} {braille}"
879            )],
880            braille,
881        },
882        Err(_) => Transcription::exception(vec![format!(
883            "<music21.meter.TimeSignature {numerator}/{denominator}> None"
884        )]),
885    }
886}
887
888/// Whether a note shows its octave after the one before: always after
889/// none, after a sixth or more, and after a fourth or fifth into another
890/// octave: music21's `showOctaveWithNote`.
891pub fn show_octave_with_note(previous: Option<&Pitch>, current: &Pitch) -> bool {
892    let Some(previous) = previous else {
893        return true;
894    };
895    let distance = generic_distance(previous, current);
896    distance >= 6 || (matches!(distance, 4 | 5) && previous.octave() != current.octave())
897}
898
899/// A key and time signature in braille, the key cancelling the one before
900/// where given: music21's `transcribeSignatures`. Nothing for no time
901/// signature and a key of no sharps with nothing to cancel.
902pub fn transcribe_signatures(
903    sharps: Option<Option<IntegerType>>,
904    meter: Option<&TimeSignature>,
905    outgoing: Option<Option<IntegerType>>,
906) -> Transcription {
907    let empty_key = match sharps {
908        None => true,
909        Some(sharps) => sharps == Some(0) && outgoing.is_none(),
910    };
911    if meter.is_none() && empty_key {
912        return Transcription::default();
913    }
914    let mut out = Transcription::default();
915    if let Some(sharps) = sharps {
916        let key = key_sig_to_braille(sharps, outgoing);
917        out.braille.push_str(&key.braille);
918        out.english.extend(key.english);
919    }
920    if let Some(meter) = meter {
921        let time = time_sig_to_braille(meter);
922        out.braille.push_str(&time.braille);
923        out.english.extend(time.english);
924    }
925    out
926}
927
928/// A heading: the tempo words above, then the metronome mark, key and time
929/// signature, each line centred: music21's `transcribeHeading`.
930///
931/// # Errors
932///
933/// Nothing to head with.
934pub fn transcribe_heading(
935    sharps: Option<Option<IntegerType>>,
936    meter: Option<&TimeSignature>,
937    tempo: Option<&TempoText>,
938    metronome: Option<&MetronomeMark>,
939    max_line_length: usize,
940) -> Result<String> {
941    if sharps.is_none() && meter.is_none() && tempo.is_none() && metronome.is_none() {
942        return Err(Error::Notation("No heading can be made.".to_string()));
943    }
944    let space = symbol("space");
945    let tempo_text = tempo.map(|tempo| tempo_to_braille(tempo, max_line_length).braille);
946    let centred_lines = |lines: &[String]| -> String {
947        lines
948            .iter()
949            .map(|line| center(line, max_line_length, &space))
950            .collect::<Vec<_>>()
951            .join("\n")
952    };
953    if sharps.is_none() && meter.is_none() && metronome.is_none() {
954        let tempo_text = tempo_text.unwrap_or_default();
955        let lines: Vec<String> = python_lines(&tempo_text);
956        return Ok(
957            if tempo_text.chars().count() <= max_line_length.saturating_sub(6) {
958                center(&lines.concat(), max_line_length, &space)
959            } else {
960                centred_lines(&lines)
961            },
962        );
963    }
964    let mut other = String::new();
965    if let Some(metronome) = metronome {
966        other.push_str(
967            &metronome_mark_to_braille(metronome)
968                .map(|mark| mark.braille)
969                .unwrap_or_default(),
970        );
971    }
972    let signatures = transcribe_signatures(sharps, meter, None);
973    if metronome.is_some() {
974        other.push_str(&space);
975    }
976    other.push_str(&signatures.braille);
977    let Some(tempo_text) = tempo_text else {
978        return Ok(center(&other, max_line_length, &space));
979    };
980    let mut lines = python_lines(&tempo_text);
981    lines.push(other.clone());
982    while lines.len() > 1 && lines[0].is_empty() {
983        lines.remove(0);
984    }
985    while lines.len() > 1 && lines.last().is_some_and(String::is_empty) {
986        lines.pop();
987    }
988    Ok(
989        if tempo_text.chars().count() + other.chars().count() < max_line_length.saturating_sub(6) {
990            center(&lines.join(&space), max_line_length, &space)
991        } else {
992            centred_lines(&lines)
993        },
994    )
995}
996
997/// The lines of a text as Python's `splitlines` gives them.
998pub(crate) fn python_lines(text: &str) -> Vec<String> {
999    let mut lines = Vec::new();
1000    let mut current = String::new();
1001    let mut characters = text.chars().peekable();
1002    while let Some(character) = characters.next() {
1003        match character {
1004            '\r' => {
1005                if characters.peek() == Some(&'\n') {
1006                    characters.next();
1007                }
1008                lines.push(std::mem::take(&mut current));
1009            }
1010            '\n' | '\u{b}' | '\u{c}' | '\u{1c}' | '\u{1d}' | '\u{1e}' | '\u{85}' | '\u{2028}'
1011            | '\u{2029}' => lines.push(std::mem::take(&mut current)),
1012            other => current.push(other),
1013        }
1014    }
1015    if !current.is_empty() {
1016        lines.push(current);
1017    }
1018    lines
1019}
1020
1021/// A note's fingering: one finger, a change of finger as `1-2`, or a choice
1022/// of fingerings as `1,2` (either may be missing) or `1|2`: music21's
1023/// `transcribeNoteFingering`.
1024///
1025/// # Errors
1026///
1027/// A finger braille has no sign for, or more than two choices.
1028pub fn transcribe_note_fingering(
1029    fingering: &str,
1030    upper_first_in_fingering: bool,
1031) -> Result<String> {
1032    let refuse = || Error::Notation(format!("Cannot translate note fingering: {fingering}"));
1033    if fingering.chars().count() == 1 {
1034        return lookup::finger_mark(fingering)
1035            .map(String::from)
1036            .ok_or_else(refuse);
1037    }
1038    let mut choice: Vec<&str> = fingering.split(',').collect();
1039    let allow_absence = match choice.len() {
1040        2 => true,
1041        1 => {
1042            choice = fingering.split('|').collect();
1043            false
1044        }
1045        _ => return Err(refuse()),
1046    };
1047    if !upper_first_in_fingering {
1048        choice.reverse();
1049    }
1050    let mut braille = String::new();
1051    for (index, one) in choice.iter().enumerate() {
1052        let change: Vec<&str> = one.split('-').collect();
1053        match change.as_slice() {
1054            [from, to] => {
1055                braille.push(lookup::finger_mark(from).ok_or_else(refuse)?);
1056                braille.push_str(&symbol("finger_change"));
1057                braille.push(lookup::finger_mark(to).ok_or_else(refuse)?);
1058            }
1059            [mark] => match lookup::finger_mark(mark) {
1060                Some(sign) => braille.push(sign),
1061                None if allow_absence => braille.push_str(&symbol(if index == 0 {
1062                    "first_set_missing_fingermark"
1063                } else {
1064                    "second_set_missing_fingermark"
1065                })),
1066                None => return Err(refuse()),
1067            },
1068            _ => {}
1069        }
1070    }
1071    Ok(braille)
1072}
1073
1074/// Braille written as braille ASCII: music21's `brailleUnicodeToBrailleAscii`.
1075///
1076/// # Errors
1077///
1078/// A character that is not a braille cell.
1079pub fn braille_unicode_to_braille_ascii(braille: &str) -> Result<String> {
1080    python_lines(braille)
1081        .iter()
1082        .map(|line| {
1083            line.chars()
1084                .map(|cell| {
1085                    lookup::ascii_char(cell)
1086                        .ok_or_else(|| Error::Notation(format!("{cell:?} is not a braille cell")))
1087                })
1088                .collect::<Result<String>>()
1089        })
1090        .collect::<Result<Vec<_>>>()
1091        .map(|lines| lines.join("\n"))
1092}
1093
1094/// Braille ASCII written as braille: music21's `brailleAsciiToBrailleUnicode`.
1095///
1096/// # Errors
1097///
1098/// A character braille ASCII has no cell for.
1099pub fn braille_ascii_to_braille_unicode(ascii: &str) -> Result<String> {
1100    python_lines(ascii)
1101        .iter()
1102        .map(|line| {
1103            line.chars()
1104                .map(|character| {
1105                    let upper = character.to_ascii_uppercase();
1106                    (0..64u32)
1107                        .map(|bits| char::from_u32(0x2800 + bits).expect("a braille cell"))
1108                        .find(|cell| lookup::ascii_char(*cell) == Some(upper))
1109                        .ok_or_else(|| Error::Notation(format!("{upper:?} is not braille ASCII")))
1110                })
1111                .collect::<Result<String>>()
1112        })
1113        .collect::<Result<Vec<_>>>()
1114        .map(|lines| lines.join("\n"))
1115}
1116
1117/// Braille drawn as dots, each cell two columns of three rows, filled and
1118/// empty: music21's `brailleUnicodeToSymbols`.
1119///
1120/// # Errors
1121///
1122/// A character that is not a braille cell.
1123pub fn braille_unicode_to_symbols(braille: &str, filled: &str, empty: &str) -> Result<String> {
1124    let draw = |dots: &str| -> String {
1125        dots.chars()
1126            .map(|dot| if dot == '1' { filled } else { empty })
1127            .collect()
1128    };
1129    let mut lines: Vec<String> = Vec::new();
1130    for line in python_lines(braille) {
1131        let mut rows = [Vec::new(), Vec::new(), Vec::new()];
1132        for cell in line.chars() {
1133            let dots = lookup::binary_dots(cell)
1134                .ok_or_else(|| Error::Notation(format!("{cell:?} is not a braille cell")))?;
1135            for (row, dots) in rows.iter_mut().zip(dots) {
1136                row.push(draw(dots));
1137            }
1138        }
1139        for row in rows {
1140            lines.push(row.join("  "));
1141        }
1142        lines.push(String::new());
1143    }
1144    lines.pop();
1145    Ok(lines.join("\n"))
1146}
1147
1148/// A dot for each row of a cell whose left dot is raised: music21's
1149/// `yieldDots`, which looks at the left column only.
1150pub fn yield_dots(cell: char) -> Vec<String> {
1151    lookup::binary_dots(cell)
1152        .map(|rows| {
1153            rows.iter()
1154                .filter(|dots| dots.starts_with('1'))
1155                .map(|_| symbol("dot"))
1156                .collect()
1157        })
1158        .unwrap_or_default()
1159}
1160
1161/// A character as music21's `stripAccents` leaves it: a Latin letter
1162/// without its diacritics, a spacing character as a space, a curly quote
1163/// straight and a superscript digit as the digit, as its compatibility
1164/// decomposition does.
1165fn strip_accents(letter: &str) -> String {
1166    letter
1167        .chars()
1168        .map(|character| match character {
1169            '\u{a0}' | '\u{2000}'..='\u{200a}' | '\u{202f}' | '\u{205f}' | '\u{3000}' => ' ',
1170            '\u{2018}' | '\u{2019}' => '\'',
1171            '\u{201c}' | '\u{201d}' => '"',
1172            '\u{b9}' => '1',
1173            '\u{b2}' => '2',
1174            '\u{b3}' => '3',
1175            'à' | 'á' | 'â' | 'ã' | 'ä' | 'å' | 'ā' | 'ă' | 'ą' => 'a',
1176            'ç' | 'ć' | 'ĉ' | 'ċ' | 'č' => 'c',
1177            'ď' => 'd',
1178            'è' | 'é' | 'ê' | 'ë' | 'ē' | 'ĕ' | 'ė' | 'ę' | 'ě' => 'e',
1179            'ĝ' | 'ğ' | 'ġ' | 'ģ' => 'g',
1180            'ĥ' => 'h',
1181            'ì' | 'í' | 'î' | 'ï' | 'ĩ' | 'ī' | 'ĭ' | 'į' => 'i',
1182            'ĵ' => 'j',
1183            'ķ' => 'k',
1184            'ĺ' | 'ļ' | 'ľ' => 'l',
1185            'ñ' | 'ń' | 'ņ' | 'ň' => 'n',
1186            'ò' | 'ó' | 'ô' | 'õ' | 'ö' | 'ō' | 'ŏ' | 'ő' => 'o',
1187            'ŕ' | 'ŗ' | 'ř' => 'r',
1188            'ś' | 'ŝ' | 'ş' | 'š' => 's',
1189            'ţ' | 'ť' => 't',
1190            'ù' | 'ú' | 'û' | 'ü' | 'ũ' | 'ū' | 'ŭ' | 'ů' | 'ű' | 'ų' => 'u',
1191            'ŵ' => 'w',
1192            'ý' | 'ÿ' | 'ŷ' => 'y',
1193            'ź' | 'ż' | 'ž' => 'z',
1194            '\u{300}'..='\u{36f}' => '\0',
1195            other => other,
1196        })
1197        .filter(|character| *character != '\0')
1198        .collect()
1199}
1200
1201/// A word in braille: music21's `wordToBraille`. As text in the music a
1202/// word is spelled letter by letter; otherwise capitals and digits take
1203/// their signs, and a letter after a digit the letter sign. A letter with
1204/// diacritics is written as the accent sign and the letter.
1205///
1206/// # Errors
1207///
1208/// A character braille has no sign for.
1209pub fn word_to_braille(word: &str, is_text_expression: bool) -> Result<String> {
1210    let refuse = |letter: char| {
1211        Error::Notation(if is_text_expression {
1212            format!(
1213                "Character '{letter}' in Text Expression '{word}' cannot be transcribed to braille."
1214            )
1215        } else {
1216            format!("Character '{letter}' in word '{word}' cannot be transcribed to braille.")
1217        })
1218    };
1219    let mut braille = String::new();
1220    let add_letter = |braille: &mut String, letter: &str| -> bool {
1221        let mut characters = letter.chars();
1222        if let (Some(single), None) = (characters.next(), characters.next())
1223            && let Some(sign) = lookup::alphabet(single)
1224        {
1225            braille.push_str(&sign);
1226            return true;
1227        }
1228        let stripped = strip_accents(letter);
1229        if stripped == letter {
1230            return false;
1231        }
1232        let mut characters = stripped.chars();
1233        match (characters.next(), characters.next()) {
1234            (Some(single), None) => match lookup::alphabet(single) {
1235                Some(sign) => {
1236                    braille.push_str(&lookup::alphabet('^').unwrap_or_default());
1237                    braille.push_str(&sign);
1238                    true
1239                }
1240                None => false,
1241            },
1242            _ => false,
1243        }
1244    };
1245    if is_text_expression {
1246        for letter in word.chars() {
1247            let added = if letter.is_uppercase() {
1248                add_letter(&mut braille, &letter.to_lowercase().to_string())
1249            } else if letter == '.' {
1250                braille.push_str(&symbol("dot"));
1251                true
1252            } else if letter == 'ß' {
1253                add_letter(&mut braille, "s") && add_letter(&mut braille, "s")
1254            } else {
1255                add_letter(&mut braille, &letter.to_string())
1256            };
1257            if !added {
1258                return Err(refuse(letter));
1259            }
1260        }
1261        return Ok(braille);
1262    }
1263    let mut last_was_number = false;
1264    for letter in word.chars() {
1265        let digit = letter.is_ascii_digit();
1266        if digit && !last_was_number {
1267            braille.push_str(&symbol("number"));
1268            last_was_number = true;
1269        } else if letter.is_alphabetic() && last_was_number {
1270            braille.push_str(&symbol("letter_sign"));
1271            last_was_number = false;
1272        }
1273        let added = if letter.is_uppercase() {
1274            braille.push_str(&symbol("uppercase"));
1275            add_letter(&mut braille, &letter.to_lowercase().to_string())
1276        } else if digit {
1277            braille.push(
1278                lookup::number_upper(letter.to_digit(10).unwrap_or_default()).unwrap_or_default(),
1279            );
1280            true
1281        } else if letter == 'ß' {
1282            add_letter(&mut braille, "s") && add_letter(&mut braille, "s")
1283        } else {
1284            add_letter(&mut braille, &letter.to_string())
1285        };
1286        if !added {
1287            return Err(refuse(letter));
1288        }
1289    }
1290    Ok(braille)
1291}
1292
1293/// A number in braille, its digits high or low, after the number sign where
1294/// asked: music21's `numberToBraille`.
1295///
1296/// # Errors
1297///
1298/// A character that is not a digit.
1299pub fn number_to_braille(number: &str, with_number_sign: bool, lower: bool) -> Result<String> {
1300    let mut braille = String::new();
1301    if with_number_sign {
1302        braille.push_str(&symbol("number"));
1303    }
1304    for digit in number.chars() {
1305        let sign = digit
1306            .to_digit(10)
1307            .filter(|_| digit.is_ascii_digit())
1308            .and_then(|digit| {
1309                if lower {
1310                    lookup::number_lower(digit)
1311                } else {
1312                    lookup::number_upper(digit)
1313                }
1314            })
1315            .ok_or_else(|| {
1316                Error::Notation(format!(
1317                    "Digit '{digit}' in number '{number}' cannot be transcribed to braille."
1318                ))
1319            })?;
1320        braille.push(sign);
1321    }
1322    Ok(braille)
1323}
1324
1325#[cfg(test)]
1326mod tests {
1327    use super::*;
1328
1329    #[test]
1330    fn words_and_numbers_are_written_as_music21_writes_them() -> Result<()> {
1331        // music21's doctests of wordToBraille and numberToBraille.
1332        assert_eq!(word_to_braille("Andante", false)?, "⠠⠁⠝⠙⠁⠝⠞⠑");
1333        assert_eq!(word_to_braille("Café", false)?, "⠠⠉⠁⠋⠈⠑");
1334        assert_eq!(number_to_braille("12", true, false)?, "⠼⠁⠃");
1335        assert_eq!(number_to_braille("7", false, true)?, "⠶");
1336        assert_eq!(transcribe_note_fingering("1-3", true)?, "⠁⠉⠇");
1337        assert_eq!(transcribe_note_fingering("2,x", false)?, "⠠⠃");
1338        assert_eq!(center("ab", 5, "."), "..ab.");
1339        assert_eq!(center("abc", 6, "."), ".abc..");
1340        Ok(())
1341    }
1342
1343    #[test]
1344    fn notes_and_rests_are_written_with_their_values() -> Result<()> {
1345        let mut note = Note::from_name("C4")?;
1346        note.set_duration(Duration::new(1.5)?);
1347        let written = note_to_braille(&note, true, true, NoteContext::default());
1348        assert_eq!(written.braille, "⠐⠹⠄");
1349        assert_eq!(written.english, ["Octave 4 ⠐", "C quarter ⠹", "Dot ⠄"]);
1350        let rest = Rest::new(Duration::new(2.0)?);
1351        assert_eq!(rest_to_braille(&rest).braille, "⠥");
1352        Ok(())
1353    }
1354
1355    /// A chord is written for its own length; a chord symbol as written
1356    /// takes none and gives the exception sign, named by its figure.
1357    #[test]
1358    fn a_chord_is_written_for_its_own_length() -> Result<()> {
1359        let eighth = Chord::new("C5 E5")?.with_duration(Duration::new(0.5)?);
1360        assert_eq!(chord_to_braille(&eighth, true, true).braille, "⠨⠋⠬");
1361        let written = chord_symbol_to_braille(&ChordSymbol::parse("C")?, true, true)
1362            .expect("C spells pitches");
1363        assert_eq!(written.braille, symbol("basic_exception"));
1364        assert_eq!(written.english, ["<music21.harmony.ChordSymbol C> None"]);
1365        let mut flat = ChordSymbol::from_kind(Pitch::from_name("B-")?, "major-sixth", None)?;
1366        flat.add_chord_step_modification(crate::chordsymbol::ChordStepModification::new(
1367            crate::chordsymbol::ChordStepModificationType::Add,
1368            9,
1369            -1,
1370        )?);
1371        let written = chord_symbol_to_braille(&flat, true, true).expect("B-6 spells pitches");
1372        assert_eq!(
1373            written.english,
1374            ["<music21.harmony.ChordSymbol B-6 add b9> None"]
1375        );
1376        Ok(())
1377    }
1378}