music21_rs/braille/
translate.rs1use 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)]
15pub struct BrailleOptions {
16 pub in_place: bool,
19 pub debug: bool,
22 pub segment: SegmentOptions,
24}
25
26pub fn stream_to_braille(stream: &Stream, options: &BrailleOptions) -> Result<String> {
34 match stream.kind() {
35 StreamKind::Part | StreamKind::PartStaff => part_to_braille(stream, options),
36 StreamKind::Measure => measure_to_braille(stream, options),
37 StreamKind::Score => score_to_braille(stream, options),
38 StreamKind::Opus => {
39 let scores: Vec<String> = stream
40 .events()
41 .iter()
42 .filter_map(|event| event.element().as_stream())
43 .filter(|inner| inner.kind() == StreamKind::Score)
44 .map(|score| score_to_braille(score, options))
45 .collect::<Result<_>>()?;
46 Ok(scores.join("\n\n"))
47 }
48 _ => {
49 let staves: Vec<&Stream> = stream
50 .events()
51 .iter()
52 .filter_map(|event| event.element().as_stream())
53 .filter(|inner| inner.kind() == StreamKind::PartStaff)
54 .collect();
55 if staves.len() == 2 {
56 score_to_braille(stream, options)
57 } else {
58 Err(Error::Notation(
59 "Stream cannot be translated to Braille.".to_string(),
60 ))
61 }
62 }
63 }
64}
65
66pub fn score_to_braille(score: &Stream, options: &BrailleOptions) -> Result<String> {
73 let mut lines = Vec::new();
74 if let Some(metadata) = score.metadata() {
75 lines.push(metadata_to_string(metadata, !options.debug)?);
76 }
77 let mut pending: Option<Stream> = None;
78 let mut first_leaf = 0;
79 for event in score.events() {
80 let leaves = match event.element() {
81 StreamElement::Stream(inner) => inner.leaves().len(),
82 _ => 1,
83 };
84 let start = first_leaf;
85 first_leaf += leaves;
86 let Some(part) = event.element().as_stream() else {
87 continue;
88 };
89 let part = &with_own_spanners(score, part, start);
90 match part.kind() {
91 StreamKind::PartStaff => {
92 if let Some(upper) = pending.take() {
93 lines.push(keyboard_parts_to_braille(&upper, part, options)?);
94 } else {
95 pending = Some(part.clone());
96 }
97 }
98 StreamKind::Part => {
99 if let Some(alone) = pending.take() {
100 lines.push(part_to_braille(&alone, options)?);
101 }
102 lines.push(part_to_braille(part, options)?);
103 }
104 _ => {}
105 }
106 }
107 if let Some(alone) = pending {
108 lines.push(part_to_braille(&alone, options)?);
109 }
110 Ok(lines.join("\n"))
111}
112
113fn with_own_spanners(score: &Stream, part: &Stream, start: usize) -> Stream {
119 let mut part = part.clone();
120 let later_staff = part.kind() == StreamKind::PartStaff
121 && part
122 .id()
123 .and_then(|id| id.rsplit_once("-Staff"))
124 .is_some_and(|(_, number)| number != "1");
125 if later_staff {
126 return part;
127 }
128 let end = start + part.leaves().len();
129 let inside = |place: Option<&Option<usize>>| {
130 place.is_some_and(|place| place.is_some_and(|place| (start..end).contains(&place)))
131 };
132 for spanner in score.spanners() {
133 let places = spanner.spanned();
134 if inside(places.first()) && inside(places.last()) {
135 let mut own = spanner.clone();
136 let moved: Vec<Option<usize>> =
137 (0..end).map(|place| place.checked_sub(start)).collect();
138 own.move_places(&moved);
139 part.add_spanner(own);
140 }
141 }
142 part
143}
144
145pub fn metadata_to_string(metadata: &Metadata, braille: bool) -> Result<String> {
152 let mut lines = Vec::new();
153 for (unique_name, value) in metadata.all() {
154 if unique_name == "software" {
155 continue;
156 }
157 let Some(namespace) = crate::metadata::namespace_name(unique_name) else {
158 continue;
159 };
160 if namespace.starts_with("m21FileInfo:") {
161 continue;
162 }
163 let words = split_camel_case(unique_name);
164 let mut line = format!("{}: {}", title_case(&words.join(" ")), value.text());
165 if braille {
166 let written: Vec<String> = line
167 .split_whitespace()
168 .map(|word| word_to_braille(word, false))
169 .collect::<Result<_>>()?;
170 line = written.join(&lookup::alphabet(' ').unwrap_or_default());
171 }
172 lines.push(line);
173 }
174 lines.sort();
175 Ok(lines.join("\n"))
176}
177
178fn split_camel_case(name: &str) -> Vec<String> {
181 let characters: Vec<char> = name.chars().collect();
182 let mut words = Vec::new();
183 let mut index = 0;
184 while index < characters.len() {
185 let start = index;
186 let mut upper_end = index;
187 while upper_end < characters.len() && characters[upper_end].is_ascii_uppercase() {
188 upper_end += 1;
189 }
190 let mut lower_end = upper_end;
191 while lower_end < characters.len() && characters[lower_end].is_ascii_lowercase() {
192 lower_end += 1;
193 }
194 if lower_end > upper_end {
195 words.push(characters[start..lower_end].iter().collect());
196 index = lower_end;
197 } else {
198 index = start + 1;
199 }
200 }
201 words
202}
203
204fn title_case(text: &str) -> String {
207 let mut out = String::new();
208 let mut previous_letter = false;
209 for character in text.chars() {
210 if character.is_alphabetic() {
211 if previous_letter {
212 out.extend(character.to_lowercase());
213 } else {
214 out.extend(character.to_uppercase());
215 }
216 previous_letter = true;
217 } else {
218 out.push(character);
219 previous_letter = false;
220 }
221 }
222 out
223}
224
225fn notated(part: &Stream, options: &BrailleOptions) -> Result<Stream> {
228 let mut part = part.clone();
229 if !options.in_place {
230 crate::makenotation::make_part_notation_keeping_spanners(&mut part, false)?;
231 }
232 Ok(part)
233}
234
235pub fn part_to_braille(part: &Stream, options: &BrailleOptions) -> Result<String> {
241 let part = notated(part, options)?;
242 let mut segments = find_segments(&part, None, options.segment);
243 let mut written = Vec::new();
244 for segment in &mut segments {
245 let braille = segment.transcribe()?;
246 written.push(if options.debug {
247 segment.english()
248 } else {
249 braille
250 });
251 }
252 Ok(written.join("\n"))
253}
254
255pub fn measure_to_braille(measure: &Stream, options: &BrailleOptions) -> Result<String> {
262 let mut measure = measure.clone();
263 if !options.in_place {
264 crate::makenotation::make_part_notation_keeping_spanners(&mut measure, false)?;
265 }
266 let mut part = Stream::with_kind(StreamKind::Part);
267 part.push(measure);
268 part_to_braille(
269 &part,
270 &BrailleOptions {
271 in_place: true,
272 ..*options
273 },
274 )
275}
276
277pub fn keyboard_parts_to_braille(
284 upper: &Stream,
285 lower: &Stream,
286 options: &BrailleOptions,
287) -> Result<String> {
288 let upper = notated(upper, options)?;
289 let lower = notated(lower, options)?;
290 let right = find_segments(&upper, Some(Hand::Right), options.segment);
291 let left = find_segments(&lower, Some(Hand::Left), options.segment);
292 let mut written = Vec::new();
293 for (right, left) in right.into_iter().zip(left) {
294 let mut grand = BrailleGrandSegment {
295 groupings: right.groupings.into_iter().chain(left.groupings).collect(),
296 line_length: options.segment.max_line_length,
297 right_contexts: right.contexts,
298 left_contexts: left.contexts,
299 };
300 let braille = grand.transcribe()?;
301 written.push(if options.debug {
302 grand.english()
303 } else {
304 braille
305 });
306 }
307 Ok(written.join("\n"))
308}
309
310pub fn element_to_braille(element: StreamElement, options: &BrailleOptions) -> Result<String> {
317 if element.as_stream().is_some() {
318 return Err(Error::Notation(
319 "Stream cannot be translated to Braille.".to_string(),
320 ));
321 }
322 let mut measure = Stream::with_kind(StreamKind::Measure);
323 measure.push(element);
324 let mut part = Stream::with_kind(StreamKind::Part);
325 part.push(measure);
326 let mut segment = options.segment;
327 segment.show_first_measure_number = false;
328 segment.show_heading = false;
329 part_to_braille(
330 &part,
331 &BrailleOptions {
332 in_place: true,
333 segment,
334 ..*options
335 },
336 )
337}