1pub 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
30pub fn cells(dots: &[u32]) -> String {
32 dots.iter().map(|dots| cell(*dots)).collect()
33}
34
35pub 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
54fn 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
68pub 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
92pub 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
108pub 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
131pub 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
146pub 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
157pub fn naturals(count: i64) -> Option<String> {
159 signs_of(count, 16)
160}
161
162fn 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
175pub 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
193pub 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
211pub 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
226pub 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
238pub 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
249pub 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
261pub fn clef_prefix() -> char {
263 cell(345)
264}
265
266pub 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
284pub fn clef_suffix(keyboard_hand_switched: bool) -> char {
287 if keyboard_hand_switched {
288 cell(13)
289 } else {
290 cell(123)
291 }
292}
293
294pub 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
303pub 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
317pub 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
327pub 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
379pub 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
395pub 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
436pub 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
446pub 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
455const 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
463pub 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 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}