1use regex::Regex;
2use std::{fmt, str::FromStr};
3
4use crate::{Chord, Error, Result, StaffText, TimeSignature};
5
6#[derive(Debug, Eq, PartialEq, Clone)]
8pub enum ProgressionElement {
9 SingleBarLine,
10 OpeningDoubleBarLine,
11 ClosingDoubleBarLine,
12 OpeningRepeatBarLine,
13 ClosingRepeatBarLine,
14 FinalThickDoubleBarLine,
15 SmallChord,
16 LargeChord,
17 Chord(Chord),
18 AlternateChord(Chord),
19 NoChord,
20 RepeatOneMeasure,
21 RepeatTwoMeasures,
22 TimeSignature(TimeSignature),
23 Section(String),
25 Verse,
26 Intro,
27 Segno,
28 Coda,
29 Fermata,
30 Ending(u8),
32 StaffText(StaffText),
33 VerticalSpace(u8),
34 Divider,
35 Slash,
36}
37
38impl fmt::Display for ProgressionElement {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 use ProgressionElement::*;
41
42 match self {
43 SingleBarLine => write!(f, "|"),
44 OpeningDoubleBarLine => write!(f, "["),
45 ClosingDoubleBarLine => write!(f, "]"),
46 OpeningRepeatBarLine => write!(f, "{{"),
47 ClosingRepeatBarLine => write!(f, "}}"),
48 FinalThickDoubleBarLine => write!(f, "Z"),
49 Chord(chord) => write!(f, "{} ", chord),
50 AlternateChord(chord) => write!(f, "({}) ", chord),
51 NoChord => write!(f, "n"),
52 TimeSignature(ts) => write!(f, "{ts}"),
53 Section(rm) => write!(f, "*{}", rm),
54 Verse => write!(f, "*V"),
55 Intro => write!(f, "*i"),
56 Segno => write!(f, "S"),
57 Coda => write!(f, "Q"),
58 Fermata => write!(f, "f"),
59 Ending(ending) => write!(f, "N{ending}"),
60 StaffText(text) => write!(f, "{text}"),
61 VerticalSpace(n) => write!(f, "{}", "Y".repeat((*n).into())),
62 Divider => write!(f, ","),
63 Slash => write!(f, "p"),
64 SmallChord => write!(f, "s"),
65 LargeChord => write!(f, "l"),
66 RepeatOneMeasure => write!(f, " x "),
67 RepeatTwoMeasures => write!(f, " r"),
68 }
69 }
70}
71
72#[derive(Debug, PartialEq, Clone)]
74pub struct Progression(Vec<ProgressionElement>);
75
76impl Progression {
77 pub fn new(elements: Vec<ProgressionElement>) -> Self {
79 Progression(elements)
80 }
81
82 pub fn elements(&self) -> &Vec<ProgressionElement> {
84 &self.0
85 }
86
87 pub fn elements_mut(&mut self) -> &mut Vec<ProgressionElement> {
89 &mut self.0
90 }
91}
92
93impl fmt::Display for Progression {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 let s: String = self.0.iter().map(|e| e.to_string()).collect();
96 write!(f, "{s}")
97 }
98}
99
100impl FromStr for Progression {
101 type Err = Error;
102
103 fn from_str(s: &str) -> Result<Self> {
104 parse_irealbook_progression(s)
105 }
106}
107
108pub fn parse_irealbook_progression(progression: &str) -> Result<Progression> {
118 let chord_regex = Regex::new(
119 r"(?x)
120 T(?:44|34|24|54|64|74|22|32|58|68|78|98|12) | # Match optional time signature
121 \| | # single bar line
122 \[ | # opening double bar line
123 \] | # closing double bar line
124 \{ | # opening repeat bar line
125 \} | # closing repeat bar line
126 Z | # final thick double bar line
127 \*[A-DVfi] | # rehearsal marks / section
128 S | # Segno
129 Q | # Coda
130 f | # Fermata
131 N[0-3] | # Endings
132 x | # repeat one measure
133 r | # repeat two measures
134 s | l | # small / large chord
135 n | # 'No Chord' symbol
136 (?: # chord symbol
137 \(? # Match optional opening parenthesis for alternate chords
138 [A-G](?:b|♭|\#|♯)? # Match root
139 (?:5|2|add9|\+|o|h|sus|\^7|\-7|7sus|h7|o7|\^9|\^13|6|69|\^7\#11|\^9\#11|\^7\#5|
140 \-6|\-69|\-\^7|\-\^9|-9|-11|-7b5|h9|-b6|-\#5|9|7b9|7\#9|7\#11|
141 7b5|7\#5|9\#11|9b5|9\#5|7b13|7\#9\#5|7\#9b5|7\#9\#11|7b9\#11|7b9b5|7b9\#5|
142 7b9\#9|7b9b13|7alt|13|13\#11|13b9|13\#9|7b9sus|7susadd3|9sus|13sus|7b13sus|11|\^|-|7)?
143 (?:/[A-G](?:b|♭|\#|♯)?)? # Match optional inversion
144 \)? # Match optional closing parenthesis for alternate chords
145 ) |
146 <(?:\*\d{2})?[^>]*> | # staff text (including specific phrases and repeat count)
147 YYY | YY | Y | # vertical space
148 , | # Divider
149 p # Slash
150 ",
151 )
152 .unwrap();
153
154 let mut result = Vec::new();
155
156 for capture in chord_regex.captures_iter(progression) {
157 use ProgressionElement::*;
158
159 let symbol_str = capture.get(0).ok_or(Error::InvalidProgression)?.as_str();
160 let progression_element = match symbol_str {
161 _ if symbol_str.starts_with('T') => TimeSignature(symbol_str.parse()?),
162 "|" => SingleBarLine,
163 "[" => OpeningDoubleBarLine,
164 "]" => ClosingDoubleBarLine,
165 "{" => OpeningRepeatBarLine,
166 "}" => ClosingRepeatBarLine,
167 "Z" => FinalThickDoubleBarLine,
168 "x" => RepeatOneMeasure,
169 "r" => RepeatTwoMeasures,
170 "s" => SmallChord,
171 "l" => LargeChord,
172 "n" => NoChord,
173 _ if symbol_str.starts_with('*') => {
174 let section = &symbol_str[1..];
175 match section {
176 "V" => Verse,
177 "i" => Intro,
178 _ => Section(section.to_owned()),
179 }
180 }
181 "S" => Segno,
182 "Q" => Coda,
183 "f" => Fermata,
184 _ if symbol_str.starts_with('N') => {
185 let n = symbol_str[1..].to_owned();
186 Ending(n.parse().map_err(|_| Error::InvalidProgression)?)
187 }
188 _ if symbol_str.starts_with('<') => StaffText(symbol_str.parse()?),
189 "YYY" => VerticalSpace(3),
190 "YY" => VerticalSpace(2),
191 "Y" => VerticalSpace(1),
192 "," => Divider,
193 "p" => Slash,
194 _ if symbol_str.starts_with('(') => {
195 let chord = symbol_str[1..symbol_str.len() - 1].to_owned();
196 AlternateChord(chord.parse()?)
197 }
198 _ => Chord(symbol_str.parse()?),
199 };
200 result.push(progression_element);
201 }
202
203 Ok(Progression(result))
204}