1use 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub struct Transcription {
29 pub braille: String,
31 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48pub struct NoteContext {
49 pub begin_long_bracket_slur: bool,
51 pub end_long_bracket_slur: bool,
53 pub begin_long_double_slur: bool,
55 pub end_long_double_slur: bool,
57 pub short_slur: bool,
59 pub beam_start: bool,
61 pub beam_continue: bool,
63}
64
65pub(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
79pub(crate) fn duration_dots(duration: &Duration) -> u32 {
81 duration
82 .written_values()
83 .first()
84 .map_or(0, |value| value.dots())
85}
86
87pub(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
102pub(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
115pub 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
128pub 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
145fn 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 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
174fn generic_distance(from: &Pitch, to: &Pitch) -> i64 {
177 (i64::from(to.diatonic_note_number()) - i64::from(from.diatonic_note_number())).abs() + 1
178}
179
180pub 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 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
251fn 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
269pub 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
313pub 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
336pub 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
362fn 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
374pub 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
438pub 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(¬e, 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
472fn 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
481fn 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
504pub 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 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
682fn 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
695pub 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
724pub 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
761pub fn tempo_to_braille(tempo: &TempoText, max_line_length: usize) -> Transcription {
763 tempo_text_to_braille(tempo.text(), max_line_length)
764}
765
766pub 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
799pub 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
826pub 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
837pub 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
866pub 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
935pub(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
959pub 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
1012pub 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
1032pub 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
1055pub 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
1086pub 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
1099fn 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
1139pub 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
1231pub 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 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(¬e, 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}