1use std::collections::HashMap;
2
3use super::pattern::{Pattern, StepPlayMode};
4use super::snapshot::Snapshot;
5use crate::control::ParameterCommand;
6
7#[derive(Debug, Clone)]
22pub struct SnapshotSequencer {
23 snapshots: HashMap<String, Snapshot>,
25 patterns: HashMap<String, Pattern>,
27 active_pattern: String,
29 current_step: usize,
31 step_start_sample: u64,
33 direction: i8,
35 running: bool,
37 latest_beat_position: f32,
39 latest_new_beat: bool,
41 latest_new_bar: bool,
43}
44
45impl SnapshotSequencer {
46 pub fn new() -> Self {
48 Self {
49 snapshots: HashMap::new(),
50 patterns: HashMap::new(),
51 active_pattern: String::new(),
52 current_step: 0,
53 step_start_sample: 0,
54 direction: 1,
55 running: false,
56 latest_beat_position: 0.0,
57 latest_new_beat: false,
58 latest_new_bar: false,
59 }
60 }
61
62 pub fn with_lib(
66 snapshots: Vec<Snapshot>,
67 patterns: Vec<Pattern>,
68 ) -> Self {
69 let mut s = Self::new();
70 for snap in snapshots {
71 s.add_snapshot(snap);
72 }
73 for pat in patterns {
74 s.add_pattern(pat);
75 }
76 if let Some(first) = s.patterns.keys().next() {
77 s.active_pattern = first.clone();
78 }
79 s
80 }
81
82 pub fn add_snapshot(&mut self, snapshot: Snapshot) {
86 self.snapshots.insert(snapshot.id.clone(), snapshot);
87 }
88
89 pub fn get_snapshot(&self, id: &str) -> Option<&Snapshot> {
91 self.snapshots.get(id)
92 }
93
94 pub fn remove_snapshot(&mut self, id: &str) -> bool {
96 self.snapshots.remove(id).is_some()
97 }
98
99 pub fn add_pattern(&mut self, pattern: Pattern) {
103 if self.active_pattern.is_empty() {
104 self.active_pattern = pattern.id.clone();
105 }
106 self.patterns.insert(pattern.id.clone(), pattern);
107 }
108
109 pub fn get_pattern(&self, id: &str) -> Option<&Pattern> {
111 self.patterns.get(id)
112 }
113
114 pub fn remove_pattern(&mut self, id: &str) -> bool {
117 if self.patterns.remove(id).is_some() {
118 if self.active_pattern == id {
119 self.active_pattern.clear();
120 self.running = false;
121 }
122 true
123 } else {
124 false
125 }
126 }
127
128 pub fn set_active_pattern(&mut self, id: &str) {
133 if self.patterns.contains_key(id) || id.is_empty() {
134 self.active_pattern = id.to_string();
135 self.current_step = 0;
136 self.step_start_sample = 0;
137 self.direction = 1;
138 }
139 }
140
141 pub fn active_pattern(&self) -> &str {
143 &self.active_pattern
144 }
145
146 pub fn start(&mut self) {
150 self.running = true;
151 }
152
153 pub fn stop(&mut self) {
155 self.running = false;
156 }
157
158 pub fn reset(&mut self, sample_pos: u64) {
160 self.current_step = 0;
161 self.step_start_sample = sample_pos;
162 self.direction = 1;
163 }
164
165 pub fn is_running(&self) -> bool {
167 self.running
168 }
169
170 pub fn current_step(&self) -> usize {
172 self.current_step
173 }
174
175 pub fn latest_beat_position(&self) -> f32 {
180 self.latest_beat_position
181 }
182
183 pub fn is_new_beat(&self) -> bool {
185 self.latest_new_beat
186 }
187
188 pub fn is_new_bar(&self) -> bool {
190 self.latest_new_bar
191 }
192
193 pub fn tick(
201 &mut self,
202 sample_pos: u64,
203 sample_rate: f32,
204 tempo: f32,
205 ) -> Vec<ParameterCommand> {
206 self.tick_ext(sample_pos, sample_rate, tempo, 0.0, false, false)
207 }
208
209 pub fn tick_ext(
222 &mut self,
223 sample_pos: u64,
224 sample_rate: f32,
225 tempo: f32,
226 beat_position: f32,
227 is_new_beat: bool,
228 is_new_bar: bool,
229 ) -> Vec<ParameterCommand> {
230 self.latest_beat_position = beat_position;
231 self.latest_new_beat = is_new_beat;
232 self.latest_new_bar = is_new_bar;
233 if !self.running {
234 return Vec::new();
235 }
236
237 let (len, play_mode, step_dur) = {
238 let pat = match self.patterns.get(&self.active_pattern) {
239 Some(p) if !p.steps.is_empty() => p,
240 _ => return Vec::new(),
241 };
242 let step = &pat.steps[self.current_step];
243 (pat.steps.len(), pat.play_mode, step.duration_samples(tempo, sample_rate))
244 };
245
246 let elapsed = sample_pos.saturating_sub(self.step_start_sample);
247
248 if elapsed >= step_dur {
249 self.current_step = self.advance_step(len, play_mode);
250 self.step_start_sample = sample_pos;
251
252 if let Some(pat) = self.patterns.get(&self.active_pattern) {
253 if self.current_step < pat.steps.len() {
254 let new_step = &pat.steps[self.current_step];
255 return new_step
256 .parameters
257 .iter()
258 .map(|p| ParameterCommand {
259 node_id: p.node_id,
260 param: p.param_name.clone(),
261 value: p.value,
262 })
263 .collect();
264 }
265 }
266 }
267
268 Vec::new()
269 }
270
271 fn advance_step(&mut self, len: usize, play_mode: StepPlayMode) -> usize {
273 if len == 0 {
274 return 0;
275 }
276 match play_mode {
277 StepPlayMode::OneShot => (self.current_step + 1).min(len.saturating_sub(1)),
278 StepPlayMode::Loop => (self.current_step + 1) % len,
279 StepPlayMode::PingPong => {
280 let next = self.current_step as isize + self.direction as isize;
281 if next < 0 {
282 self.direction = 1;
283 1
284 } else if next >= len as isize {
285 self.direction = -1;
286 len.saturating_sub(2)
287 } else {
288 next as usize
289 }
290 }
291 StepPlayMode::Random => {
292 use rand::Rng;
293 let mut rng = rand::thread_rng();
294 rng.gen_range(0..len)
295 }
296 StepPlayMode::Brownian => {
297 use rand::Rng;
298 let mut rng = rand::thread_rng();
299 let offset: isize = rng.gen_range(-1..=1);
300 (self.current_step as isize + offset)
301 .clamp(0, len.saturating_sub(1) as isize) as usize
302 }
303 }
304 }
305}
306
307impl Default for SnapshotSequencer {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313#[derive(Debug, Clone, PartialEq)]
319pub enum SequencerCommand {
320 Start,
322 Stop,
324 Reset {
326 sample_pos: u64,
328 },
329 SetPattern(String),
331}
332
333#[derive(Debug, Clone)]
339pub struct SequencerHandle {
340 cmd_tx: std::sync::Arc<crossbeam_channel::Sender<SequencerCommand>>,
341}
342
343impl SequencerHandle {
344 pub(crate) fn new(cmd_tx: crossbeam_channel::Sender<SequencerCommand>) -> Self {
345 Self {
346 cmd_tx: std::sync::Arc::new(cmd_tx),
347 }
348 }
349
350 pub fn start(&self) {
352 let _ = self.cmd_tx.try_send(SequencerCommand::Start);
353 }
354
355 pub fn stop(&self) {
357 let _ = self.cmd_tx.try_send(SequencerCommand::Stop);
358 }
359
360 pub fn reset(&self, sample_pos: u64) {
362 let _ = self
363 .cmd_tx
364 .try_send(SequencerCommand::Reset { sample_pos });
365 }
366
367 pub fn set_pattern(&self, id: &str) {
369 let _ = self
370 .cmd_tx
371 .try_send(SequencerCommand::SetPattern(id.to_string()));
372 }
373}
374
375#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::sequencer::{ParameterTarget, SequenceStep};
383 use rill_core::NodeId;
384
385 fn make_step(value: f32, dur: f64) -> SequenceStep {
386 SequenceStep::single(NodeId(1), "param", value, dur)
387 }
388
389 fn simple_pattern() -> Pattern {
390 Pattern::new(
391 "p1",
392 vec![
393 make_step(0.0, 1.0),
394 make_step(0.5, 1.0),
395 make_step(1.0, 1.0),
396 make_step(0.5, 1.0),
397 ],
398 )
399 }
400
401 #[test]
402 fn test_sequencer_loop() {
403 let mut seq = SnapshotSequencer::with_lib(vec![], vec![simple_pattern()]);
404 seq.start();
405
406 let sr = 48000.0;
407 let tempo = 120.0;
408
409 let cmds = seq.tick(24000, sr, tempo);
410 assert!(!cmds.is_empty(), "should advance to step 1");
411 assert_eq!(seq.current_step, 1);
412
413 let cmds = seq.tick(48000, sr, tempo);
414 assert!(!cmds.is_empty());
415 assert_eq!(seq.current_step, 2);
416
417 let cmds = seq.tick(72000, sr, tempo);
418 assert!(!cmds.is_empty());
419 assert_eq!(seq.current_step, 3);
420
421 let cmds = seq.tick(96000, sr, tempo);
422 assert!(!cmds.is_empty());
423 assert_eq!(seq.current_step, 0);
424 }
425
426 #[test]
427 fn test_sequencer_not_running() {
428 let mut seq = SnapshotSequencer::with_lib(vec![], vec![simple_pattern()]);
429 let cmds = seq.tick(24000, 48000.0, 120.0);
430 assert!(cmds.is_empty());
431 assert_eq!(seq.current_step, 0);
432 }
433
434 #[test]
435 fn test_sequencer_stop() {
436 let mut seq = SnapshotSequencer::with_lib(vec![], vec![simple_pattern()]);
437 seq.start();
438 seq.tick(24000, 48000.0, 120.0);
439 assert_eq!(seq.current_step, 1);
440
441 seq.stop();
442 seq.tick(48000, 48000.0, 120.0);
443 assert_eq!(seq.current_step, 1, "should not advance after stop");
444 }
445
446 #[test]
447 fn test_sequencer_pingpong() {
448 let mut seq = SnapshotSequencer::with_lib(
449 vec![],
450 vec![Pattern::new("p1", vec![
451 make_step(0.0, 1.0),
452 make_step(0.5, 1.0),
453 make_step(1.0, 1.0),
454 ]).with_mode(StepPlayMode::PingPong)],
455 );
456 seq.start();
457
458 seq.tick(24000, 48000.0, 120.0);
459 assert_eq!(seq.current_step, 1);
460 seq.tick(48000, 48000.0, 120.0);
461 assert_eq!(seq.current_step, 2);
462 seq.tick(72000, 48000.0, 120.0);
463 assert_eq!(seq.current_step, 1);
464 seq.tick(96000, 48000.0, 120.0);
465 assert_eq!(seq.current_step, 0);
466 }
467
468 #[test]
469 fn test_sequencer_oneshot() {
470 let mut seq = SnapshotSequencer::with_lib(
471 vec![],
472 vec![
473 Pattern::new("p1", vec![
474 make_step(0.0, 1.0),
475 make_step(0.5, 1.0),
476 ]).with_mode(StepPlayMode::OneShot),
477 ],
478 );
479 seq.start();
480
481 seq.tick(24000, 48000.0, 120.0);
482 assert_eq!(seq.current_step, 1);
483 seq.tick(48000, 48000.0, 120.0);
484 assert_eq!(seq.current_step, 1);
485 }
486
487 #[test]
488 fn test_sequencer_set_pattern() {
489 let mut seq = SnapshotSequencer::with_lib(
490 vec![],
491 vec![
492 Pattern::new("a", vec![make_step(1.0, 1.0)]),
493 Pattern::new("b", vec![make_step(0.0, 1.0), make_step(0.5, 1.0)]),
494 ],
495 );
496 seq.set_active_pattern("a");
497 seq.start();
498
499 assert_eq!(seq.active_pattern(), "a");
500
501 seq.set_active_pattern("b");
502 assert_eq!(seq.active_pattern(), "b");
503 assert_eq!(seq.current_step, 0);
504
505 let cmds = seq.tick(24000, 48000.0, 120.0);
506 assert!(!cmds.is_empty());
507 assert_eq!(seq.current_step, 1);
508
509 let cmds = seq.tick(48000, 48000.0, 120.0);
510 assert!(!cmds.is_empty());
511 assert_eq!(seq.current_step, 0);
512 }
513
514 #[test]
515 fn test_step_duration_samples() {
516 let step = make_step(0.5, 1.0);
517 assert_eq!(step.duration_samples(120.0, 48000.0), 24000);
518 assert_eq!(step.duration_samples(120.0, 44100.0), 22050);
519 assert_eq!(step.duration_samples(60.0, 48000.0), 48000);
520
521 let eighth = make_step(0.5, 0.5);
522 assert_eq!(eighth.duration_samples(120.0, 48000.0), 12000);
523
524 let sixteenth = make_step(0.5, 0.25);
525 assert_eq!(sixteenth.duration_samples(120.0, 48000.0), 6000);
526 }
527
528 #[test]
529 fn test_parameter_target_creation() {
530 let pt = ParameterTarget::new(NodeId(1), "gain", 0.5);
531 assert_eq!(pt.node_id, NodeId(1));
532 assert_eq!(pt.param_name, "gain");
533 assert_eq!(pt.value, 0.5);
534 }
535
536 #[test]
537 fn test_sequencer_handle_send() {
538 let (tx, rx) = crossbeam_channel::unbounded::<SequencerCommand>();
539 let handle = SequencerHandle::new(tx);
540
541 handle.start();
542 assert_eq!(rx.try_recv(), Ok(SequencerCommand::Start));
543
544 handle.stop();
545 assert_eq!(rx.try_recv(), Ok(SequencerCommand::Stop));
546
547 handle.set_pattern("foo");
548 assert_eq!(rx.try_recv(), Ok(SequencerCommand::SetPattern("foo".into())));
549
550 handle.reset(12345);
551 assert_eq!(rx.try_recv(), Ok(SequencerCommand::Reset { sample_pos: 12345 }));
552 }
553
554 #[test]
555 fn test_tick_ext_stores_beat_info() {
556 let mut seq = SnapshotSequencer::new();
557 let pat = Pattern::new("p1", vec![
558 SequenceStep::single(NodeId(1), "p", 0.5, 1.0),
559 ]);
560 seq.add_pattern(pat);
561 seq.set_active_pattern("p1");
562 seq.start();
563
564 let _ = seq.tick_ext(0, 48000.0, 120.0, 0.0, true, true);
565 assert!((seq.latest_beat_position() - 0.0).abs() < 1e-6);
566 assert!(seq.is_new_beat());
567 assert!(seq.is_new_bar());
568
569 let _ = seq.tick(24000, 48000.0, 120.0);
570 assert!((seq.latest_beat_position() - 0.0).abs() < 1e-6);
571 assert!(!seq.is_new_beat());
572 assert!(!seq.is_new_bar());
573
574 let _ = seq.tick_ext(48000, 48000.0, 120.0, 2.5, false, false);
575 assert!((seq.latest_beat_position() - 2.5).abs() < 1e-6);
576 assert!(!seq.is_new_beat());
577 assert!(!seq.is_new_bar());
578 }
579}