Skip to main content

music21/braille/
translate.rs

1//! Scores, parts and measures in braille: music21's `braille.translate`.
2
3use 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/// How music21's braille translation is asked to write: its keyword
13/// arguments.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15#[non_exhaustive]
16pub struct BrailleOptions {
17    /// Whether the stream is written as it stands, or first given the
18    /// notation it leaves unsaid as music21's `makeNotation` gives it.
19    pub in_place: bool,
20    /// Whether each segment is written as music21's English listing of its
21    /// groupings instead of braille.
22    pub debug: bool,
23    /// The options each segment is written with.
24    pub segment: SegmentOptions,
25}
26
27/// A score, part or measure in braille, as music21's `objectToBraille`
28/// writes one: a score part by part, two piano staves side by side, a part
29/// segment by segment.
30///
31/// # Errors
32///
33/// A stream of another kind, or music that music21 cannot write.
34pub 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
67/// A score in braille: its metadata, then each part, a piano's two staves
68/// together: music21's `scoreToBraille`.
69///
70/// # Errors
71///
72/// Music music21 cannot write.
73pub 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
114/// A part of a score with the spanners music21 keeps in it: those of the
115/// score that start and end among the part's elements, braille reading a
116/// slur by its ends alone. music21 keeps every spanner of a part written on
117/// several staves on its first staff, so a later staff has none. `start` is
118/// where the part's leaves start among the score's.
119fn 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
146/// A score's metadata, a line each sorted, as `Name: value` or its braille:
147/// music21's `metadataToString`.
148///
149/// # Errors
150///
151/// A character braille has no sign for.
152pub 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
179/// The words of a camel-cased name, as music21 finds them with
180/// `([A-Z]*[a-z]+)`.
181fn 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
205/// Text in title case as Python's `str.title` writes it: each run of
206/// letters capitalised.
207fn 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
226/// A copy of a part with the notation it leaves unsaid made, as music21's
227/// `makeNotation(cautionaryNotImmediateRepeat=False)` makes it.
228fn 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
236/// A part in braille, segment by segment: music21's `partToBraille`.
237///
238/// # Errors
239///
240/// Music music21 cannot write.
241pub 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
256/// A measure in braille, as the one measure of a part with no heading or
257/// measure number unless asked: music21's `measureToBraille`.
258///
259/// # Errors
260///
261/// Music music21 cannot write.
262pub 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        // music21's `Measure.makeNotation(cautionaryNotImmediateRepeat=False)`,
267        // which is the *measure's* notation and not a part's: running a
268        // part's steps over a lone measure cuts it into measures again and
269        // the notes end up a level deeper, where nothing transcribing a
270        // part looks for them.
271        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
286/// A piano's two staves in braille, side by side measure by measure:
287/// music21's `keyboardPartsToBraille`.
288///
289/// # Errors
290///
291/// Music music21 cannot write.
292pub 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
319/// An element that is not a stream, in braille as the one element of a
320/// measure: music21's `objectToBraille` given one.
321///
322/// # Errors
323///
324/// Music music21 cannot write.
325pub 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    /// Two measures of one whole note, a key of two sharps in the first.
356    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    /// music21 36d9144c0: a key signature standing alone heads the two
374    /// hands as a key signature, where it used to be read as a meter.
375    #[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    /// music21's `getRawSegments` example: `3/4 c4 c c e e e g g g c'2.`
384    /// with a division in a measure, transcribed.
385    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    /// A division starts a new segment there, in the middle of a measure
402    /// or not at all where the segment is empty; an optional one only once
403    /// the segment holds 48 elements.
404    #[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    /// One measure of a whole note, with a key of two sharps and four-four
416    /// where `signed`.
417    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    /// The signatures of either hand alone head the two hands.
435    #[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    /// Four eighths, each with `counts` staccatos, as one measure of a
450    /// part, cut into segments.
451    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    /// Four staccatos in a row: two on the first note, none on those
480    /// between and one on the last; three in a row are left as they are.
481    #[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    /// A chord's direction set by a clef carries into the next segment;
489    /// with no clef, chords are written from the top down.
490    #[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}