Skip to main content

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