Skip to main content

music21_rs/braille/
lookup.rs

1//! The signs of braille music: music21's `braille.lookup`.
2//!
3//! A cell is named by its raised dots, written as one number with the dots
4//! in order: `1245` is dots one, two, four and five, `⠛`. The signs follow
5//! Mary Turner De Garmo's *Introduction to Braille Music Transcription*
6//! (2005), with some from Bettye Krolick's *New International Manual of
7//! Braille Music Notation* where De Garmo has none.
8
9/// The cell with these dots raised: music21's `brailleDotDict`. Nought is
10/// the blank cell.
11///
12/// # Panics
13///
14/// A digit outside one to six.
15pub const fn cell(dots: u32) -> char {
16    let mut rest = dots;
17    let mut bits = 0;
18    while rest > 0 {
19        let dot = rest % 10;
20        assert!(dot >= 1 && dot <= 6, "a braille cell has dots one to six");
21        bits |= 1 << (dot - 1);
22        rest /= 10;
23    }
24    match char::from_u32(0x2800 + bits) {
25        Some(cell) => cell,
26        None => unreachable!(),
27    }
28}
29
30/// The cells with these dots, one after another.
31pub fn cells(dots: &[u32]) -> String {
32    dots.iter().map(|dots| cell(*dots)).collect()
33}
34
35/// The cell holding every dot of these numbers together: music21's
36/// `dotsAdd`.
37pub fn dots_add(dots: &[u32]) -> char {
38    let mut all: Vec<u32> = dots
39        .iter()
40        .flat_map(|number| {
41            number
42                .to_string()
43                .chars()
44                .filter_map(|digit| digit.to_digit(10))
45                .filter(|digit| *digit != 0)
46                .collect::<Vec<_>>()
47        })
48        .collect();
49    all.sort_unstable();
50    let joined = all.iter().fold(0, |number, digit| number * 10 + digit);
51    cell(joined)
52}
53
54/// The dots a note's letter is written with.
55fn step_dots(step: char) -> Option<u32> {
56    Some(match step {
57        'C' => 145,
58        'D' => 15,
59        'E' => 124,
60        'F' => 1245,
61        'G' => 125,
62        'A' => 24,
63        'B' => 245,
64        _ => return None,
65    })
66}
67
68/// A note of this letter and note value: music21's `pitchNameToNotes`. A
69/// value and its sixteenfold share a sign; a breve and a longa are a whole
70/// note written around one and two breve signs.
71pub fn pitch_name_to_note(step: char, duration_type: &str) -> Option<String> {
72    let step = step_dots(step)?;
73    let value = |dots: u32| dots_add(&[step, dots]);
74    let whole = value(36);
75    Some(match duration_type {
76        "128th" | "eighth" => value(0).to_string(),
77        "64th" | "quarter" => value(6).to_string(),
78        "32nd" | "half" => value(3).to_string(),
79        "16th" | "whole" => whole.to_string(),
80        "breve" => format!("{whole}{}{}{whole}", cell(45), cell(14)),
81        "longa" => format!(
82            "{whole}{}{}{}{}{whole}",
83            cell(45),
84            cell(14),
85            cell(45),
86            cell(14)
87        ),
88        _ => return None,
89    })
90}
91
92/// The sign of an octave from nought to eight: music21's `octaves`.
93pub fn octave(octave: i64) -> Option<String> {
94    Some(match octave {
95        0 => cells(&[4, 4]),
96        1 => cells(&[4]),
97        2 => cells(&[45]),
98        3 => cells(&[456]),
99        4 => cells(&[5]),
100        5 => cells(&[46]),
101        6 => cells(&[56]),
102        7 => cells(&[6]),
103        8 => cells(&[6, 6]),
104        _ => return None,
105    })
106}
107
108/// The sign of an accidental by music21's name for it: music21's
109/// `accidentals`.
110pub fn accidental(name: &str) -> Option<String> {
111    let sharp = cell(146);
112    let flat = cell(126);
113    Some(match name {
114        "sharp" => sharp.to_string(),
115        "double-sharp" => format!("{sharp}{sharp}"),
116        "triple-sharp" => format!("{sharp}{sharp}{sharp}"),
117        "quadruple-sharp" => format!("{sharp}{sharp}{sharp}{sharp}"),
118        "half-sharp" => format!("{}{sharp}", cell(4)),
119        "one-and-a-half-sharp" => format!("{}{sharp}", cell(456)),
120        "flat" => flat.to_string(),
121        "double-flat" => format!("{flat}{flat}"),
122        "triple-flat" => format!("{flat}{flat}{flat}"),
123        "quadruple-flat" => format!("{flat}{flat}{flat}{flat}"),
124        "half-flat" => format!("{}{flat}", cell(4)),
125        "one-and-a-half-flat" => format!("{}{flat}", cell(456)),
126        "natural" => cell(16).to_string(),
127        _ => return None,
128    })
129}
130
131/// The sign of an interval from a second to an octave: music21's
132/// `intervals`.
133pub fn interval(size: i64) -> Option<char> {
134    Some(cell(match size {
135        2 => 34,
136        3 => 346,
137        4 => 3456,
138        5 => 35,
139        6 => 356,
140        7 => 25,
141        8 => 36,
142        _ => return None,
143    }))
144}
145
146/// The key signature of so many sharps, or flats below nought: music21's
147/// `keySignatures`.
148pub fn key_signature(sharps: i64) -> Option<String> {
149    let (count, sign) = if sharps < 0 {
150        (-sharps, 126)
151    } else {
152        (sharps, 146)
153    };
154    signs_of(count, sign)
155}
156
157/// So many naturals cancelling a key signature: music21's `naturals`.
158pub fn naturals(count: i64) -> Option<String> {
159    signs_of(count, 16)
160}
161
162/// One to three signs written out, four to seven as a number and the sign.
163fn signs_of(count: i64, sign: u32) -> Option<String> {
164    Some(match count {
165        0 => String::new(),
166        1..=3 => cells(&vec![sign; count as usize]),
167        4 => cells(&[3456, 145, sign]),
168        5 => cells(&[3456, 15, sign]),
169        6 => cells(&[3456, 124, sign]),
170        7 => cells(&[3456, 1245, sign]),
171        _ => return None,
172    })
173}
174
175/// A digit in the upper cells, as a number is written: music21's
176/// `numbersUpper`.
177pub fn number_upper(digit: u32) -> Option<char> {
178    Some(cell(match digit {
179        0 => 245,
180        1 => 1,
181        2 => 12,
182        3 => 14,
183        4 => 145,
184        5 => 15,
185        6 => 124,
186        7 => 1245,
187        8 => 125,
188        9 => 24,
189        _ => return None,
190    }))
191}
192
193/// A digit in the lower cells, as a time signature's denominator is written:
194/// music21's `numbersLower`.
195pub fn number_lower(digit: u32) -> Option<char> {
196    Some(cell(match digit {
197        0 => 356,
198        1 => 2,
199        2 => 23,
200        3 => 25,
201        4 => 256,
202        5 => 26,
203        6 => 235,
204        7 => 2356,
205        8 => 236,
206        9 => 35,
207        _ => return None,
208    }))
209}
210
211/// The rest of a note value: music21's `rests`. `dummy` is the dot written
212/// for a rest standing in for a missing part.
213pub fn rest(duration_type: &str) -> Option<String> {
214    Some(match duration_type {
215        "dummy" => cells(&[3]),
216        "128th" | "eighth" => cells(&[1346]),
217        "64th" | "quarter" => cells(&[1236]),
218        "32nd" | "half" => cells(&[136]),
219        "16th" | "whole" => cells(&[134]),
220        "breve" => cells(&[134, 45, 14, 134]),
221        "longa" => cells(&[134, 45, 14, 45, 14, 134]),
222        _ => return None,
223    })
224}
225
226/// The sign saying which half of the note values follows: music21's
227/// `lengthPrefixes`, `larger` for whole to eighth, `smaller` for 16th to
228/// 128th and `xsmall` for shorter.
229pub fn length_prefix(size: &str) -> Option<String> {
230    Some(match size {
231        "larger" => cells(&[45, 126, 2]),
232        "smaller" => cells(&[6, 126, 2]),
233        "xsmall" => cells(&[56, 126, 2]),
234        _ => return None,
235    })
236}
237
238/// The sign of a barline by music21's type: music21's `barlines`.
239pub fn barline(barline_type: &str) -> Option<String> {
240    Some(match barline_type {
241        "final" => cells(&[126, 13]),
242        "double" => cells(&[126, 13, 3]),
243        "dashed" => cells(&[13]),
244        "heavy" => cells(&[123]),
245        _ => return None,
246    })
247}
248
249/// The sign of a finger, one to five: music21's `fingerMarks`.
250pub fn finger_mark(finger: &str) -> Option<char> {
251    Some(cell(match finger {
252        "1" => 1,
253        "2" => 12,
254        "3" => 123,
255        "4" => 2,
256        "5" => 13,
257        _ => return None,
258    }))
259}
260
261/// The sign starting a clef: music21's `clefs['prefix']`.
262pub fn clef_prefix() -> char {
263    cell(345)
264}
265
266/// The sign of a clef of this sign on this line: music21's `clefs`.
267pub fn clef(sign: &str, line: i64) -> Option<String> {
268    let (base, own_line) = match sign {
269        "G" => (34, 2),
270        "C" => (346, 3),
271        "F" => (3456, 4),
272        _ => return None,
273    };
274    if !(1..=5).contains(&line) {
275        return None;
276    }
277    let mut written = cell(base).to_string();
278    if line != own_line {
279        written.push_str(&octave(line)?);
280    }
281    Some(written)
282}
283
284/// The sign ending a clef, the other for a keyboard hand switched: music21's
285/// `clefs['suffix']`.
286pub fn clef_suffix(keyboard_hand_switched: bool) -> char {
287    if keyboard_hand_switched {
288        cell(13)
289    } else {
290        cell(123)
291    }
292}
293
294/// The sign of a bowing by music21's name: music21's `bowingSymbols`.
295pub fn bowing(name: &str) -> Option<String> {
296    Some(match name {
297        "down bow" => cells(&[126, 12]),
298        "up bow" => cells(&[126, 3]),
299        _ => return None,
300    })
301}
302
303/// The sign of an articulation written before its note, by music21's name:
304/// music21's `beforeNoteExpr`.
305pub fn before_note_expression(name: &str) -> Option<String> {
306    Some(match name {
307        "staccato" => cells(&[236]),
308        "accent" => cells(&[46, 236]),
309        "tenuto" => cells(&[456, 236]),
310        "staccatissimo" => cells(&[6, 236]),
311        "strong accent" => cells(&[56, 236]),
312        "detached legato" => cells(&[5, 236]),
313        _ => return None,
314    })
315}
316
317/// The signs of the hairpin words written as text: music21's
318/// `textExpressions`.
319pub fn text_expression(words: &str) -> Option<String> {
320    Some(match words {
321        "crescendo" | "cresc." | "cr." => cells(&[345, 14, 1235, 3]),
322        "decrescendo" | "decresc." | "decr." => cells(&[345, 145, 15, 14, 1235, 3]),
323        _ => return None,
324    })
325}
326
327/// A letter, a digit or a punctuation mark: music21's `alphabet`.
328pub fn alphabet(character: char) -> Option<String> {
329    if let Some(digit) = character.to_digit(10)
330        && character.is_ascii_digit()
331    {
332        return number_upper(digit).map(String::from);
333    }
334    Some(match character {
335        'a' => cells(&[1]),
336        'b' => cells(&[12]),
337        'c' => cells(&[14]),
338        'd' => cells(&[145]),
339        'e' => cells(&[15]),
340        'f' => cells(&[124]),
341        'g' => cells(&[1245]),
342        'h' => cells(&[125]),
343        'i' => cells(&[24]),
344        'j' => cells(&[245]),
345        'k' => cells(&[13]),
346        'l' => cells(&[123]),
347        'm' => cells(&[134]),
348        'n' => cells(&[1345]),
349        'o' => cells(&[135]),
350        'p' => cells(&[1234]),
351        'q' => cells(&[12345]),
352        'r' => cells(&[1235]),
353        's' => cells(&[234]),
354        't' => cells(&[2345]),
355        'u' => cells(&[136]),
356        'v' => cells(&[1236]),
357        'w' => cells(&[2456]),
358        'x' => cells(&[1346]),
359        'y' => cells(&[13456]),
360        'z' => cells(&[1356]),
361        ' ' => cell(0).to_string(),
362        '!' => cells(&[235]),
363        '\'' => cells(&[3]),
364        ',' => cells(&[2]),
365        '-' => cells(&[356]),
366        '.' => cells(&[256]),
367        '/' => cells(&[34]),
368        ':' => cells(&[25]),
369        '?' => cells(&[236]),
370        '(' | ')' => cells(&[2356]),
371        '^' => cells(&[4]),
372        '[' => cells(&[6, 2356]),
373        ']' => cells(&[2356, 3]),
374        '*' => cells(&[35, 35]),
375        _ => return None,
376    })
377}
378
379/// A sign of a chord symbol: music21's `chordSymbols`.
380pub fn chord_symbol(name: &str) -> Option<String> {
381    Some(match name {
382        "plus" => cells(&[346]),
383        "minus" => cells(&[46]),
384        "diminished_circle" => cells(&[256]),
385        "half_diminished_circle" => cells(&[256, 3]),
386        "triangle" => cells(&[356]),
387        "triangle_line" => cells(&[356, 3]),
388        "italics_seven" => cells(&[46, 3456, 1246]),
389        "slash" => cells(&[34]),
390        "parentheses" => cells(&[2356]),
391        _ => return None,
392    })
393}
394
395/// A sign by music21's name for it: music21's `symbols`.
396///
397/// # Panics
398///
399/// A name music21 has no sign for: every caller asks for one it has.
400pub fn symbol(name: &str) -> String {
401    match name {
402        "space" => cells(&[0]),
403        "double_space" => cells(&[0, 0]),
404        "number" => cells(&[3456]),
405        "letter_sign" => cells(&[56]),
406        "dot" => cells(&[3]),
407        "tie" => cells(&[4, 14]),
408        "uppercase" => cells(&[6]),
409        "metronome" => cells(&[2356]),
410        "common" => cells(&[46, 14]),
411        "cut" => cells(&[456, 14]),
412        "music_hyphen" | "transcriber-added_sign" => cells(&[5]),
413        "music_asterisk" => cells(&[345, 26, 35]),
414        "rh_keyboard" => cells(&[46, 345]),
415        "lh_keyboard" => cells(&[456, 345]),
416        "word" => cells(&[345]),
417        "triplet" => cells(&[23]),
418        "tuplet_prefix" => cells(&[456]),
419        "finger_change" => cells(&[14]),
420        "first_set_missing_fingermark" => cells(&[6]),
421        "second_set_missing_fingermark" => cells(&[3]),
422        "opening_single_slur" => cells(&[14]),
423        "opening_double_slur" => cells(&[14, 14]),
424        "closing_double_slur" => cells(&[14]),
425        "opening_bracket_slur" => cells(&[56, 12]),
426        "closing_bracket_slur" => cells(&[45, 23]),
427        "basic_exception" => cells(&[345, 236]),
428        "full_inaccord" => cells(&[126, 345]),
429        "repeat" => cells(&[2356]),
430        "print-pagination" => cells(&[5, 25]),
431        "braille-music-parenthesis" => cells(&[6, 3]),
432        other => panic!("music21 has no braille sign named {other:?}"),
433    }
434}
435
436/// The sign of a fermata of this shape: music21's `fermatas['shape']`.
437pub fn fermata(shape: &str) -> Option<String> {
438    Some(match shape {
439        "normal" => cells(&[126, 123]),
440        "angled" => cells(&[45, 126, 123]),
441        "square" => cells(&[56, 126, 123]),
442        _ => return None,
443    })
444}
445
446/// The ASCII character standing for a cell in braille ASCII: music21's
447/// `ascii_chars`.
448pub fn ascii_char(braille: char) -> Option<char> {
449    let bits = (braille as u32)
450        .checked_sub(0x2800)
451        .filter(|bits| *bits < 64)?;
452    Some(ASCII[bits as usize])
453}
454
455/// Braille ASCII by the cell's dots as bits, dot one lowest.
456const ASCII: [char; 64] = [
457    ' ', 'A', '1', 'B', '\'', 'K', '2', 'L', '@', 'C', 'I', 'F', '/', 'M', 'S', 'P', '"', 'E', '3',
458    'H', '9', 'O', '6', 'R', '^', 'D', 'J', 'G', '>', 'N', 'T', 'Q', ',', '*', '5', '<', '-', 'U',
459    '8', 'V', '.', '%', '[', '$', '+', 'X', '!', '&', ';', ':', '4', '\\', '0', 'Z', '7', '(', '_',
460    '?', 'W', ']', '#', 'Y', ')', '=',
461];
462
463/// Each row of a cell's dots, left and right, as `0` and `1`: music21's
464/// `binary_dots`. music21 writes dots one, two, three, five and six as
465/// `01 11 11`, the first row backwards, and so does this.
466pub fn binary_dots(braille: char) -> Option<[&'static str; 3]> {
467    let bits = (braille as u32)
468        .checked_sub(0x2800)
469        .filter(|bits| *bits < 64)?;
470    if bits == 0b110111 {
471        return Some(["01", "11", "11"]);
472    }
473    let row = |left: u32, right: u32| match (bits >> left & 1, bits >> right & 1) {
474        (0, 0) => "00",
475        (0, _) => "01",
476        (_, 0) => "10",
477        _ => "11",
478    };
479    Some([row(0, 3), row(1, 4), row(2, 5)])
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn cells_are_named_by_their_dots() {
488        // music21's makeBrailleDictionary doctest.
489        assert_eq!(cell(124), '⠋');
490        assert_eq!(cell(126), '⠣');
491        assert_eq!(cell(0), '⠀');
492        assert_eq!(dots_add(&[1245, 36]), '⠿');
493        assert_eq!(pitch_name_to_note('C', "eighth").as_deref(), Some("⠙"));
494        assert_eq!(pitch_name_to_note('C', "whole").as_deref(), Some("⠽"));
495        assert_eq!(ascii_char(cell(1246)), Some('$'));
496        assert_eq!(binary_dots(cell(12356)), Some(["01", "11", "11"]));
497        assert_eq!(binary_dots(cell(1456)), Some(["11", "01", "01"]));
498    }
499}