1use 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct Transcription {
30 pub braille: String,
32 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
49pub struct NoteContext {
50 pub begin_long_bracket_slur: bool,
52 pub end_long_bracket_slur: bool,
54 pub begin_long_double_slur: bool,
56 pub end_long_double_slur: bool,
58 pub short_slur: bool,
60 pub beam_start: bool,
62 pub beam_continue: bool,
64}
65
66pub(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
80pub(crate) fn duration_dots(duration: &Duration) -> u32 {
82 duration
83 .written_values()
84 .first()
85 .map_or(0, |value| value.dots())
86}
87
88pub(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
103pub(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
116pub 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
129pub 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
146fn 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 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
175pub 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
196fn 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
235fn generic_distance(from: &Pitch, to: &Pitch) -> i64 {
238 (i64::from(to.diatonic_note_number()) - i64::from(from.diatonic_note_number())).abs() + 1
239}
240
241pub 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
307fn 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
325pub 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
369pub 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
392pub 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
418fn 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
430pub 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
494pub 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(¬e, 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
528fn 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
537pub 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
560pub 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
744fn 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
757pub 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
786pub 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
823pub fn tempo_to_braille(tempo: &TempoText, max_line_length: usize) -> Transcription {
825 tempo_text_to_braille(tempo.text(), max_line_length)
826}
827
828pub 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
861pub 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
888pub 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
899pub 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
928pub 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
997pub(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
1021pub 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
1074pub 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
1094pub 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
1117pub 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
1148pub 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
1161fn 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
1201pub 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
1293pub 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 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(¬e, 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 #[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}