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