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
18        .parts
19        .first()
20        .and_then(|p| p.staves.first())
21        .map(|s| &s.measures)
22    {
23        Some(m) => m,
24        None => return vec![],
25    };
26
27    let n = measures.len();
28
29    // Pre-scan: locate Fine, Segno, and Coda markers.
30    let mut segno_idx: Option<usize> = None;
31    let mut coda_idx: Option<usize> = None;
32    for (j, m) in measures.iter().enumerate() {
33        match m.navigation.as_deref() {
34            Some("Segno") => segno_idx = Some(j),
35            Some("Coda") => coda_idx = Some(j),
36            _ => {}
37        }
38    }
39
40    let mut seq = Vec::with_capacity(n + 4);
41    // Each repeat end and each D.C./D.S. mark jumps back once. Without this, marks that do not
42    // pair up (a stray first-ending end with its own repeat, a coda before its D.C.) sent
43    // playback round the same bars forever, growing the sequence until allocation failed.
44    let mut repeat_jumps = vec![0u8; n];
45    let mut jump_taken = vec![false; n];
46    // Backstop for any other unforeseen cycle: no real score plays a bar this many times.
47    let limit = n.saturating_mul(16).max(64);
48    let mut i = 0usize;
49    let mut repeat_start = 0usize;
50    let mut volta_pass: u8 = 1;
51    // Navigation-pass state (D.C./D.S. return).
52    let mut in_nav_pass = false;
53    let mut nav_fine = false; // stop at Fine
54    let mut nav_coda = false; // jump to Coda at ToCoda
55
56    while i < n && seq.len() < limit {
57        let m = &measures[i];
58
59        // Volta / barline-repeat handling is suspended during a navigation pass.
60        if !in_nav_pass {
61            // Skip volta blocks that don't belong to the current pass.
62            if let Some(volta) = &m.volta
63                && !volta.plays_on(volta_pass)
64                && (volta.kind == "begin" || volta.kind == "begin_end")
65            {
66                if volta.kind == "begin_end" {
67                    i += 1;
68                    continue;
69                } else {
70                    i += 1;
71                    while i < n {
72                        if let Some(v) = &measures[i].volta
73                            && v.kind == "end"
74                        {
75                            i += 1;
76                            break;
77                        }
78                        i += 1;
79                    }
80                    continue;
81                }
82            }
83        }
84
85        seq.push(i);
86
87        // Navigation-pass checks (Fine / ToCoda).
88        if in_nav_pass {
89            if nav_fine && matches!(m.navigation.as_deref(), Some("Fine")) {
90                break;
91            }
92            if nav_coda
93                && matches!(m.navigation.as_deref(), Some("ToCoda"))
94                && let Some(ci) = coda_idx
95            {
96                i = ci;
97                in_nav_pass = false;
98                continue;
99            }
100        } else {
101            // Check for D.C./D.S. marks.
102            let jump: Option<(usize, bool, bool)> = match m.navigation.as_deref() {
103                Some("DaCapo") => Some((0, false, false)),
104                Some("DaCapoAlFine") => Some((0, true, false)),
105                Some("DaCapoAlCoda") => Some((0, false, true)),
106                Some("DalSegno") => segno_idx.map(|s| (s, false, false)),
107                Some("DalSegnoAlFine") => segno_idx.map(|s| (s, true, false)),
108                Some("DalSegnoAlCoda") => segno_idx.map(|s| (s, false, true)),
109                _ => None,
110            };
111            if let Some((target, fine, coda)) = jump
112                && !std::mem::replace(&mut jump_taken[i], true)
113            {
114                in_nav_pass = true;
115                nav_fine = fine;
116                nav_coda = coda;
117                i = target;
118                continue;
119            }
120
121            // Barline-repeat handling (only outside navigation pass).
122            if matches!(m.barline_left, Barline::RepeatStart | Barline::RepeatBoth) {
123                // A new repeat section starts on its first pass even if the previous
124                // section's repeat end was skipped on an alternate-ending pass.
125                // Re-entering this same start after a jump keeps the current pass.
126                if repeat_start != i {
127                    volta_pass = 1;
128                }
129                repeat_start = i;
130            }
131
132            match m.barline_right {
133                Barline::RepeatEnd | Barline::RepeatBoth => {
134                    // Inside an ending, repeat until the last ending's pass is reached: a
135                    // `1, 2.` ending before a `3.` one plays the section three times.
136                    let last_pass = if m.volta.is_some() {
137                        let mut last = volta_pass;
138                        let mut j = repeat_start;
139                        while j < n && (j <= i || measures[j].volta.is_some()) {
140                            if let Some(volta) = &measures[j].volta {
141                                last = last.max(volta.passes().into_iter().max().unwrap_or(1));
142                            }
143                            j += 1;
144                        }
145                        last
146                    } else {
147                        2
148                    };
149                    // Each repeat end jumps back at most once per extra pass, so marks that do
150                    // not pair up cannot loop.
151                    let again = if m.volta.is_some() {
152                        volta_pass < last_pass
153                            && usize::from(repeat_jumps[i]) + 1 < usize::from(last_pass)
154                    } else {
155                        repeat_jumps[i] == 0
156                    };
157                    if again {
158                        repeat_jumps[i] = repeat_jumps[i].saturating_add(1);
159                        volta_pass = volta_pass.saturating_add(1).min(last_pass);
160                        i = repeat_start;
161                    } else {
162                        volta_pass = 1;
163                        if matches!(m.barline_right, Barline::RepeatBoth) {
164                            repeat_start = i + 1;
165                        }
166                        i += 1;
167                    }
168                    continue;
169                }
170                _ => {}
171            }
172        }
173
174        i += 1;
175    }
176
177    seq
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::model::notation::Barline;
184    use crate::model::score::{Measure, Score, VoltaBracket};
185
186    fn score_with_measures(measures: Vec<Measure>) -> Score {
187        let mut score = Score::new("T", 120, 4, 4, 0, 0);
188        score.parts[0].staves[0].measures = measures;
189        score
190    }
191
192    fn plain(n: u32) -> Measure {
193        let mut m = Measure::empty(4, 4);
194        m.number = n;
195        m
196    }
197
198    fn with_nav(n: u32, nav: &str) -> Measure {
199        let mut m = plain(n);
200        m.navigation = Some(nav.to_string());
201        m
202    }
203
204    #[test]
205    fn an_ending_for_several_passes_repeats_the_section_that_many_times() {
206        // |: A | 1, 2. B :| 3. C |
207        let mut a = plain(1);
208        a.barline_left = Barline::RepeatStart;
209        let mut b = plain(2);
210        b.barline_right = Barline::RepeatEnd;
211        b.volta = Some(VoltaBracket {
212            number: 1,
213            kind: "begin_end".into(),
214            numbers: vec![1, 2],
215        });
216        let mut c = plain(3);
217        c.volta = Some(VoltaBracket {
218            number: 3,
219            kind: "begin_end".into(),
220            numbers: Vec::new(),
221        });
222        let score = score_with_measures(vec![a, b, c]);
223        assert_eq!(measure_sequence(&score), vec![0, 1, 0, 1, 0, 2]);
224        assert_eq!(VoltaBracket::parse_passes("1, 2"), vec![1, 2]);
225        assert_eq!(VoltaBracket::parse_passes("1-3"), vec![1, 2, 3]);
226        let three = VoltaBracket {
227            number: 1,
228            kind: "begin".into(),
229            numbers: vec![1, 2, 3],
230        };
231        assert_eq!(three.label(), "1-3");
232    }
233
234    #[test]
235    fn unpaired_repeat_marks_play_each_repeat_once_instead_of_looping() {
236        // A first ending that only ends (its start is missing) with a repeat, a second ending,
237        // then a later repeat end with another second ending: the two repeat ends used to reset
238        // each other's pass and loop forever.
239        let volta = |number, kind: &str| {
240            Some(VoltaBracket {
241                number,
242                kind: kind.into(),
243                numbers: Vec::new(),
244            })
245        };
246        let mut m1 = plain(2);
247        m1.barline_right = Barline::RepeatEnd;
248        m1.volta = volta(1, "end");
249        let mut m2 = plain(3);
250        m2.volta = volta(2, "begin_end");
251        let mut m3 = plain(4);
252        m3.barline_right = Barline::RepeatEnd;
253        m3.volta = volta(2, "end");
254        let mut m4 = plain(5);
255        m4.volta = volta(2, "begin_end");
256        let score = score_with_measures(vec![plain(1), m1, m2, m3, m4]);
257        let sequence = measure_sequence(&score);
258        assert!(sequence.len() < 20, "{sequence:?}");
259        assert_eq!(sequence.iter().filter(|&&bar| bar == 3).count(), 2);
260    }
261
262    #[test]
263    fn a_coda_before_its_da_capo_does_not_loop() {
264        let score = score_with_measures(vec![
265            with_nav(1, "Coda"),
266            with_nav(2, "ToCoda"),
267            with_nav(3, "DaCapoAlCoda"),
268        ]);
269        let sequence = measure_sequence(&score);
270        assert!(sequence.len() < 20, "{sequence:?}");
271    }
272
273    #[test]
274    fn no_repeat_is_linear() {
275        let score = score_with_measures(vec![plain(1), plain(2), plain(3), plain(4)]);
276        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 3]);
277    }
278
279    #[test]
280    fn simple_repeat_doubles_section() {
281        let mut m0 = plain(1);
282        m0.barline_left = Barline::RepeatStart;
283        let m1 = plain(2);
284        let mut m2 = plain(3);
285        m2.barline_right = Barline::RepeatEnd;
286        let m3 = plain(4);
287
288        let score = score_with_measures(vec![m0, m1, m2, m3]);
289        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0, 1, 2, 3]);
290    }
291
292    #[test]
293    fn volta_first_pass_plays_ending_1() {
294        let mut m0 = plain(1);
295        m0.barline_left = Barline::RepeatStart;
296        let m1 = plain(2);
297        let mut m2 = plain(3);
298        m2.volta = Some(VoltaBracket {
299            number: 1,
300            kind: "begin_end".into(),
301            numbers: Vec::new(),
302        });
303        m2.barline_right = Barline::RepeatEnd;
304        let mut m3 = plain(4);
305        m3.volta = Some(VoltaBracket {
306            number: 2,
307            kind: "begin_end".into(),
308            numbers: Vec::new(),
309        });
310
311        let score = score_with_measures(vec![m0, m1, m2, m3]);
312        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0, 1, 3]);
313    }
314
315    #[test]
316    fn volta_second_pass_skips_ending_1() {
317        let mut m0 = plain(1);
318        m0.barline_left = Barline::RepeatStart;
319        let m1 = plain(2);
320        let mut m2 = plain(3);
321        m2.volta = Some(VoltaBracket {
322            number: 1,
323            kind: "begin_end".into(),
324            numbers: Vec::new(),
325        });
326        m2.barline_right = Barline::RepeatEnd;
327        let mut m3 = plain(4);
328        m3.volta = Some(VoltaBracket {
329            number: 2,
330            kind: "begin_end".into(),
331            numbers: Vec::new(),
332        });
333        let m4 = plain(5);
334
335        let score = score_with_measures(vec![m0, m1, m2, m3, m4]);
336        let seq = measure_sequence(&score);
337        assert_eq!(seq, vec![0, 1, 2, 0, 1, 3, 4]);
338        let second_pass_third = seq[5];
339        assert_eq!(second_pass_third, 3);
340    }
341
342    #[test]
343    fn independent_repeat_sections_restart_at_their_first_pass() {
344        let ending = |number| {
345            Some(VoltaBracket {
346                number,
347                kind: "begin_end".into(),
348                numbers: Vec::new(),
349            })
350        };
351        let mut first_start = plain(1);
352        first_start.barline_left = Barline::RepeatStart;
353        let mut first_ending = plain(2);
354        first_ending.barline_right = Barline::RepeatEnd;
355        first_ending.volta = ending(1);
356        let mut second_ending = plain(3);
357        second_ending.volta = ending(2);
358
359        let mut next_start = plain(4);
360        next_start.barline_left = Barline::RepeatStart;
361        let mut next_first_ending = plain(5);
362        next_first_ending.barline_right = Barline::RepeatEnd;
363        next_first_ending.volta = ending(1);
364        let mut next_second_ending = plain(6);
365        next_second_ending.volta = ending(2);
366
367        let score = score_with_measures(vec![
368            first_start,
369            first_ending,
370            second_ending,
371            next_start,
372            next_first_ending,
373            next_second_ending,
374        ]);
375        assert_eq!(measure_sequence(&score), vec![0, 1, 0, 2, 3, 4, 3, 5]);
376    }
377
378    // ── Navigation marks ─────────────────────────────────────────────────────
379
380    #[test]
381    fn da_capo_jumps_to_start() {
382        // [A][B][C D.C.] → A B C A B C ...
383        // The second D.C. encounter is ignored (in_nav_pass=true) so we play A B C straight.
384        let score = score_with_measures(vec![plain(1), plain(2), with_nav(3, "DaCapo")]);
385        // Pass: 0,1,2 → D.C. → in_nav_pass, jump to 0 → 0,1,2 → i=3 → done
386        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0, 1, 2]);
387    }
388
389    #[test]
390    fn da_capo_al_fine_stops_at_fine() {
391        // [A Fine][B][C D.C.alFine] → A B C A (stop at Fine=0)
392        let mut m0 = plain(1);
393        m0.navigation = Some("Fine".into());
394        let score = score_with_measures(vec![m0, plain(2), with_nav(3, "DaCapoAlFine")]);
395        // Pass: 0,1,2 → D.C.alFine → jump to 0 → push 0 → Fine found → break
396        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 0]);
397    }
398
399    #[test]
400    fn dal_segno_al_coda_jumps_to_coda() {
401        // [A][B Segno][C][D ToCoda][E D.S.alCoda][F Coda][G]
402        // Pass: 0,1,2,3,4 → D.S.alCoda → jump to segno(1) → in_nav_pass
403        // → push 1,2,3 → ToCoda at 3 → jump to coda(5) → push 5,6
404        let mut m1 = plain(2);
405        m1.navigation = Some("Segno".into());
406        let mut m3 = plain(4);
407        m3.navigation = Some("ToCoda".into());
408        let mut m5 = plain(6);
409        m5.navigation = Some("Coda".into());
410        let score = score_with_measures(vec![
411            plain(1),
412            m1,
413            plain(3),
414            m3,
415            with_nav(5, "DalSegnoAlCoda"),
416            m5,
417            plain(7),
418        ]);
419        assert_eq!(measure_sequence(&score), vec![0, 1, 2, 3, 4, 1, 2, 3, 5, 6]);
420    }
421}