1use phosphor_core::clip::{ClipEvent, MidiClip, NoteSnapshot};
27use phosphor_core::pattern::compile_cycle;
28use phosphor_core::transport::Transport;
29
30use super::SequencerState;
31use crate::state::Clip;
32
33pub const TICKS_PER_BAR: i64 = Transport::PPQ * 4;
35
36fn bars_covering(ticks: i64) -> i64 {
41 (ticks + TICKS_PER_BAR - 1).div_euclid(TICKS_PER_BAR)
42}
43
44fn bar_at_or_after(tick: i64) -> i64 {
46 bars_covering(tick.max(0)) * TICKS_PER_BAR
47}
48
49#[derive(Debug, Clone, PartialEq)]
51pub struct Bounce {
52 pub start_tick: i64,
54 pub length_ticks: i64,
56 pub events: Vec<ClipEvent>,
58 pub stops_playback: bool,
60}
61
62impl Bounce {
63 #[must_use]
65 pub fn bar(&self) -> i64 {
66 self.start_tick / TICKS_PER_BAR + 1
67 }
68
69 #[must_use]
72 pub fn bars(&self) -> i64 {
73 bars_covering(self.length_ticks).max(1)
74 }
75
76 #[must_use]
78 pub fn notes(&self) -> Vec<NoteSnapshot> {
79 let clip = MidiClip::new(self.start_tick, self.length_ticks, self.events.clone());
80 phosphor_core::clip::ClipSnapshot::from_clip(0, 0, &clip).notes
81 }
82}
83
84#[must_use]
86pub fn bounce_pattern(state: &SequencerState, playhead: i64, clips: &[Clip]) -> Option<Bounce> {
87 let block = state.block(state.selected_slot() as usize);
88 let mut events = Vec::new();
89 compile_cycle(&block, 0, &mut events);
90 finish(state, events, block.length_ticks(), playhead, clips)
91}
92
93#[must_use]
98pub fn bounce_chain(state: &SequencerState, playhead: i64, clips: &[Clip]) -> Option<Bounce> {
99 if !state.is_chained() {
100 return bounce_pattern(state, playhead, clips);
101 }
102
103 let mut events = Vec::new();
104 let mut origin = 0i64;
105 for entry in state.chain() {
106 let block = state.block(entry.slot as usize);
107 for _ in 0..entry.repeats.max(1) {
111 compile_cycle(&block, origin, &mut events);
112 origin += block.length_ticks();
113 }
114 }
115 events.sort_by_key(|e| e.tick);
116 finish(state, events, origin, playhead, clips)
117}
118
119fn finish(
120 state: &SequencerState,
121 events: Vec<phosphor_core::pattern::PatternEvent>,
122 length_ticks: i64,
123 playhead: i64,
124 clips: &[Clip],
125) -> Option<Bounce> {
126 if events.is_empty() || length_ticks <= 0 {
127 return None;
128 }
129 Some(Bounce {
130 start_tick: next_free_bar(clips, playhead, length_ticks),
131 length_ticks,
132 events: events
133 .into_iter()
134 .map(|e| ClipEvent { tick: e.tick, status: e.status, data1: e.data1, data2: e.data2 })
135 .collect(),
136 stops_playback: state.is_playing(),
137 })
138}
139
140#[must_use]
148pub fn next_free_bar(clips: &[Clip], playhead: i64, length: i64) -> i64 {
149 let length = length.max(1);
150 let mut start = bar_at_or_after(playhead);
151
152 for _ in 0..=clips.len() {
157 let end = start + length;
158 let blocker = clips
159 .iter()
160 .filter(|c| c.start_tick < end && c.start_tick + c.length_ticks.max(1) > start)
161 .map(|c| c.start_tick + c.length_ticks.max(1))
162 .max();
163 match blocker {
164 Some(after) => start = bar_at_or_after(after.max(start + 1)),
165 None => return start,
166 }
167 }
168 start
169}
170
171#[cfg(test)]
172mod tests {
173 use super::super::ops::{dispatch, SeqOp};
174 use super::super::tests::drum_track;
175 use super::*;
176 use crate::state::TrackState;
177
178 fn clip(start_tick: i64, length_ticks: i64) -> Clip {
179 Clip {
180 number: 1,
181 width: 4,
182 has_content: true,
183 start_tick,
184 length_ticks,
185 notes: Vec::new(),
186 hidden_notes: Vec::new(),
187 }
188 }
189
190 fn four_on_the_floor() -> TrackState {
191 let mut track = drum_track();
192 for step in [0usize, 4, 8, 12] {
193 dispatch(&mut track, SeqOp::SelectStep(step as u8));
194 dispatch(&mut track, SeqOp::ToggleStep);
195 }
196 track
197 }
198
199 #[test]
202 fn a_bounce_is_one_cycle_of_the_pattern() {
203 let track = four_on_the_floor();
204 let bounce = bounce_pattern(track.sequencer.as_ref().unwrap(), 0, &[]).unwrap();
205
206 assert_eq!(bounce.length_ticks, 3840);
207 assert_eq!(bounce.bars(), 1);
208 assert_eq!(bounce.bar(), 1);
209 let ons: Vec<i64> = bounce
210 .events
211 .iter()
212 .filter(|e| e.status == 0x90 && e.data2 > 0)
213 .map(|e| e.tick)
214 .collect();
215 assert_eq!(ons, vec![0, 960, 1920, 2880]);
216 assert_eq!(bounce.events.iter().filter(|e| e.status == 0x80).count(), 4);
218 assert_eq!(bounce.notes().len(), 4);
219 }
220
221 #[test]
224 fn a_bounce_carries_the_patterns_swing() {
225 let mut track = drum_track();
226 for step in 0..8u8 {
227 dispatch(&mut track, SeqOp::SelectStep(step));
228 dispatch(&mut track, SeqOp::ToggleStep);
229 }
230 dispatch(&mut track, SeqOp::NudgeSwing(12)); let bounce = bounce_pattern(track.sequencer.as_ref().unwrap(), 0, &[]).unwrap();
233 let ons: Vec<i64> = bounce
234 .events
235 .iter()
236 .filter(|e| e.status == 0x90 && e.data2 > 0)
237 .map(|e| e.tick)
238 .collect();
239 assert_eq!(ons, vec![0, 297, 480, 777, 960, 1257, 1440, 1737]);
241 }
242
243 #[test]
246 fn a_chain_bounces_with_its_repeats_expanded() {
247 let mut track = four_on_the_floor();
248 dispatch(&mut track, SeqOp::SelectSlot(1));
249 dispatch(&mut track, SeqOp::SelectStep(2));
250 dispatch(&mut track, SeqOp::ToggleStep);
251 dispatch(&mut track, SeqOp::PushChainEntry { slot: 0, repeats: 2 });
252 dispatch(&mut track, SeqOp::PushChainEntry { slot: 1, repeats: 1 });
253
254 let state = track.sequencer.as_ref().unwrap();
255 let bounce = bounce_chain(state, 0, &[]).unwrap();
256 assert_eq!(bounce.length_ticks, 3840 * 3);
257 assert_eq!(bounce.bars(), 3);
258
259 let ons: Vec<i64> = bounce
260 .events
261 .iter()
262 .filter(|e| e.status == 0x90 && e.data2 > 0)
263 .map(|e| e.tick)
264 .collect();
265 assert_eq!(
266 ons,
267 vec![0, 960, 1920, 2880, 3840, 4800, 5760, 6720, 7680 + 480],
268 "two times through A, then one of B"
269 );
270 }
271
272 #[test]
275 fn bouncing_a_chain_that_is_not_there_bounces_the_pattern() {
276 let track = four_on_the_floor();
277 let state = track.sequencer.as_ref().unwrap();
278 assert_eq!(bounce_chain(state, 0, &[]), bounce_pattern(state, 0, &[]));
279 }
280
281 #[test]
284 fn an_empty_pattern_bounces_to_nothing() {
285 let track = drum_track();
286 assert!(bounce_pattern(track.sequencer.as_ref().unwrap(), 0, &[]).is_none());
287 }
288
289 #[test]
292 fn a_bounce_says_when_it_has_to_stop_the_pattern() {
293 let mut track = four_on_the_floor();
295 let state = track.sequencer.as_ref().unwrap();
296 assert!(bounce_pattern(state, 0, &[]).unwrap().stops_playback);
297
298 dispatch(&mut track, SeqOp::SetPlaying(false));
299 let state = track.sequencer.as_ref().unwrap();
300 assert!(!bounce_pattern(state, 0, &[]).unwrap().stops_playback);
301 }
302
303 #[test]
306 fn a_bounce_lands_on_a_bar_line_at_or_after_the_playhead() {
307 assert_eq!(next_free_bar(&[], 0, 3840), 0);
308 assert_eq!(next_free_bar(&[], 1, 3840), 3840);
309 assert_eq!(next_free_bar(&[], 3840, 3840), 3840);
310 assert_eq!(next_free_bar(&[], 3841, 3840), 7680);
311 assert_eq!(next_free_bar(&[], -500, 3840), 0);
312 }
313
314 #[test]
318 fn a_bounce_never_lands_on_a_clip_that_is_already_there() {
319 let occupied = [clip(0, 3840), clip(3840, 3840)];
320 assert_eq!(next_free_bar(&occupied, 0, 3840), 7680);
321
322 let gap = [clip(0, 3840), clip(7680, 3840)];
324 assert_eq!(next_free_bar(&gap, 0, 3840), 3840);
325
326 assert_eq!(next_free_bar(&gap, 0, 3840 * 2), 11_520);
328 }
329
330 #[test]
333 fn the_search_for_a_free_bar_terminates() {
334 let awkward = [clip(7680, 0), clip(0, 1), clip(3840, 100_000), clip(0, 3840)];
335 let found = next_free_bar(&awkward, 0, 3840);
336 assert_eq!(found % TICKS_PER_BAR, 0);
337 assert!(found >= 103_840);
338 }
339}