Skip to main content

acorde_core/model/
repeat.rs

1use super::notation::Barline;
2use super::score::Score;
3
4/// Returns the ordered list of physical measure indices to play back,
5/// expanding repeat sections, volta brackets, and navigation marks.
6///
7/// Handles:
8/// - `RepeatStart` / `RepeatEnd` / `RepeatBoth` barlines
9/// - First/second-ending volta brackets
10/// - `DaCapo`, `DaCapoAlFine`, `DaCapoAlCoda`
11/// - `DalSegno`, `DalSegnoAlFine`, `DalSegnoAlCoda`
12/// - `Fine`, `ToCoda`, `Coda` markers
13///
14/// Limitations: single-level repeats; during a D.C./D.S. return pass, all
15/// barline repeats and voltas are ignored (safe linear fallback).
16pub fn measure_sequence(score: &Score) -> Vec<usize> {
17    let measures = match score.parts.first()
18        .and_then(|p| p.staves.first())
19        .map(|s| &s.measures)
20    {
21        Some(m) => m,
22        None => return vec![],
23    };
24
25    let n = measures.len();
26
27    // Pre-scan: locate Fine, Segno, and Coda markers.
28    let mut segno_idx: Option<usize> = None;
29    let mut coda_idx:  Option<usize> = None;
30    for (j, m) in measures.iter().enumerate() {
31        match m.navigation.as_deref() {
32            Some("Segno") => segno_idx = Some(j),
33            Some("Coda")  => coda_idx  = Some(j),
34            _ => {}
35        }
36    }
37
38    let mut seq = Vec::with_capacity(n + 4);
39    let mut i = 0usize;
40    let mut repeat_start = 0usize;
41    let mut volta_pass: u8 = 1;
42    // Navigation-pass state (D.C./D.S. return).
43    let mut in_nav_pass = false;
44    let mut nav_fine    = false; // stop at Fine
45    let mut nav_coda    = false; // jump to Coda at ToCoda
46
47    while i < n {
48        let m = &measures[i];
49
50        // Volta / barline-repeat handling is suspended during a navigation pass.
51        if !in_nav_pass {
52            // Skip volta blocks that don't belong to the current pass.
53            if let Some(volta) = &m.volta
54                && volta.number != volta_pass
55                && (volta.kind == "begin" || volta.kind == "begin_end")
56            {
57                if volta.kind == "begin_end" {
58                    i += 1;
59                    continue;
60                } else {
61                    i += 1;
62                    while i < n {
63                        if let Some(v) = &measures[i].volta
64                            && v.kind == "end"
65                        {
66                            i += 1;
67                            break;
68                        }
69                        i += 1;
70                    }
71                    continue;
72                }
73            }
74        }
75
76        seq.push(i);
77
78        // Navigation-pass checks (Fine / ToCoda).
79        if in_nav_pass {
80            if nav_fine && matches!(m.navigation.as_deref(), Some("Fine")) {
81                break;
82            }
83            if nav_coda
84                && matches!(m.navigation.as_deref(), Some("ToCoda"))
85                && let Some(ci) = coda_idx
86            {
87                i = ci;
88                in_nav_pass = false;
89                continue;
90            }
91        } else {
92            // Check for D.C./D.S. marks.
93            let jump: Option<(usize, bool, bool)> = match m.navigation.as_deref() {
94                Some("DaCapo")         => Some((0, false, false)),
95                Some("DaCapoAlFine")   => Some((0, true,  false)),
96                Some("DaCapoAlCoda")   => Some((0, false, true)),
97                Some("DalSegno")       => segno_idx.map(|s| (s, false, false)),
98                Some("DalSegnoAlFine") => segno_idx.map(|s| (s, true,  false)),
99                Some("DalSegnoAlCoda") => segno_idx.map(|s| (s, false, true)),
100                _ => None,
101            };
102            if let Some((target, fine, coda)) = jump {
103                in_nav_pass = true;
104                nav_fine    = fine;
105                nav_coda    = coda;
106                i = target;
107                continue;
108            }
109
110            // Barline-repeat handling (only outside navigation pass).
111            if matches!(m.barline_left, Barline::RepeatStart | Barline::RepeatBoth) {
112                repeat_start = i;
113            }
114
115            match m.barline_right {
116                Barline::RepeatEnd | Barline::RepeatBoth => {
117                    if volta_pass == 1 {
118                        volta_pass = 2;
119                        i = repeat_start;
120                    } else {
121                        volta_pass = 1;
122                        if matches!(m.barline_right, Barline::RepeatBoth) {
123                            repeat_start = i + 1;
124                        }
125                        i += 1;
126                    }
127                    continue;
128                }
129                _ => {}
130            }
131        }
132
133        i += 1;
134    }
135
136    seq
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::model::score::{Measure, Score, VoltaBracket};
143    use crate::model::notation::Barline;
144
145    fn score_with_measures(measures: Vec<Measure>) -> Score {
146        let mut score = Score::new("T", 120, 4, 4, 0, 0);
147        score.parts[0].staves[0].measures = measures;
148        score
149    }
150
151    fn plain(n: u32) -> Measure {
152        let mut m = Measure::empty(4, 4);
153        m.number = n;
154        m
155    }
156
157    fn with_nav(n: u32, nav: &str) -> Measure {
158        let mut m = plain(n);
159        m.navigation = Some(nav.to_string());
160        m
161    }
162
163    #[test]
164    fn no_repeat_is_linear() {
165        let score = score_with_measures(vec![plain(1), plain(2), plain(3), plain(4)]);
166        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 3]);
167    }
168
169    #[test]
170    fn simple_repeat_doubles_section() {
171        let mut m0 = plain(1);
172        m0.barline_left = Barline::RepeatStart;
173        let m1 = plain(2);
174        let mut m2 = plain(3);
175        m2.barline_right = Barline::RepeatEnd;
176        let m3 = plain(4);
177
178        let score = score_with_measures(vec![m0, m1, m2, m3]);
179        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0, 1, 2, 3]);
180    }
181
182    #[test]
183    fn volta_first_pass_plays_ending_1() {
184        let mut m0 = plain(1);
185        m0.barline_left = Barline::RepeatStart;
186        let m1 = plain(2);
187        let mut m2 = plain(3);
188        m2.volta = Some(VoltaBracket { number: 1, kind: "begin_end".into() });
189        m2.barline_right = Barline::RepeatEnd;
190        let mut m3 = plain(4);
191        m3.volta = Some(VoltaBracket { number: 2, kind: "begin_end".into() });
192
193        let score = score_with_measures(vec![m0, m1, m2, m3]);
194        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0, 1, 3]);
195    }
196
197    #[test]
198    fn volta_second_pass_skips_ending_1() {
199        let mut m0 = plain(1);
200        m0.barline_left = Barline::RepeatStart;
201        let m1 = plain(2);
202        let mut m2 = plain(3);
203        m2.volta = Some(VoltaBracket { number: 1, kind: "begin_end".into() });
204        m2.barline_right = Barline::RepeatEnd;
205        let mut m3 = plain(4);
206        m3.volta = Some(VoltaBracket { number: 2, kind: "begin_end".into() });
207        let m4 = plain(5);
208
209        let score = score_with_measures(vec![m0, m1, m2, m3, m4]);
210        let seq = measure_sequence(&score);
211        assert_eq!(seq, vec![0, 1, 2, 0, 1, 3, 4]);
212        let second_pass_third = seq[5];
213        assert_eq!(second_pass_third, 3);
214    }
215
216    // ── Navigation marks ─────────────────────────────────────────────────────
217
218    #[test]
219    fn da_capo_jumps_to_start() {
220        // [A][B][C D.C.] → A B C A B C ...
221        // The second D.C. encounter is ignored (in_nav_pass=true) so we play A B C straight.
222        let score = score_with_measures(vec![
223            plain(1), plain(2), with_nav(3, "DaCapo"),
224        ]);
225        // Pass: 0,1,2 → D.C. → in_nav_pass, jump to 0 → 0,1,2 → i=3 → done
226        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0, 1, 2]);
227    }
228
229    #[test]
230    fn da_capo_al_fine_stops_at_fine() {
231        // [A Fine][B][C D.C.alFine] → A B C A (stop at Fine=0)
232        let mut m0 = plain(1);
233        m0.navigation = Some("Fine".into());
234        let score = score_with_measures(vec![
235            m0, plain(2), with_nav(3, "DaCapoAlFine"),
236        ]);
237        // Pass: 0,1,2 → D.C.alFine → jump to 0 → push 0 → Fine found → break
238        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0]);
239    }
240
241    #[test]
242    fn dal_segno_al_coda_jumps_to_coda() {
243        // [A][B Segno][C][D ToCoda][E D.S.alCoda][F Coda][G]
244        // Pass: 0,1,2,3,4 → D.S.alCoda → jump to segno(1) → in_nav_pass
245        // → push 1,2,3 → ToCoda at 3 → jump to coda(5) → push 5,6
246        let mut m1 = plain(2);
247        m1.navigation = Some("Segno".into());
248        let mut m3 = plain(4);
249        m3.navigation = Some("ToCoda".into());
250        let mut m5 = plain(6);
251        m5.navigation = Some("Coda".into());
252        let score = score_with_measures(vec![
253            plain(1), m1, plain(3), m3, with_nav(5, "DalSegnoAlCoda"), m5, plain(7),
254        ]);
255        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 3, 4, 1, 2, 3, 5, 6]);
256    }
257}