Skip to main content

music21_rs/
repeat.rs

1//! Words and signs telling a player where to go next: music21's repeat
2//! expressions, `Segno`, `Coda`, `Fine`, *Da Capo* and the rest.
3//!
4//! The barline repeats and the endings are [`crate::bar`]'s; these are the
5//! marks written above the staff.
6
7use crate::display::Drawn;
8use crate::notation::Placement;
9
10/// Which of music21's repeat expressions a mark is.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub enum RepeatExpressionKind {
14    /// The place a *to Coda* jumps to: music21's `Coda`.
15    Coda,
16    /// The sign a *Dal Segno* goes back to: music21's `Segno`.
17    Segno,
18    /// Where the piece ends after a jump back: music21's `Fine`.
19    Fine,
20    /// Back to the start: music21's `DaCapo`.
21    DaCapo,
22    /// Back to the start and on to *Fine*: music21's `DaCapoAlFine`.
23    DaCapoAlFine,
24    /// Back to the start and on to the coda: music21's `DaCapoAlCoda`.
25    DaCapoAlCoda,
26    /// On to the sign: music21's `AlSegno`.
27    AlSegno,
28    /// Back to the sign: music21's `DalSegno`.
29    DalSegno,
30    /// Back to the sign and on to *Fine*: music21's `DalSegnoAlFine`.
31    DalSegnoAlFine,
32    /// Back to the sign and on to the coda: music21's `DalSegnoAlCoda`.
33    DalSegnoAlCoda,
34}
35
36impl RepeatExpressionKind {
37    /// Every kind, in the order music21's module defines them.
38    pub const ALL: [Self; 10] = [
39        Self::Coda,
40        Self::Segno,
41        Self::Fine,
42        Self::DaCapo,
43        Self::DaCapoAlFine,
44        Self::DaCapoAlCoda,
45        Self::AlSegno,
46        Self::DalSegno,
47        Self::DalSegnoAlFine,
48        Self::DalSegnoAlCoda,
49    ];
50
51    /// music21's class name for the kind.
52    pub fn class_name(self) -> &'static str {
53        match self {
54            Self::Coda => "Coda",
55            Self::Segno => "Segno",
56            Self::Fine => "Fine",
57            Self::DaCapo => "DaCapo",
58            Self::DaCapoAlFine => "DaCapoAlFine",
59            Self::DaCapoAlCoda => "DaCapoAlCoda",
60            Self::AlSegno => "AlSegno",
61            Self::DalSegno => "DalSegno",
62            Self::DalSegnoAlFine => "DalSegnoAlFine",
63            Self::DalSegnoAlCoda => "DalSegnoAlCoda",
64        }
65    }
66
67    /// The kind music21 names by this class name.
68    pub fn from_class_name(name: &str) -> Option<Self> {
69        Self::ALL.into_iter().find(|kind| kind.class_name() == name)
70    }
71
72    /// The ways the mark may be written, the first being how it is written
73    /// when nothing says otherwise: music21's `_textAlternatives`.
74    pub fn text_alternatives(self) -> &'static [&'static str] {
75        match self {
76            Self::Coda => &["Coda", "to Coda", "al Coda"],
77            Self::Segno => &["Segno"],
78            Self::Fine => &["fine"],
79            Self::DaCapo => &["Da Capo", "D.C."],
80            Self::DaCapoAlFine => &["Da Capo al fine", "D.C. al fine"],
81            Self::DaCapoAlCoda => &["Da Capo al Coda", "D.C. al Coda"],
82            Self::AlSegno => &["al Segno"],
83            Self::DalSegno => &["Dal Segno", "D.S."],
84            Self::DalSegnoAlFine => &["Dal Segno al fine", "D.S. al fine"],
85            Self::DalSegnoAlCoda => &["Dal Segno al Coda", "D.S. al Coda"],
86        }
87    }
88
89    /// Whether the mark is a place to jump to rather than an instruction to
90    /// jump: music21's `RepeatExpressionMarker` against
91    /// `RepeatExpressionCommand`.
92    pub fn is_marker(self) -> bool {
93        matches!(self, Self::Coda | Self::Segno | Self::Fine)
94    }
95
96    /// How the words are justified: an instruction to jump stands at the
97    /// end of the bar it acts on, and so does *Fine*.
98    pub fn justify(self) -> &'static str {
99        match self {
100            Self::Coda | Self::Segno => "center",
101            _ => "right",
102        }
103    }
104
105    /// Whether the mark has a sign of its own: only the coda and the segno
106    /// do.
107    pub fn has_symbol(self) -> bool {
108        matches!(self, Self::Coda | Self::Segno)
109    }
110}
111
112/// A word or sign telling a player where to go next: music21's
113/// `RepeatExpression`.
114///
115/// ```
116/// use music21_rs::repeat::{RepeatExpression, RepeatExpressionKind};
117///
118/// let fine = RepeatExpression::new(RepeatExpressionKind::Fine);
119/// assert_eq!(fine.text(), "fine");
120/// assert!(!fine.use_symbol());
121///
122/// let coda = RepeatExpression::with_text(RepeatExpressionKind::Coda, "to Coda");
123/// assert_eq!(coda.text(), "to Coda");
124/// assert!(!coda.use_symbol());
125/// ```
126#[derive(Clone, Debug, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[must_use]
129pub struct RepeatExpression {
130    kind: RepeatExpressionKind,
131    text: String,
132    use_symbol: bool,
133    #[cfg_attr(feature = "serde", serde(default))]
134    placement: Drawn<Option<Placement>>,
135}
136
137impl RepeatExpression {
138    /// A mark of a kind, written as music21 writes it when nothing says
139    /// otherwise: a coda and a segno as their signs, everything else in
140    /// words.
141    pub fn new(kind: RepeatExpressionKind) -> Self {
142        Self {
143            kind,
144            text: kind.text_alternatives()[0].to_string(),
145            use_symbol: kind.has_symbol(),
146            placement: Drawn(None),
147        }
148    }
149
150    /// A mark of a kind written in these words, where they are one of its
151    /// ways of being written, and as it is written by default otherwise:
152    /// music21's `text` argument. A coda given words is written in them
153    /// rather than as its sign. A segno and *fine* are always written their
154    /// own way.
155    pub fn with_text(kind: RepeatExpressionKind, text: &str) -> Self {
156        let mut mark = Self::new(kind);
157        let takes_text = !matches!(
158            kind,
159            RepeatExpressionKind::Segno | RepeatExpressionKind::Fine
160        );
161        if takes_text && mark.is_valid_text(text) {
162            mark.text = text.to_string();
163            mark.use_symbol = false;
164        }
165        mark
166    }
167
168    /// Which kind of mark this is.
169    pub fn kind(&self) -> RepeatExpressionKind {
170        self.kind
171    }
172
173    /// The words the mark is written in: music21's `getText`.
174    pub fn text(&self) -> &str {
175        &self.text
176    }
177
178    /// Writes the mark in other words, whatever they are: music21's
179    /// `setText`.
180    pub fn set_text(&mut self, text: impl Into<String>) {
181        self.text = text.into();
182    }
183
184    /// Whether these words are one of the ways this kind of mark is
185    /// written, ignoring case, spaces and full stops: music21's
186    /// `isValidText`.
187    pub fn is_valid_text(&self, text: &str) -> bool {
188        let strip = |text: &str| -> String {
189            text.trim()
190                .chars()
191                .filter(|character| *character != ' ' && *character != '.')
192                .flat_map(char::to_lowercase)
193                .collect()
194        };
195        let text = strip(text);
196        self.kind
197            .text_alternatives()
198            .iter()
199            .any(|candidate| strip(candidate) == text)
200    }
201
202    /// Whether the mark is drawn as its sign rather than in words.
203    pub fn use_symbol(&self) -> bool {
204        self.use_symbol
205    }
206
207    /// Says whether the mark is drawn as its sign. Only a coda and a segno
208    /// have one to draw.
209    pub fn set_use_symbol(&mut self, use_symbol: bool) {
210        self.use_symbol = use_symbol;
211    }
212
213    /// Which side of the staff the words are written on, where a score says.
214    pub fn placement(&self) -> Option<Placement> {
215        self.placement.0
216    }
217
218    /// Says which side of the staff the words are written on.
219    pub fn set_placement(&mut self, placement: Option<Placement>) {
220        self.placement.0 = placement;
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn a_mark_takes_only_its_own_words() {
230        // music21: DaCapo('D.C.') keeps the words, DaCapo('D.S.') does not.
231        let da_capo = RepeatExpression::with_text(RepeatExpressionKind::DaCapo, "D.C.");
232        assert_eq!(da_capo.text(), "D.C.");
233        let wrong = RepeatExpression::with_text(RepeatExpressionKind::DaCapo, "D.S.");
234        assert_eq!(wrong.text(), "Da Capo");
235        assert!(da_capo.is_valid_text("da capo"));
236    }
237
238    #[test]
239    fn a_coda_is_a_sign_until_it_is_given_words() {
240        assert!(RepeatExpression::new(RepeatExpressionKind::Coda).use_symbol());
241        assert!(!RepeatExpression::with_text(RepeatExpressionKind::Coda, "al Coda").use_symbol());
242        assert!(RepeatExpression::new(RepeatExpressionKind::Segno).use_symbol());
243        assert_eq!(RepeatExpressionKind::Fine.justify(), "right");
244        assert_eq!(RepeatExpressionKind::Segno.justify(), "center");
245    }
246}