1use crate::error::{Error, Result};
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub enum BarlineType {
15 #[default]
17 Regular,
18 Dotted,
20 Dashed,
22 Heavy,
24 Double,
26 Final,
28 HeavyLight,
30 HeavyHeavy,
32 Tick,
34 Short,
36 None,
38}
39
40impl BarlineType {
41 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 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 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub enum RepeatDirection {
103 Start,
105 End,
107}
108
109impl RepeatDirection {
110 pub fn as_str(self) -> &'static str {
112 match self {
113 Self::Start => "start",
114 Self::End => "end",
115 }
116 }
117}
118
119#[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 pub fn new(bar_type: BarlineType) -> Self {
132 Self {
133 bar_type,
134 repeat: None,
135 }
136 }
137
138 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 pub fn bar_type(&self) -> BarlineType {
153 self.bar_type
154 }
155
156 pub fn set_bar_type(&mut self, bar_type: BarlineType) {
158 self.bar_type = bar_type;
159 }
160
161 pub fn repeat_direction(&self) -> Option<RepeatDirection> {
163 self.repeat.map(|(direction, _)| direction)
164 }
165
166 pub fn repeat_times(&self) -> Option<u32> {
168 self.repeat.and_then(|(_, times)| times)
169 }
170
171 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#[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 pub fn new(numbers: Vec<u32>, starts: bool, stops: bool) -> Self {
211 Self {
212 numbers,
213 starts,
214 stops,
215 }
216 }
217
218 pub fn numbers(&self) -> &[u32] {
220 &self.numbers
221 }
222
223 pub fn starts(&self) -> bool {
225 self.starts
226 }
227
228 pub fn stops(&self) -> bool {
230 self.stops
231 }
232
233 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 #[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 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}