Skip to main content

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