1use super::basic::word_to_braille;
4use super::lookup;
5use super::segment::{BrailleGrandSegment, Hand, SegmentOptions, find_segments};
6use crate::{
7 error::{Error, Result},
8 metadata::Metadata,
9 stream::{Stream, StreamElement, StreamKind},
10};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15#[non_exhaustive]
16pub struct BrailleOptions {
17 pub in_place: bool,
20 pub debug: bool,
23 pub segment: SegmentOptions,
25}
26
27pub fn stream_to_braille(stream: &Stream, options: &BrailleOptions) -> Result<String> {
35 match stream.kind() {
36 StreamKind::Part | StreamKind::PartStaff => part_to_braille(stream, options),
37 StreamKind::Measure => measure_to_braille(stream, options),
38 StreamKind::Score => score_to_braille(stream, options),
39 StreamKind::Opus => {
40 let scores: Vec<String> = stream
41 .events()
42 .iter()
43 .filter_map(|event| event.element().as_stream())
44 .filter(|inner| inner.kind() == StreamKind::Score)
45 .map(|score| score_to_braille(score, options))
46 .collect::<Result<_>>()?;
47 Ok(scores.join("\n\n"))
48 }
49 _ => {
50 let staves: Vec<&Stream> = stream
51 .events()
52 .iter()
53 .filter_map(|event| event.element().as_stream())
54 .filter(|inner| inner.kind() == StreamKind::PartStaff)
55 .collect();
56 if staves.len() == 2 {
57 score_to_braille(stream, options)
58 } else {
59 Err(Error::Notation(
60 "Stream cannot be translated to Braille.".to_string(),
61 ))
62 }
63 }
64 }
65}
66
67pub fn score_to_braille(score: &Stream, options: &BrailleOptions) -> Result<String> {
74 let mut lines = Vec::new();
75 if let Some(metadata) = score.metadata() {
76 lines.push(metadata_to_string(metadata, !options.debug)?);
77 }
78 let mut pending: Option<Stream> = None;
79 let mut first_leaf = 0;
80 for event in score.events() {
81 let leaves = match event.element() {
82 StreamElement::Stream(inner) => inner.leaves().len(),
83 _ => 1,
84 };
85 let start = first_leaf;
86 first_leaf += leaves;
87 let Some(part) = event.element().as_stream() else {
88 continue;
89 };
90 let part = &with_own_spanners(score, part, start);
91 match part.kind() {
92 StreamKind::PartStaff => {
93 if let Some(upper) = pending.take() {
94 lines.push(keyboard_parts_to_braille(&upper, part, options)?);
95 } else {
96 pending = Some(part.clone());
97 }
98 }
99 StreamKind::Part => {
100 if let Some(alone) = pending.take() {
101 lines.push(part_to_braille(&alone, options)?);
102 }
103 lines.push(part_to_braille(part, options)?);
104 }
105 _ => {}
106 }
107 }
108 if let Some(alone) = pending {
109 lines.push(part_to_braille(&alone, options)?);
110 }
111 Ok(lines.join("\n"))
112}
113
114fn with_own_spanners(score: &Stream, part: &Stream, start: usize) -> Stream {
120 let mut part = part.clone();
121 let later_staff = part.kind() == StreamKind::PartStaff
122 && part
123 .id()
124 .and_then(|id| id.rsplit_once("-Staff"))
125 .is_some_and(|(_, number)| number != "1");
126 if later_staff {
127 return part;
128 }
129 let end = start + part.leaves().len();
130 let inside = |place: Option<&Option<usize>>| {
131 place.is_some_and(|place| place.is_some_and(|place| (start..end).contains(&place)))
132 };
133 for spanner in score.spanners() {
134 let places = spanner.spanned();
135 if inside(places.first()) && inside(places.last()) {
136 let mut own = spanner.clone();
137 let moved: Vec<Option<usize>> =
138 (0..end).map(|place| place.checked_sub(start)).collect();
139 own.move_places(&moved);
140 part.add_spanner(own);
141 }
142 }
143 part
144}
145
146pub fn metadata_to_string(metadata: &Metadata, braille: bool) -> Result<String> {
153 let mut lines = Vec::new();
154 for (unique_name, value) in metadata.all() {
155 if unique_name == "software" {
156 continue;
157 }
158 let Some(namespace) = crate::metadata::namespace_name(unique_name) else {
159 continue;
160 };
161 if namespace.starts_with("m21FileInfo:") {
162 continue;
163 }
164 let words = split_camel_case(unique_name);
165 let mut line = format!("{}: {}", title_case(&words.join(" ")), value.text());
166 if braille {
167 let written: Vec<String> = line
168 .split_whitespace()
169 .map(|word| word_to_braille(word, false))
170 .collect::<Result<_>>()?;
171 line = written.join(&lookup::alphabet(' ').unwrap_or_default());
172 }
173 lines.push(line);
174 }
175 lines.sort();
176 Ok(lines.join("\n"))
177}
178
179fn split_camel_case(name: &str) -> Vec<String> {
182 let characters: Vec<char> = name.chars().collect();
183 let mut words = Vec::new();
184 let mut index = 0;
185 while index < characters.len() {
186 let start = index;
187 let mut upper_end = index;
188 while upper_end < characters.len() && characters[upper_end].is_ascii_uppercase() {
189 upper_end += 1;
190 }
191 let mut lower_end = upper_end;
192 while lower_end < characters.len() && characters[lower_end].is_ascii_lowercase() {
193 lower_end += 1;
194 }
195 if lower_end > upper_end {
196 words.push(characters[start..lower_end].iter().collect());
197 index = lower_end;
198 } else {
199 index = start + 1;
200 }
201 }
202 words
203}
204
205fn title_case(text: &str) -> String {
208 let mut out = String::new();
209 let mut previous_letter = false;
210 for character in text.chars() {
211 if character.is_alphabetic() {
212 if previous_letter {
213 out.extend(character.to_lowercase());
214 } else {
215 out.extend(character.to_uppercase());
216 }
217 previous_letter = true;
218 } else {
219 out.push(character);
220 previous_letter = false;
221 }
222 }
223 out
224}
225
226fn notated(part: &Stream, options: &BrailleOptions) -> Result<Stream> {
229 let mut part = part.clone();
230 if !options.in_place {
231 crate::makenotation::make_part_notation_keeping_spanners(&mut part, false)?;
232 }
233 Ok(part)
234}
235
236pub fn part_to_braille(part: &Stream, options: &BrailleOptions) -> Result<String> {
242 let part = notated(part, options)?;
243 let mut segments = find_segments(&part, None, options.segment);
244 let mut written = Vec::new();
245 for segment in &mut segments {
246 let braille = segment.transcribe()?;
247 written.push(if options.debug {
248 segment.english()
249 } else {
250 braille
251 });
252 }
253 Ok(written.join("\n"))
254}
255
256pub fn measure_to_braille(measure: &Stream, options: &BrailleOptions) -> Result<String> {
263 let mut part = Stream::with_kind(StreamKind::Part);
264 part.push(measure.clone());
265 if !options.in_place {
266 crate::makenotation::keeping_spanners(
272 &mut part,
273 crate::makenotation::Kept::Retained,
274 &mut crate::makenotation::make_measure_notation,
275 )?;
276 }
277 part_to_braille(
278 &part,
279 &BrailleOptions {
280 in_place: true,
281 ..*options
282 },
283 )
284}
285
286pub fn keyboard_parts_to_braille(
293 upper: &Stream,
294 lower: &Stream,
295 options: &BrailleOptions,
296) -> Result<String> {
297 let upper = notated(upper, options)?;
298 let lower = notated(lower, options)?;
299 let right = find_segments(&upper, Some(Hand::Right), options.segment);
300 let left = find_segments(&lower, Some(Hand::Left), options.segment);
301 let mut written = Vec::new();
302 for (right, left) in right.into_iter().zip(left) {
303 let mut grand = BrailleGrandSegment {
304 groupings: right.groupings.into_iter().chain(left.groupings).collect(),
305 line_length: options.segment.max_line_length,
306 right_contexts: right.contexts,
307 left_contexts: left.contexts,
308 };
309 let braille = grand.transcribe()?;
310 written.push(if options.debug {
311 grand.english()
312 } else {
313 braille
314 });
315 }
316 Ok(written.join("\n"))
317}
318
319pub fn element_to_braille(element: StreamElement, options: &BrailleOptions) -> Result<String> {
326 if element.as_stream().is_some() {
327 return Err(Error::Notation(
328 "Stream cannot be translated to Braille.".to_string(),
329 ));
330 }
331 let mut measure = Stream::with_kind(StreamKind::Measure);
332 measure.push(element);
333 let mut part = Stream::with_kind(StreamKind::Part);
334 part.push(measure);
335 let mut segment = options.segment;
336 segment.show_first_measure_number = false;
337 segment.show_heading = false;
338 part_to_braille(
339 &part,
340 &BrailleOptions {
341 in_place: true,
342 segment,
343 ..*options
344 },
345 )
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::braille::SegmentDivision;
352 use crate::defaults::IntegerType;
353 use crate::{Duration, KeySignature, Note, StreamKind};
354
355 fn staff(name: &str) -> Stream {
357 let mut staff = Stream::with_kind(StreamKind::PartStaff);
358 for number in 1..=2 {
359 let mut measure = Stream::with_kind(StreamKind::Measure);
360 measure.set_number(number);
361 if number == 1 {
362 measure.push(KeySignature::new(2));
363 }
364 let note = Note::from_name(name)
365 .unwrap()
366 .with_duration(Duration::new(4.0).unwrap());
367 measure.push(note);
368 staff.insert(4.0 * (number - 1) as f64, measure);
369 }
370 staff
371 }
372
373 #[test]
376 fn a_lone_key_signature_heads_the_keyboard() {
377 let written =
378 keyboard_parts_to_braille(&staff("D5"), &staff("D3"), &BrailleOptions::default())
379 .unwrap();
380 assert_eq!(written, "⠀⠀⠩⠩⠀⠀\n⠁⠀⠨⠜⠨⠵\n⠀⠀⠸⠜⠸⠵");
381 }
382
383 fn divided(measure: IntegerType, offset: f64, division: SegmentDivision) -> String {
386 let mut part =
387 crate::tinynotation::from_tiny_notation("3/4 c4 c c e e e g g g c'2.").unwrap();
388 crate::makenotation::make_notation(&mut part).unwrap();
389 for event in part.events_mut() {
390 let StreamElement::Stream(held) = event.element_mut() else {
391 continue;
392 };
393 if held.number() != measure {
394 continue;
395 }
396 held.insert(offset, division);
397 }
398 stream_to_braille(&part, &BrailleOptions::default()).unwrap()
399 }
400
401 #[test]
405 fn a_segment_division_divides_the_segment() {
406 let whole = "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠼⠁⠀⠐⠹⠹⠹⠀⠫⠫⠫⠀⠳⠳⠳⠀⠨⠝⠄⠣⠅";
407 assert_eq!(
408 divided(2, 1.0, SegmentDivision::Required),
409 "⠀⠀⠀⠼⠉⠲⠀⠀⠀⠀\n⠼⠁⠀⠐⠹⠹⠹⠀⠫⠐\n⠼⠃⠄⠀⠐⠫⠫⠀⠳⠳⠳⠀⠨⠝⠄⠣⠅"
410 );
411 assert_eq!(divided(1, 1.0, SegmentDivision::Optional), whole);
412 assert_eq!(divided(1, 0.0, SegmentDivision::Required), whole);
413 }
414
415 fn one_measure(name: &str, signed: bool) -> Stream {
418 let mut staff = Stream::with_kind(StreamKind::PartStaff);
419 let mut measure = Stream::with_kind(StreamKind::Measure);
420 measure.set_number(1);
421 if signed {
422 measure.push(KeySignature::new(2));
423 measure.push(crate::TimeSignature::new(4, 4).unwrap());
424 }
425 measure.push(
426 Note::from_name(name)
427 .unwrap()
428 .with_duration(Duration::new(4.0).unwrap()),
429 );
430 staff.insert(0.0, measure);
431 staff
432 }
433
434 #[test]
436 fn either_hands_signatures_head_the_keyboard() {
437 for left_signed in [false, true] {
438 let written = keyboard_parts_to_braille(
439 &one_measure("C5", !left_signed),
440 &one_measure("C3", left_signed),
441 &BrailleOptions::default(),
442 )
443 .unwrap();
444 let heading = written.lines().next().unwrap().trim_matches('\u{2800}');
445 assert_eq!(heading, "⠩⠩⠼⠙⠲");
446 }
447 }
448
449 fn staccato_counts(counts: &[usize]) -> Vec<usize> {
452 use crate::articulations::{Articulation, ArticulationKind};
453 let mut part = Stream::with_kind(StreamKind::Part);
454 let mut measure = Stream::with_kind(StreamKind::Measure);
455 measure.set_number(1);
456 for &count in counts {
457 let mut note = Note::from_name("C4")
458 .unwrap()
459 .with_duration(Duration::new(0.5).unwrap());
460 for _ in 0..count {
461 note.articulations_mut()
462 .push(Articulation::of_kind(ArticulationKind::Staccato));
463 }
464 measure.push(note);
465 }
466 part.insert(0.0, measure);
467 let segments = find_segments(&part, None, SegmentOptions::default());
468 segments
469 .iter()
470 .flat_map(|segment| segment.groupings.values())
471 .flat_map(|grouping| &grouping.elements)
472 .filter_map(|element| match &element.element {
473 StreamElement::Note(note) => Some(note.articulations().len()),
474 _ => None,
475 })
476 .collect()
477 }
478
479 #[test]
482 fn four_staccatos_in_a_row_are_doubled_on_the_first() {
483 assert_eq!(staccato_counts(&[1, 1, 1, 1]), [2, 0, 0, 1]);
484 assert_eq!(staccato_counts(&[1, 2, 1, 1]), [2, 0, 0, 1]);
485 assert_eq!(staccato_counts(&[1, 1, 1]), [1, 1, 1]);
486 }
487
488 #[test]
491 fn a_clefs_chord_direction_carries_into_the_next_segment() {
492 for (clef, expected) in [
493 (Some(crate::Clef::treble()), true),
494 (Some(crate::Clef::bass()), false),
495 (None, true),
496 ] {
497 let mut part = Stream::with_kind(StreamKind::Part);
498 for number in 1..=3 {
499 let mut measure = Stream::with_kind(StreamKind::Measure);
500 measure.set_number(number);
501 if number == 1
502 && let Some(clef) = &clef
503 {
504 measure.push(clef.clone());
505 }
506 if number == 3 {
507 measure.push(SegmentDivision::Required);
508 }
509 measure.push(
510 crate::Chord::new("C3 E3 G3")
511 .unwrap()
512 .with_duration(Duration::new(4.0).unwrap()),
513 );
514 part.insert(4.0 * f64::from(number - 1), measure);
515 }
516 let segments = find_segments(&part, None, SegmentOptions::default());
517 assert_eq!(segments.len(), 2);
518 for segment in &segments {
519 for (key, grouping) in &segment.groupings {
520 if key.affinity == crate::braille::segment::Affinity::NoteGrouping {
521 assert_eq!(grouping.descending_chords, Some(expected));
522 }
523 }
524 }
525 }
526 }
527}