1use phosphor_core::pattern::{
32 ChainEntry, Chord, PatternBlock, Step, Voicing, LANES, MAX_CHAIN, MAX_CHORD_NOTES, MAX_STEPS,
33 SLOTS, STEP_COUNTS,
34};
35
36use super::{chords, is_drum_child, SequencerState, DEFAULT_DRUM_LANES};
37use crate::state::{InstrumentType, TrackState};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
45pub struct HeldNotes {
46 notes: [u8; MAX_CHORD_NOTES],
47 len: u8,
48}
49
50impl HeldNotes {
51 #[must_use]
54 pub fn new(held: &[u8]) -> Self {
55 let mut notes = [0u8; MAX_CHORD_NOTES];
56 let len = held.len().min(MAX_CHORD_NOTES);
57 notes[..len].copy_from_slice(&held[..len]);
58 notes[..len].sort_unstable();
59 Self { notes, len: len as u8 }
60 }
61
62 #[must_use]
63 pub fn as_slice(&self) -> &[u8] {
64 &self.notes[..self.len as usize]
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum SeqOp {
74 SelectSlot(u8),
77 SelectLane(u8),
78 MoveLane(i8),
79 SelectStep(u8),
80 MoveStep(i8),
81
82 ToggleStep,
84 SetStep(bool),
85 ClearStep,
86 ToggleAccent,
87 NudgePitch(i8),
90 NudgeOctave(i8),
91 CycleChord(i8),
92 CycleVoicing(i8),
93 ToggleRootBelow,
94 NudgeGate(i8),
96 ToggleTie,
97
98 ToggleLaneMute,
100 ToggleLaneSolo,
101 SetLaneNote(u8),
103
104 CycleLength(i8),
107 CycleRate(i8),
108 NudgeSwing(i8),
109 NudgeBaseVelocity(i8),
110 NudgeAccentVelocity(i8),
111 NudgeDefaultGate(i8),
113 CycleMode(i8),
114 SetTonic(u8),
115 ClearPattern,
116 CopyPattern { from: u8, to: u8 },
117
118 SetPlaying(bool),
120 TogglePlaying,
121 QueueSlot(u8),
123 ClearQueue,
124 CycleSwitchQuant(i8),
125
126 PushChainEntry { slot: u8, repeats: u8 },
128 SetChainRepeats { index: u8, repeats: u8 },
129 RemoveChainEntry(u8),
130 ClearChain,
131
132 SetChild(InstrumentType),
134
135 ArmStepRecord(bool),
137 RecordNotes(HeldNotes),
139 RecordRest,
141 RecordTie,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
151pub struct SeqEffect {
152 pub patterns: u8,
157 pub child: bool,
160}
161
162impl SeqEffect {
163 pub const NOTHING: Self = Self { patterns: 0, child: false };
165
166 #[must_use]
168 pub const fn slot(slot: u8) -> Self {
169 Self { patterns: 1 << (slot & 0b111), child: false }
170 }
171
172 #[must_use]
174 pub const fn all_slots() -> Self {
175 Self { patterns: 0xFF, child: false }
176 }
177
178 #[must_use]
179 pub const fn is_nothing(self) -> bool {
180 self.patterns == 0 && !self.child
181 }
182
183 #[must_use]
185 pub const fn wants(self, slot: u8) -> bool {
186 self.patterns & (1 << (slot & 0b111)) != 0
187 }
188
189 pub fn slots(self) -> impl Iterator<Item = u8> {
191 (0..SLOTS as u8).filter(move |&slot| self.wants(slot))
192 }
193
194 #[must_use]
196 pub fn and(self, other: Self) -> Self {
197 Self { patterns: self.patterns | other.patterns, child: self.child || other.child }
198 }
199}
200
201pub fn dispatch(track: &mut TrackState, op: SeqOp) -> SeqEffect {
211 if let SeqOp::SetChild(child) = op {
214 return set_child(track, child);
215 }
216
217 let Some(state) = track.sequencer.as_mut() else {
218 return SeqEffect::NOTHING;
219 };
220 apply(state, op)
221}
222
223fn set_child(track: &mut TrackState, child: InstrumentType) -> SeqEffect {
231 if track.sequencer.is_none() || child.is_sequencer() {
232 return SeqEffect::NOTHING;
233 }
234 let previous = track.instrument_type;
235 if previous == Some(child) {
236 return SeqEffect::NOTHING;
237 }
238
239 track.instrument_type = Some(child);
240 track.synth_params = crate::preset::defaults(child);
241
242 let was_drums = previous.is_some_and(is_drum_child);
243 let effect = if was_drums == is_drum_child(child) {
244 SeqEffect::NOTHING
245 } else {
246 let state = track.sequencer.as_mut().expect("checked above");
247 relay_lanes(state, child);
248 SeqEffect::all_slots()
249 };
250 SeqEffect { child: true, ..effect }
251}
252
253fn relay_lanes(state: &mut SequencerState, child: InstrumentType) {
255 let drums = is_drum_child(child);
256 for slot in 0..SLOTS {
257 for (lane_index, lane) in state.patterns[slot].lanes.iter_mut().enumerate() {
258 lane.note = if drums {
259 DEFAULT_DRUM_LANES[lane_index]
260 } else {
261 phosphor_core::pattern::Lane::FROM_STEP
262 };
263 }
264 }
265}
266
267#[allow(clippy::too_many_lines)] fn apply(state: &mut SequencerState, op: SeqOp) -> SeqEffect {
270 let selected = state.selected_slot();
271 let lane_index = state.lane_cursor();
272 let step_index = state.step_cursor();
273 let here = SeqEffect::slot(selected);
274
275 match op {
276 SeqOp::SetChild(_) => SeqEffect::NOTHING, SeqOp::SelectSlot(slot) => {
280 state.selected = slot.min(SLOTS as u8 - 1);
281 SeqEffect::NOTHING
282 }
283 SeqOp::SelectLane(lane) => {
284 state.lane = lane.min(LANES as u8 - 1);
285 SeqEffect::NOTHING
286 }
287 SeqOp::MoveLane(delta) => {
288 state.lane = step_within(state.lane, delta, LANES as u8);
289 SeqEffect::NOTHING
290 }
291 SeqOp::SelectStep(step) => {
292 state.step = step.min(MAX_STEPS as u8 - 1);
293 SeqEffect::NOTHING
294 }
295 SeqOp::MoveStep(delta) => {
296 let len = state.pattern().step_count() as u8;
300 state.step = wrap_within(state.step.min(len - 1), delta, len);
301 SeqEffect::NOTHING
302 }
303
304 SeqOp::ToggleStep => {
306 let on = !state.step().on;
307 set_step(state, lane_index, step_index, on);
308 here
309 }
310 SeqOp::SetStep(on) => {
311 set_step(state, lane_index, step_index, on);
312 here
313 }
314 SeqOp::ClearStep => {
315 let default_gate = state.pattern().default_gate;
316 let step = step_mut(state, lane_index, step_index);
317 *step = Step { gate: default_gate, ..Step::silent() };
318 here
319 }
320 SeqOp::ToggleAccent => {
321 let step = step_mut(state, lane_index, step_index);
322 step.accent = !step.accent;
323 here
324 }
325 SeqOp::NudgePitch(delta) => {
326 if state.lane().is_pitched() {
327 let (mode, tonic) = {
328 let p = state.pattern();
329 (p.mode, p.tonic)
330 };
331 let step = step_mut(state, lane_index, step_index);
332 let note = mode.walk(step.root(), tonic, i32::from(delta));
333 step.octave = note / 12;
334 step.key = note % 12;
335 here
336 } else {
337 SeqEffect::NOTHING
338 }
339 }
340 SeqOp::NudgeOctave(delta) => {
341 if state.lane().is_pitched() {
342 let step = step_mut(state, lane_index, step_index);
343 let note = (i32::from(step.root()) + i32::from(delta) * 12).clamp(0, 127) as u8;
344 step.octave = note / 12;
345 step.key = note % 12;
346 here
347 } else {
348 SeqEffect::NOTHING
349 }
350 }
351 SeqOp::CycleChord(delta) => {
352 if state.lane().is_pitched() {
353 let step = step_mut(state, lane_index, step_index);
354 step.chord = step.chord_kind().stepped(i32::from(delta)).index();
355 here
356 } else {
357 SeqEffect::NOTHING
358 }
359 }
360 SeqOp::CycleVoicing(delta) => {
361 if state.lane().is_pitched() {
362 let step = step_mut(state, lane_index, step_index);
363 let voicing = step.voicing_kind().stepped(i32::from(delta)).index();
364 step.voicing = voicing | (step.voicing & Step::ROOT_BELOW);
365 here
366 } else {
367 SeqEffect::NOTHING
368 }
369 }
370 SeqOp::ToggleRootBelow => {
371 if state.lane().is_pitched() {
372 let step = step_mut(state, lane_index, step_index);
373 step.voicing ^= Step::ROOT_BELOW;
374 here
375 } else {
376 SeqEffect::NOTHING
377 }
378 }
379 SeqOp::NudgeGate(delta) => {
380 let step = step_mut(state, lane_index, step_index);
381 step.gate = nudge_gate(step.gate, delta);
382 here
383 }
384 SeqOp::ToggleTie => {
385 let default_gate = state.pattern().default_gate;
386 let step = step_mut(state, lane_index, step_index);
387 step.gate = if step.gate == Step::TIE { default_gate } else { Step::TIE };
388 here
389 }
390
391 SeqOp::ToggleLaneMute => {
393 let lane = &mut state.patterns[selected as usize].lanes[lane_index];
394 lane.muted = !lane.muted;
395 here
396 }
397 SeqOp::ToggleLaneSolo => {
398 let lane = &mut state.patterns[selected as usize].lanes[lane_index];
399 lane.soloed = !lane.soloed;
400 here
401 }
402 SeqOp::SetLaneNote(note) => {
403 let lane = &mut state.patterns[selected as usize].lanes[lane_index];
404 lane.note = note;
405 here
406 }
407
408 SeqOp::CycleLength(delta) => {
410 let block = &mut state.patterns[selected as usize];
411 let current = STEP_COUNTS
412 .iter()
413 .position(|&c| c == block.steps)
414 .unwrap_or(3) as i32;
415 let index = (current + i32::from(delta)).clamp(0, STEP_COUNTS.len() as i32 - 1);
416 block.steps = STEP_COUNTS[index as usize];
417 here
418 }
419 SeqOp::CycleRate(delta) => {
420 let block = &mut state.patterns[selected as usize];
421 block.rate = block.rate.stepped(i32::from(delta));
422 here
423 }
424 SeqOp::NudgeSwing(delta) => {
425 let block = &mut state.patterns[selected as usize];
426 block.swing = clamp_u8(
427 block.swing,
428 delta,
429 PatternBlock::MIN_SWING,
430 PatternBlock::MAX_SWING,
431 );
432 here
433 }
434 SeqOp::NudgeBaseVelocity(delta) => {
435 let block = &mut state.patterns[selected as usize];
436 block.base_vel = clamp_u8(block.base_vel, delta, 1, 127);
437 here
438 }
439 SeqOp::NudgeAccentVelocity(delta) => {
440 let block = &mut state.patterns[selected as usize];
441 block.accent_vel = clamp_u8(block.accent_vel, delta, 1, 127);
442 here
443 }
444 SeqOp::NudgeDefaultGate(delta) => {
445 let block = &mut state.patterns[selected as usize];
448 block.default_gate = clamp_u8(
449 block.default_gate,
450 delta.saturating_mul(GATE_STEP),
451 Step::MIN_GATE,
452 Step::MAX_GATE,
453 );
454 here
455 }
456 SeqOp::CycleMode(delta) => {
457 let block = &mut state.patterns[selected as usize];
458 block.mode = block.mode.stepped(i32::from(delta));
459 here
460 }
461 SeqOp::SetTonic(tonic) => {
462 let block = &mut state.patterns[selected as usize];
463 block.tonic = tonic % 12;
464 here
465 }
466 SeqOp::ClearPattern => {
467 let block = &mut state.patterns[selected as usize];
470 for lane in &mut block.lanes {
471 for step in &mut lane.steps {
472 *step = Step { gate: block.default_gate, ..Step::silent() };
473 }
474 }
475 here
476 }
477 SeqOp::CopyPattern { from, to } => {
478 let from = (from as usize).min(SLOTS - 1);
479 let to = (to as usize).min(SLOTS - 1);
480 if from == to {
481 return SeqEffect::NOTHING;
482 }
483 state.patterns[to] = state.patterns[from];
484 SeqEffect::slot(to as u8)
485 }
486
487 SeqOp::SetPlaying(playing) => {
489 if state.playing == playing {
490 return SeqEffect::NOTHING;
491 }
492 state.playing = playing;
493 here
494 }
495 SeqOp::TogglePlaying => {
496 state.playing = !state.playing;
497 here
498 }
499 SeqOp::QueueSlot(slot) => {
500 let slot = slot.min(SLOTS as u8 - 1);
501 if state.is_chained() || slot == state.live {
504 return SeqEffect::NOTHING;
505 }
506 state.pending = Some(slot);
507 here
508 }
509 SeqOp::ClearQueue => {
510 if state.pending.take().is_none() {
511 return SeqEffect::NOTHING;
512 }
513 here
514 }
515 SeqOp::CycleSwitchQuant(delta) => {
516 state.switch_quant = state.switch_quant.stepped(i32::from(delta));
517 here
518 }
519
520 SeqOp::PushChainEntry { slot, repeats } => {
522 let len = state.chain_len as usize;
523 if len >= MAX_CHAIN {
524 return SeqEffect::NOTHING;
525 }
526 state.chain[len] =
527 ChainEntry { slot: slot.min(SLOTS as u8 - 1), repeats: repeats.max(1) };
528 state.chain_len += 1;
529 state.pending = None;
531 here
532 }
533 SeqOp::SetChainRepeats { index, repeats } => {
534 let Some(entry) = state.chain.get_mut(index as usize) else {
535 return SeqEffect::NOTHING;
536 };
537 if index >= state.chain_len {
538 return SeqEffect::NOTHING;
539 }
540 entry.repeats = repeats.max(1);
541 here
542 }
543 SeqOp::RemoveChainEntry(index) => {
544 let len = state.chain_len as usize;
545 if index as usize >= len {
546 return SeqEffect::NOTHING;
547 }
548 for i in index as usize..len - 1 {
549 state.chain[i] = state.chain[i + 1];
550 }
551 state.chain_len -= 1;
552 here
553 }
554 SeqOp::ClearChain => {
555 if state.chain_len == 0 {
556 return SeqEffect::NOTHING;
557 }
558 state.chain_len = 0;
559 here
560 }
561
562 SeqOp::ArmStepRecord(armed) => {
564 state.step_record = armed;
565 SeqEffect::NOTHING
566 }
567 SeqOp::RecordNotes(held) => {
568 if !state.step_record || held.as_slice().is_empty() {
569 return SeqEffect::NOTHING;
570 }
571 let pitched = state.lane().is_pitched();
572 let default_gate = state.pattern().default_gate;
573 let (mode, tonic) = {
574 let p = state.pattern();
575 (p.mode, p.tonic)
576 };
577
578 if pitched {
579 let found = chords::identify(held.as_slice(), mode, tonic);
584 let step = step_mut(state, lane_index, step_index);
585 step.on = true;
586 step.gate = default_gate;
587 match found {
588 Some(named) => {
589 step.octave = named.root / 12;
590 step.key = named.root % 12;
591 step.chord = named.chord.index();
592 step.voicing = named.voicing.index()
593 | if named.root_below { Step::ROOT_BELOW } else { 0 };
594 }
595 None => {
596 let root = held.as_slice()[0];
597 step.octave = root / 12;
598 step.key = root % 12;
599 step.chord = Chord::None.index();
600 step.voicing = Voicing::Close.index();
601 }
602 }
603 } else {
604 let step = step_mut(state, lane_index, step_index);
608 step.on = true;
609 step.gate = default_gate;
610 }
611
612 advance_record_cursor(state);
613 here
614 }
615 SeqOp::RecordRest => {
616 if !state.step_record {
617 return SeqEffect::NOTHING;
618 }
619 advance_record_cursor(state);
620 SeqEffect::NOTHING
621 }
622 SeqOp::RecordTie => {
623 if !state.step_record {
624 return SeqEffect::NOTHING;
625 }
626 let len = state.pattern().step_count() as u8;
629 let previous = wrap_within(state.step.min(len - 1), -1, len) as usize;
630 let step = step_mut(state, lane_index, previous);
631 step.gate = Step::TIE;
632 advance_record_cursor(state);
633 here
634 }
635 }
636}
637
638fn step_mut(state: &mut SequencerState, lane: usize, step: usize) -> &mut Step {
641 let slot = state.selected as usize;
642 &mut state.patterns[slot].lanes[lane].steps[step]
643}
644
645fn set_step(state: &mut SequencerState, lane: usize, step_index: usize, on: bool) {
649 let default_gate = state.pattern().default_gate;
650 let step = step_mut(state, lane, step_index);
651 step.on = on;
652 if on {
653 step.gate = default_gate;
654 }
655}
656
657fn advance_record_cursor(state: &mut SequencerState) {
659 let len = state.pattern().step_count() as u8;
660 state.step = wrap_within(state.step.min(len - 1), 1, len);
661}
662
663fn step_within(current: u8, delta: i8, count: u8) -> u8 {
665 (i32::from(current) + i32::from(delta)).clamp(0, i32::from(count) - 1) as u8
666}
667
668fn wrap_within(current: u8, delta: i8, count: u8) -> u8 {
670 let count = i32::from(count.max(1));
671 ((i32::from(current) + i32::from(delta)).rem_euclid(count)) as u8
672}
673
674const GATE_STEP: i8 = 5;
676
677fn clamp_u8(current: u8, delta: i8, low: u8, high: u8) -> u8 {
678 (i32::from(current) + i32::from(delta)).clamp(i32::from(low), i32::from(high)) as u8
679}
680
681fn nudge_gate(gate: u8, delta: i8) -> u8 {
687 if gate == Step::TIE {
688 return if delta < 0 { Step::MAX_GATE } else { Step::TIE };
689 }
690 let next = i32::from(gate) + i32::from(delta) * i32::from(GATE_STEP);
691 if next > i32::from(Step::MAX_GATE) {
692 return Step::TIE;
693 }
694 next.clamp(i32::from(Step::MIN_GATE), i32::from(Step::MAX_GATE)) as u8
695}
696
697#[cfg(test)]
698mod tests {
699 use super::super::tests::drum_track;
700 use super::*;
701 use crate::state::TrackState;
702 use phosphor_core::pattern::{Lane, Mode, Rate, SwitchQuant};
703 use phosphor_core::project::TrackKind;
704
705 fn melodic_track() -> TrackState {
706 let mut track = drum_track();
707 dispatch(&mut track, SeqOp::SetChild(InstrumentType::Juno60));
708 track
709 }
710
711 fn seq(track: &TrackState) -> &SequencerState {
712 track.sequencer.as_ref().unwrap()
713 }
714
715 #[test]
719 fn a_track_without_a_sequencer_ignores_the_ops() {
720 let mut track = TrackState::new("synth", 0, false, TrackKind::Instrument, vec![]);
721 assert_eq!(dispatch(&mut track, SeqOp::ToggleStep), SeqEffect::NOTHING);
722 assert!(track.sequencer.is_none());
723 }
724
725 #[test]
728 fn an_edit_reports_the_slot_it_changed() {
729 let mut track = drum_track();
730 dispatch(&mut track, SeqOp::SelectSlot(3));
731 let effect = dispatch(&mut track, SeqOp::ToggleStep);
732 assert!(effect.wants(3));
733 assert_eq!(effect.slots().collect::<Vec<_>>(), vec![3]);
734 assert!(!effect.wants(0));
735
736 assert!(dispatch(&mut track, SeqOp::MoveStep(1)).is_nothing());
738 assert!(dispatch(&mut track, SeqOp::SelectLane(2)).is_nothing());
739 }
740
741 #[test]
742 fn toggling_a_step_writes_the_patterns_default_gate() {
743 let mut track = drum_track();
744 dispatch(&mut track, SeqOp::NudgeDefaultGate(4)); dispatch(&mut track, SeqOp::ToggleStep);
746 assert!(seq(&track).step().on);
747 assert_eq!(seq(&track).step().gate, 70);
748
749 dispatch(&mut track, SeqOp::ToggleStep);
750 assert!(!seq(&track).step().on);
751 }
752
753 #[test]
756 fn the_step_cursor_wraps_at_the_patterns_length() {
757 let mut track = drum_track();
758 dispatch(&mut track, SeqOp::CycleLength(-2)); dispatch(&mut track, SeqOp::SelectStep(7));
760 dispatch(&mut track, SeqOp::MoveStep(1));
761 assert_eq!(seq(&track).step_cursor(), 0);
762 dispatch(&mut track, SeqOp::MoveStep(-1));
763 assert_eq!(seq(&track).step_cursor(), 7);
764 }
765
766 #[test]
770 fn shortening_a_pattern_masks_rather_than_clears() {
771 let mut track = drum_track();
772 dispatch(&mut track, SeqOp::SelectStep(30));
773 dispatch(&mut track, SeqOp::ToggleStep);
774 dispatch(&mut track, SeqOp::CycleLength(-3));
775 assert_eq!(seq(&track).pattern().step_count(), 4);
776 assert!(seq(&track).pattern().lanes[0].steps[30].on);
777 dispatch(&mut track, SeqOp::CycleLength(5));
778 assert_eq!(seq(&track).pattern().step_count(), 32);
779 assert!(seq(&track).pattern().lanes[0].steps[30].on);
780 }
781
782 #[test]
786 fn a_pinned_drum_lane_has_no_pitch_to_walk() {
787 let mut track = drum_track();
788 assert!(dispatch(&mut track, SeqOp::NudgePitch(1)).is_nothing());
789 assert!(dispatch(&mut track, SeqOp::CycleChord(1)).is_nothing());
790 assert_eq!(seq(&track).lane().note, DEFAULT_DRUM_LANES[0]);
791 }
792
793 #[test]
796 fn pitch_walks_semitones_off_a_mode_and_degrees_on_one() {
797 let mut track = melodic_track();
798 let start = seq(&track).step().root();
799 dispatch(&mut track, SeqOp::NudgePitch(1));
800 assert_eq!(seq(&track).step().root(), start + 1, "chromatic is semitones");
801
802 dispatch(&mut track, SeqOp::CycleMode(1)); dispatch(&mut track, SeqOp::SetTonic(0));
804 dispatch(&mut track, SeqOp::NudgePitch(1));
805 assert_eq!(seq(&track).pattern().mode, Mode::Ionian);
806 assert_eq!(seq(&track).step().root(), 62, "C# up a degree in C major is D");
807 }
808
809 #[test]
810 fn octaves_move_by_twelve_and_stop_at_the_ends() {
811 let mut track = melodic_track();
812 dispatch(&mut track, SeqOp::NudgeOctave(1));
813 assert_eq!(seq(&track).step().root(), 72);
814 for _ in 0..20 {
815 dispatch(&mut track, SeqOp::NudgeOctave(1));
816 }
817 assert!(seq(&track).step().root() <= 127);
818 for _ in 0..40 {
819 dispatch(&mut track, SeqOp::NudgeOctave(-1));
820 }
821 assert!(seq(&track).step().root() < 12);
822 }
823
824 #[test]
827 fn the_gate_walks_off_the_top_into_the_tie() {
828 let mut track = drum_track();
829 for _ in 0..40 {
830 dispatch(&mut track, SeqOp::NudgeGate(1));
831 }
832 assert_eq!(seq(&track).step().gate, Step::TIE);
833 dispatch(&mut track, SeqOp::NudgeGate(-1));
834 assert_eq!(seq(&track).step().gate, Step::MAX_GATE);
835 for _ in 0..80 {
836 dispatch(&mut track, SeqOp::NudgeGate(-1));
837 }
838 assert_eq!(seq(&track).step().gate, Step::MIN_GATE);
839 }
840
841 #[test]
844 fn root_below_is_independent_of_the_voicing() {
845 let mut track = melodic_track();
846 dispatch(&mut track, SeqOp::CycleVoicing(1));
847 dispatch(&mut track, SeqOp::ToggleRootBelow);
848 assert_eq!(seq(&track).step().voicing_kind(), Voicing::Drop2);
849 assert!(seq(&track).step().root_below());
850 dispatch(&mut track, SeqOp::CycleVoicing(1));
851 assert_eq!(seq(&track).step().voicing_kind(), Voicing::First);
852 assert!(seq(&track).step().root_below(), "the voicing wiped the bass double");
853 }
854
855 #[test]
858 fn changing_the_child_relays_the_lanes_only_when_the_kind_changes() {
859 let mut track = drum_track();
860 dispatch(&mut track, SeqOp::SetLaneNote(75));
861 let effect = dispatch(&mut track, SeqOp::SetChild(InstrumentType::DrumRack));
862 assert!(effect.is_nothing(), "the same child is not a change");
863 assert_eq!(seq(&track).lane().note, 75);
864
865 let effect = dispatch(&mut track, SeqOp::SetChild(InstrumentType::Juno60));
866 assert!(effect.child);
867 assert_eq!(effect.patterns, 0xFF, "every slot's lanes moved");
868 assert!(seq(&track).lane().is_pitched());
869 assert_eq!(track.instrument_type, Some(InstrumentType::Juno60));
870 assert_eq!(
871 track.synth_params.len(),
872 crate::preset::param_count(InstrumentType::Juno60)
873 );
874
875 dispatch(&mut track, SeqOp::SetChild(InstrumentType::DrumRack));
877 assert_eq!(seq(&track).lane().note, DEFAULT_DRUM_LANES[0]);
878 }
879
880 #[test]
882 fn a_sequencer_cannot_drive_a_sequencer() {
883 let mut track = drum_track();
884 assert!(dispatch(&mut track, SeqOp::SetChild(InstrumentType::Sequencer)).is_nothing());
885 assert_eq!(track.instrument_type, Some(InstrumentType::DrumRack));
886 }
887
888 #[test]
891 fn queueing_the_live_slot_is_not_a_queue() {
892 let mut track = drum_track();
893 assert!(dispatch(&mut track, SeqOp::QueueSlot(0)).is_nothing());
894 assert_eq!(seq(&track).queued_slot(), None);
895 assert!(!dispatch(&mut track, SeqOp::QueueSlot(4)).is_nothing());
896 assert_eq!(seq(&track).queued_slot(), Some(4));
897 }
898
899 #[test]
902 fn a_chain_takes_over_from_the_queue() {
903 let mut track = drum_track();
904 dispatch(&mut track, SeqOp::QueueSlot(2));
905 dispatch(&mut track, SeqOp::PushChainEntry { slot: 0, repeats: 2 });
906 assert_eq!(seq(&track).queued_slot(), None);
907 assert!(dispatch(&mut track, SeqOp::QueueSlot(3)).is_nothing());
908
909 dispatch(&mut track, SeqOp::ClearChain);
910 assert!(!seq(&track).is_chained());
911 assert!(!dispatch(&mut track, SeqOp::QueueSlot(3)).is_nothing());
912 }
913
914 #[test]
915 fn chain_entries_can_be_added_edited_and_removed() {
916 let mut track = drum_track();
917 dispatch(&mut track, SeqOp::PushChainEntry { slot: 0, repeats: 4 });
918 dispatch(&mut track, SeqOp::PushChainEntry { slot: 1, repeats: 0 });
919 dispatch(&mut track, SeqOp::PushChainEntry { slot: 2, repeats: 3 });
920 assert_eq!(
921 seq(&track).chain().iter().map(|e| (e.slot, e.repeats)).collect::<Vec<_>>(),
922 vec![(0, 4), (1, 1), (2, 3)],
923 "a repeat count of zero is one time through"
924 );
925
926 dispatch(&mut track, SeqOp::SetChainRepeats { index: 1, repeats: 8 });
927 dispatch(&mut track, SeqOp::RemoveChainEntry(0));
928 assert_eq!(
929 seq(&track).chain().iter().map(|e| (e.slot, e.repeats)).collect::<Vec<_>>(),
930 vec![(1, 8), (2, 3)]
931 );
932
933 assert!(dispatch(&mut track, SeqOp::RemoveChainEntry(9)).is_nothing());
934 }
935
936 #[test]
939 fn the_chain_is_bounded() {
940 let mut track = drum_track();
941 for _ in 0..MAX_CHAIN + 4 {
942 dispatch(&mut track, SeqOp::PushChainEntry { slot: 1, repeats: 1 });
943 }
944 assert_eq!(seq(&track).chain().len(), MAX_CHAIN);
945 }
946
947 #[test]
948 fn copying_a_pattern_marks_the_slot_it_landed_in() {
949 let mut track = drum_track();
950 dispatch(&mut track, SeqOp::ToggleStep);
951 let effect = dispatch(&mut track, SeqOp::CopyPattern { from: 0, to: 5 });
952 assert_eq!(effect.slots().collect::<Vec<_>>(), vec![5]);
953 assert!(seq(&track).pattern_at(5).lanes[0].steps[0].on);
954 assert!(dispatch(&mut track, SeqOp::CopyPattern { from: 3, to: 3 }).is_nothing());
955 }
956
957 #[test]
959 fn clearing_a_pattern_leaves_the_lanes_pointed_where_they_were() {
960 let mut track = drum_track();
961 dispatch(&mut track, SeqOp::ToggleStep);
962 dispatch(&mut track, SeqOp::ClearPattern);
963 assert!(!seq(&track).step().on);
964 assert_eq!(seq(&track).lane().note, DEFAULT_DRUM_LANES[0]);
965 }
966
967 #[test]
972 fn step_record_writes_a_note_and_advances() {
973 let mut track = melodic_track();
974 assert!(dispatch(&mut track, SeqOp::RecordNotes(HeldNotes::new(&[64]))).is_nothing());
976
977 dispatch(&mut track, SeqOp::ArmStepRecord(true));
978 dispatch(&mut track, SeqOp::RecordNotes(HeldNotes::new(&[64])));
979 assert!(seq(&track).pattern().lanes[0].steps[0].on);
980 assert_eq!(seq(&track).pattern().lanes[0].steps[0].root(), 64);
981 assert_eq!(seq(&track).step_cursor(), 1);
982
983 dispatch(&mut track, SeqOp::RecordRest);
985 assert!(!seq(&track).pattern().lanes[0].steps[1].on);
986 assert_eq!(seq(&track).step_cursor(), 2);
987 }
988
989 #[test]
992 fn step_record_names_the_chord_that_was_played() {
993 let mut track = melodic_track();
994 dispatch(&mut track, SeqOp::ArmStepRecord(true));
995 dispatch(&mut track, SeqOp::RecordNotes(HeldNotes::new(&[60, 63, 67, 70])));
996
997 let step = seq(&track).pattern().lanes[0].steps[0];
998 assert_eq!(step.root(), 60);
999 assert_eq!(step.chord_kind(), Chord::Min7);
1000 assert_eq!(step.voicing_kind(), Voicing::Close);
1001 }
1002
1003 #[test]
1006 fn step_record_ties_the_step_behind_the_cursor() {
1007 let mut track = melodic_track();
1008 dispatch(&mut track, SeqOp::ArmStepRecord(true));
1009 dispatch(&mut track, SeqOp::RecordNotes(HeldNotes::new(&[60])));
1010 dispatch(&mut track, SeqOp::RecordTie);
1011 assert_eq!(seq(&track).pattern().lanes[0].steps[0].gate, Step::TIE);
1012 assert_eq!(seq(&track).step_cursor(), 2);
1013 }
1014
1015 #[test]
1018 fn step_record_on_a_drum_lane_writes_a_hit() {
1019 let mut track = drum_track();
1020 dispatch(&mut track, SeqOp::ArmStepRecord(true));
1021 dispatch(&mut track, SeqOp::RecordNotes(HeldNotes::new(&[38])));
1022 assert!(seq(&track).pattern().lanes[0].steps[0].on);
1023 assert_eq!(seq(&track).pattern().lanes[0].steps[0].octave, Step::silent().octave);
1024 assert_eq!(seq(&track).lane().note, DEFAULT_DRUM_LANES[0]);
1025 }
1026
1027 #[test]
1030 fn an_unnameable_chord_falls_back_to_its_lowest_note() {
1031 let mut track = melodic_track();
1032 dispatch(&mut track, SeqOp::ArmStepRecord(true));
1033 dispatch(&mut track, SeqOp::RecordNotes(HeldNotes::new(&[60, 61, 62])));
1034 let step = seq(&track).pattern().lanes[0].steps[0];
1035 assert_eq!(step.root(), 60);
1036 assert_eq!(step.chord_kind(), Chord::None);
1037 }
1038
1039 #[test]
1040 fn held_notes_are_bounded_and_sorted() {
1041 let held = HeldNotes::new(&[67, 60, 64, 72, 76, 79, 83]);
1042 assert_eq!(held.as_slice(), &[60, 64, 67, 72, 76]);
1043 assert!(HeldNotes::new(&[]).as_slice().is_empty());
1044 }
1045
1046 #[test]
1049 fn a_lane_can_be_pinned_and_unpinned() {
1050 let mut track = melodic_track();
1051 assert!(seq(&track).lane().is_pitched());
1052 dispatch(&mut track, SeqOp::SetLaneNote(42));
1053 assert!(!seq(&track).lane().is_pitched());
1054 dispatch(&mut track, SeqOp::SetLaneNote(Lane::FROM_STEP));
1055 assert!(seq(&track).lane().is_pitched());
1056 }
1057
1058 #[test]
1059 fn mute_and_solo_are_per_lane() {
1060 let mut track = drum_track();
1061 dispatch(&mut track, SeqOp::SelectLane(2));
1062 dispatch(&mut track, SeqOp::ToggleLaneMute);
1063 assert!(seq(&track).pattern().lanes[2].muted);
1064 assert!(!seq(&track).pattern().lanes[0].muted);
1065 dispatch(&mut track, SeqOp::ToggleLaneSolo);
1066 assert!(seq(&track).pattern().lanes[2].soloed);
1067 }
1068
1069 #[test]
1072 fn every_control_stops_at_its_ends() {
1073 let mut track = drum_track();
1074 for _ in 0..200 {
1075 dispatch(&mut track, SeqOp::NudgeSwing(1));
1076 dispatch(&mut track, SeqOp::NudgeBaseVelocity(1));
1077 dispatch(&mut track, SeqOp::NudgeAccentVelocity(1));
1078 dispatch(&mut track, SeqOp::NudgeDefaultGate(1));
1079 dispatch(&mut track, SeqOp::CycleRate(1));
1080 dispatch(&mut track, SeqOp::NudgeGate(1));
1081 dispatch(&mut track, SeqOp::CycleLength(1));
1082 dispatch(&mut track, SeqOp::CycleMode(1));
1083 dispatch(&mut track, SeqOp::CycleSwitchQuant(1));
1084 }
1085 {
1086 let block = seq(&track).pattern();
1087 assert_eq!(block.swing, PatternBlock::MAX_SWING);
1088 assert_eq!(block.base_vel, 127);
1089 assert_eq!(block.accent_vel, 127);
1090 assert_eq!(block.default_gate, Step::MAX_GATE);
1091 assert_eq!(block.rate, Rate::SixteenthTriplet);
1092 assert_eq!(block.steps, 32);
1093 assert_eq!(block.mode, Mode::Locrian);
1094 }
1095 assert_eq!(seq(&track).switch_quant(), SwitchQuant::Immediate);
1096
1097 for _ in 0..200 {
1098 dispatch(&mut track, SeqOp::NudgeSwing(-1));
1099 dispatch(&mut track, SeqOp::NudgeBaseVelocity(-1));
1100 dispatch(&mut track, SeqOp::NudgeAccentVelocity(-1));
1101 dispatch(&mut track, SeqOp::NudgeDefaultGate(-1));
1102 dispatch(&mut track, SeqOp::CycleRate(-1));
1103 dispatch(&mut track, SeqOp::CycleLength(-1));
1104 dispatch(&mut track, SeqOp::CycleMode(-1));
1105 dispatch(&mut track, SeqOp::CycleSwitchQuant(-1));
1106 }
1107 {
1108 let block = seq(&track).pattern();
1109 assert_eq!(block.swing, PatternBlock::MIN_SWING);
1110 assert_eq!(block.base_vel, 1);
1111 assert_eq!(block.default_gate, Step::MIN_GATE);
1112 assert_eq!(block.rate, Rate::Quarter);
1113 assert_eq!(block.steps, 4);
1114 assert_eq!(block.mode, Mode::Chromatic);
1115 }
1116 assert_eq!(seq(&track).switch_quant(), SwitchQuant::PatternEnd);
1117 }
1118
1119 #[test]
1122 fn out_of_range_indices_are_clamped() {
1123 let mut track = drum_track();
1124 dispatch(&mut track, SeqOp::SelectSlot(200));
1125 dispatch(&mut track, SeqOp::SelectLane(200));
1126 dispatch(&mut track, SeqOp::SelectStep(200));
1127 dispatch(&mut track, SeqOp::QueueSlot(200));
1128 dispatch(&mut track, SeqOp::SetTonic(200));
1129 dispatch(&mut track, SeqOp::CopyPattern { from: 200, to: 201 });
1130 let state = seq(&track);
1131 assert_eq!(state.selected_slot(), SLOTS as u8 - 1);
1132 assert_eq!(state.lane_cursor(), LANES - 1);
1133 assert_eq!(state.step_cursor(), MAX_STEPS - 1);
1134 assert_eq!(state.queued_slot(), Some(SLOTS as u8 - 1));
1135 assert!(state.pattern().tonic < 12);
1136 }
1137}