Skip to main content

music21_rs/
bar.rs

1//! Barlines: music21's `bar` module.
2//!
3//! A measure has a barline at each end, and a repeat sign is a barline that
4//! also says which way the music repeats. music21 keeps them as the
5//! measure's `leftBarline` and `rightBarline`, which is where
6//! [`crate::Stream::left_barline`] and [`crate::Stream::right_barline`] keep
7//! them here.
8
9use crate::error::{Error, Result};
10
11/// How a barline is drawn: music21's `bar.barTypeList`.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub enum BarlineType {
15    /// A single thin line.
16    #[default]
17    Regular,
18    /// A dotted line.
19    Dotted,
20    /// A dashed line.
21    Dashed,
22    /// A single thick line.
23    Heavy,
24    /// Two thin lines.
25    Double,
26    /// A thin line and a thick one, which ends a piece.
27    Final,
28    /// A thick line and a thin one.
29    HeavyLight,
30    /// Two thick lines.
31    HeavyHeavy,
32    /// A short stroke through the top line.
33    Tick,
34    /// A line through the middle of the staff only.
35    Short,
36    /// No line at all.
37    None,
38}
39
40impl BarlineType {
41    /// Every type, in music21's order.
42    pub const ALL: [BarlineType; 11] = [
43        Self::Regular,
44        Self::Dotted,
45        Self::Dashed,
46        Self::Heavy,
47        Self::Double,
48        Self::Final,
49        Self::HeavyLight,
50        Self::HeavyHeavy,
51        Self::Tick,
52        Self::Short,
53        Self::None,
54    ];
55
56    /// music21's name for the type.
57    pub fn as_str(self) -> &'static str {
58        match self {
59            Self::Regular => "regular",
60            Self::Dotted => "dotted",
61            Self::Dashed => "dashed",
62            Self::Heavy => "heavy",
63            Self::Double => "double",
64            Self::Final => "final",
65            Self::HeavyLight => "heavy-light",
66            Self::HeavyHeavy => "heavy-heavy",
67            Self::Tick => "tick",
68            Self::Short => "short",
69            Self::None => "none",
70        }
71    }
72
73    /// Reads a type by music21's name or by the MusicXML name music21
74    /// allows for two of them: music21's `standardizeBarType`.
75    pub fn from_name(name: &str) -> Result<Self> {
76        let name = name.to_lowercase();
77        let name = match name.as_str() {
78            "light-light" => "double",
79            "light-heavy" => "final",
80            other => other,
81        };
82        Self::ALL
83            .into_iter()
84            .find(|candidate| candidate.as_str() == name)
85            .ok_or_else(|| Error::Value(format!("cannot process style: {name}")))
86    }
87
88    /// The name MusicXML draws it by: music21's `musicXMLBarStyle`, which
89    /// is the type's own name but for `double` and `final`.
90    pub fn musicxml_bar_style(self) -> &'static str {
91        match self {
92            Self::Double => "light-light",
93            Self::Final => "light-heavy",
94            other => other.as_str(),
95        }
96    }
97}
98
99/// Which way a repeat sign sends the music.
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub enum RepeatDirection {
103    /// The passage to repeat starts here.
104    Start,
105    /// The passage to repeat ends here, and goes back.
106    End,
107}
108
109impl RepeatDirection {
110    /// music21's name for the direction.
111    pub fn as_str(self) -> &'static str {
112        match self {
113            Self::Start => "start",
114            Self::End => "end",
115        }
116    }
117}
118
119/// A barline: music21's `bar.Barline`, or its `bar.Repeat` where it is a
120/// repeat sign.
121#[derive(Clone, Debug, Default, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[must_use]
124pub struct Barline {
125    bar_type: BarlineType,
126    repeat: Option<(RepeatDirection, Option<u32>)>,
127}
128
129impl Barline {
130    /// A plain barline of a type.
131    pub fn new(bar_type: BarlineType) -> Self {
132        Self {
133            bar_type,
134            repeat: None,
135        }
136    }
137
138    /// A repeat sign, drawn as music21 draws one: `heavy-light` where a
139    /// repeat starts and `final` where it ends. `times` is how many times the
140    /// passage is played, where the score says.
141    pub fn repeat(direction: RepeatDirection, times: Option<u32>) -> Self {
142        Self {
143            bar_type: match direction {
144                RepeatDirection::Start => BarlineType::HeavyLight,
145                RepeatDirection::End => BarlineType::Final,
146            },
147            repeat: Some((direction, times)),
148        }
149    }
150
151    /// How the barline is drawn.
152    pub fn bar_type(&self) -> BarlineType {
153        self.bar_type
154    }
155
156    /// Changes how the barline is drawn.
157    pub fn set_bar_type(&mut self, bar_type: BarlineType) {
158        self.bar_type = bar_type;
159    }
160
161    /// Which way the music repeats, where this is a repeat sign.
162    pub fn repeat_direction(&self) -> Option<RepeatDirection> {
163        self.repeat.map(|(direction, _)| direction)
164    }
165
166    /// How many times the passage is played, where a repeat sign says.
167    pub fn repeat_times(&self) -> Option<u32> {
168        self.repeat.and_then(|(_, times)| times)
169    }
170
171    /// The words a repeat's count is written as, for a caller that wants
172    /// them over the staff: music21's `Repeat.getTextExpression`, which
173    /// writes the times between a prefix and a postfix -- `3x` by
174    /// default.
175    ///
176    /// music21 writes the count whatever it is, so a repeat that says no
177    /// count is written `Nonex`; this answers nothing instead, there
178    /// being nothing to say.
179    pub fn repeat_text_expression(
180        &self,
181        prefix: &str,
182        postfix: &str,
183    ) -> Option<crate::expressions::TextExpression> {
184        let times = self.repeat_times()?;
185        Some(crate::expressions::TextExpression::new(format!(
186            "{prefix}{times}{postfix}"
187        )))
188    }
189}
190
191/// Where a measure stands in an alternative ending: music21's
192/// `RepeatBracket`, the bracket drawn over the measures played the first
193/// time through, or the second.
194///
195/// music21 keeps the bracket as a spanner over the measures; a measure here
196/// says which ending it is in and whether the bracket opens or closes on it.
197#[derive(Clone, Debug, PartialEq, Eq)]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199#[must_use]
200pub struct Ending {
201    numbers: Vec<u32>,
202    starts: bool,
203    stops: bool,
204}
205
206impl Ending {
207    /// An ending played the times numbered, opening on this measure where
208    /// `starts` and closing on it where `stops`. Nought as the only number
209    /// is an ending that gives none.
210    pub fn new(numbers: Vec<u32>, starts: bool, stops: bool) -> Self {
211        Self {
212            numbers,
213            starts,
214            stops,
215        }
216    }
217
218    /// The times through the ending is played: music21's `numberRange`.
219    pub fn numbers(&self) -> &[u32] {
220        &self.numbers
221    }
222
223    /// Whether the bracket opens on this measure.
224    pub fn starts(&self) -> bool {
225        self.starts
226    }
227
228    /// Whether the bracket closes on this measure.
229    pub fn stops(&self) -> bool {
230        self.stops
231    }
232
233    /// The numbers as MusicXML writes them, `1,2`; an ending numbered
234    /// nought writes nothing.
235    pub fn number_text(&self) -> String {
236        let mut text = match self.numbers.first() {
237            Some(0) | None => String::new(),
238            Some(first) => first.to_string(),
239        };
240        for number in self.numbers.iter().skip(1) {
241            text.push(',');
242            text.push_str(&number.to_string());
243        }
244        text
245    }
246}
247
248#[cfg(test)]
249mod tests {
250
251    /// music21's own answers: `3x` by default, and what the prefix and
252    /// postfix make of it.
253    #[test]
254    fn a_repeats_count_is_written_as_music21_writes_it() {
255        let twice = Barline::repeat(RepeatDirection::End, Some(3));
256        let said = twice
257            .repeat_text_expression("", "x")
258            .expect("a count to write");
259        assert_eq!(said.content(), "3x");
260        let longer = twice
261            .repeat_text_expression("repeat ", " times")
262            .expect("a count to write");
263        assert_eq!(longer.content(), "repeat 3 times");
264        // A repeat that says no count has nothing to write.
265        assert!(
266            Barline::repeat(RepeatDirection::End, None)
267                .repeat_text_expression("", "x")
268                .is_none()
269        );
270    }
271    use super::*;
272
273    #[test]
274    fn two_types_have_musicxml_names_of_their_own() {
275        assert_eq!(BarlineType::Final.musicxml_bar_style(), "light-heavy");
276        assert_eq!(BarlineType::Double.musicxml_bar_style(), "light-light");
277        assert_eq!(BarlineType::Tick.musicxml_bar_style(), "tick");
278        assert_eq!(
279            BarlineType::from_name("light-heavy").unwrap(),
280            BarlineType::Final
281        );
282        assert_eq!(
283            BarlineType::from_name("Heavy-Light").unwrap(),
284            BarlineType::HeavyLight
285        );
286        assert!(BarlineType::from_name("wiggly").is_err());
287    }
288
289    #[test]
290    fn a_repeat_is_drawn_by_its_direction() {
291        let end = Barline::repeat(RepeatDirection::End, Some(3));
292        assert_eq!(end.bar_type(), BarlineType::Final);
293        assert_eq!(end.repeat_times(), Some(3));
294        let start = Barline::repeat(RepeatDirection::Start, None);
295        assert_eq!(start.bar_type(), BarlineType::HeavyLight);
296        assert_eq!(start.repeat_direction(), Some(RepeatDirection::Start));
297        assert_eq!(Barline::default().repeat_direction(), None);
298    }
299}