1pub const LANES: usize = 8;
71
72pub const MAX_STEPS: usize = 32;
75
76pub const SLOTS: usize = 8;
78
79pub const MAX_CHAIN: usize = 16;
82
83pub const MAX_PENDING_OFFS: usize = 32;
91
92pub const STEP_COUNTS: [u8; 6] = [4, 8, 12, 16, 24, 32];
98
99const MAX_STEP_SCAN: i64 = 64;
106
107const MAX_SEGMENTS: usize = 4;
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
122pub enum Rate {
123 Quarter,
124 Eighth,
125 #[default]
126 Sixteenth,
127 ThirtySecond,
128 EighthTriplet,
129 SixteenthTriplet,
130}
131
132impl Rate {
133 pub const ALL: [Rate; 6] = [
135 Self::Quarter,
136 Self::Eighth,
137 Self::Sixteenth,
138 Self::ThirtySecond,
139 Self::EighthTriplet,
140 Self::SixteenthTriplet,
141 ];
142
143 #[must_use]
145 pub const fn ticks(self) -> i64 {
146 match self {
147 Self::Quarter => 960,
148 Self::Eighth => 480,
149 Self::Sixteenth => 240,
150 Self::ThirtySecond => 120,
151 Self::EighthTriplet => 320,
152 Self::SixteenthTriplet => 160,
153 }
154 }
155
156 #[must_use]
157 pub const fn label(self) -> &'static str {
158 match self {
159 Self::Quarter => "1/4",
160 Self::Eighth => "1/8",
161 Self::Sixteenth => "1/16",
162 Self::ThirtySecond => "1/32",
163 Self::EighthTriplet => "1/8T",
164 Self::SixteenthTriplet => "1/16T",
165 }
166 }
167
168 #[must_use]
170 pub const fn index(self) -> u8 {
171 match self {
172 Self::Quarter => 0,
173 Self::Eighth => 1,
174 Self::Sixteenth => 2,
175 Self::ThirtySecond => 3,
176 Self::EighthTriplet => 4,
177 Self::SixteenthTriplet => 5,
178 }
179 }
180
181 #[must_use]
186 pub fn from_index(index: u8) -> Self {
187 Self::ALL.get(index as usize).copied().unwrap_or_default()
188 }
189
190 #[must_use]
192 pub fn stepped(self, delta: i32) -> Self {
193 let target = (i32::from(self.index()) + delta).clamp(0, Self::ALL.len() as i32 - 1);
194 Self::ALL[target as usize]
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
206pub enum SwitchQuant {
207 #[default]
210 PatternEnd,
211 Bar,
213 Beat,
215 Immediate,
217}
218
219impl SwitchQuant {
220 pub const ALL: [SwitchQuant; 4] = [Self::PatternEnd, Self::Bar, Self::Beat, Self::Immediate];
221
222 #[must_use]
223 pub const fn label(self) -> &'static str {
224 match self {
225 Self::PatternEnd => "pattern",
226 Self::Bar => "bar",
227 Self::Beat => "beat",
228 Self::Immediate => "now",
229 }
230 }
231
232 #[must_use]
233 pub const fn index(self) -> u8 {
234 match self {
235 Self::PatternEnd => 0,
236 Self::Bar => 1,
237 Self::Beat => 2,
238 Self::Immediate => 3,
239 }
240 }
241
242 #[must_use]
243 pub fn from_index(index: u8) -> Self {
244 Self::ALL.get(index as usize).copied().unwrap_or_default()
245 }
246
247 #[must_use]
248 pub fn stepped(self, delta: i32) -> Self {
249 let target = (i32::from(self.index()) + delta).clamp(0, Self::ALL.len() as i32 - 1);
250 Self::ALL[target as usize]
251 }
252
253 #[must_use]
261 pub fn boundary(self, now: i64, pattern_ticks: i64) -> i64 {
262 let grid = match self {
263 Self::PatternEnd => pattern_ticks,
264 Self::Bar => crate::transport::Transport::PPQ * 4,
265 Self::Beat => crate::transport::Transport::PPQ,
266 Self::Immediate => return now,
267 };
268 if grid <= 0 {
269 return now;
270 }
271 (now + grid - 1).div_euclid(grid) * grid
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
286pub enum Mode {
287 #[default]
288 Chromatic,
289 Ionian,
290 Dorian,
291 Phrygian,
292 Lydian,
293 Mixolydian,
294 Aeolian,
295 Locrian,
296}
297
298const IONIAN: [i32; 7] = [0, 2, 4, 5, 7, 9, 11];
300
301const IONIAN_TRIADS: [Chord; 7] = [
304 Chord::Maj,
305 Chord::Min,
306 Chord::Min,
307 Chord::Maj,
308 Chord::Maj,
309 Chord::Min,
310 Chord::Dim,
311];
312
313const IONIAN_SEVENTHS: [[i32; 4]; 7] = [
320 [0, 4, 7, 11], [0, 3, 7, 10], [0, 3, 7, 10], [0, 4, 7, 11], [0, 4, 7, 10], [0, 3, 7, 10], [0, 3, 6, 10], ];
328
329impl Mode {
330 pub const ALL: [Mode; 8] = [
331 Self::Chromatic,
332 Self::Ionian,
333 Self::Dorian,
334 Self::Phrygian,
335 Self::Lydian,
336 Self::Mixolydian,
337 Self::Aeolian,
338 Self::Locrian,
339 ];
340
341 #[must_use]
342 pub const fn label(self) -> &'static str {
343 match self {
344 Self::Chromatic => "chromatic",
345 Self::Ionian => "ionian",
346 Self::Dorian => "dorian",
347 Self::Phrygian => "phrygian",
348 Self::Lydian => "lydian",
349 Self::Mixolydian => "mixolydian",
350 Self::Aeolian => "aeolian",
351 Self::Locrian => "locrian",
352 }
353 }
354
355 #[must_use]
356 pub const fn index(self) -> u8 {
357 match self {
358 Self::Chromatic => 0,
359 Self::Ionian => 1,
360 Self::Dorian => 2,
361 Self::Phrygian => 3,
362 Self::Lydian => 4,
363 Self::Mixolydian => 5,
364 Self::Aeolian => 6,
365 Self::Locrian => 7,
366 }
367 }
368
369 #[must_use]
370 pub fn from_index(index: u8) -> Self {
371 Self::ALL.get(index as usize).copied().unwrap_or_default()
372 }
373
374 #[must_use]
375 pub fn stepped(self, delta: i32) -> Self {
376 let target = (i32::from(self.index()) + delta).clamp(0, Self::ALL.len() as i32 - 1);
377 Self::ALL[target as usize]
378 }
379
380 #[must_use]
383 pub const fn rotation(self) -> Option<usize> {
384 match self {
385 Self::Chromatic => None,
386 Self::Ionian => Some(0),
387 Self::Dorian => Some(1),
388 Self::Phrygian => Some(2),
389 Self::Lydian => Some(3),
390 Self::Mixolydian => Some(4),
391 Self::Aeolian => Some(5),
392 Self::Locrian => Some(6),
393 }
394 }
395
396 #[must_use]
401 pub fn scale(self) -> Option<[i32; 7]> {
402 let rot = self.rotation()?;
403 let base = IONIAN[rot];
404 let mut out = [0; 7];
405 for (i, slot) in out.iter_mut().enumerate() {
406 *slot = (IONIAN[(i + rot) % 7] - base).rem_euclid(12);
407 }
408 Some(out)
409 }
410
411 #[must_use]
417 pub fn degree_of(self, note: u8, tonic: u8) -> Option<usize> {
418 let scale = self.scale()?;
419 let pitch_class = (i32::from(note) - i32::from(tonic % 12)).rem_euclid(12);
420 scale.iter().position(|&s| s == pitch_class)
421 }
422
423 #[must_use]
432 pub fn walk(self, note: u8, tonic: u8, steps: i32) -> u8 {
433 let Some(scale) = self.scale() else {
434 return (i32::from(note) + steps).clamp(0, 127) as u8;
435 };
436 let tonic = i32::from(tonic % 12);
437 let relative = i32::from(note) - tonic;
438 let octave = relative.div_euclid(12);
439 let pitch_class = relative.rem_euclid(12);
440
441 let (degree, on_scale) = match scale.iter().position(|&s| s == pitch_class) {
444 Some(d) => (d as i32, true),
445 None => (scale.iter().filter(|&&s| s < pitch_class).count() as i32 - 1, false),
446 };
447 let target = degree + steps + i32::from(!on_scale && steps < 0);
452 let target_octave = octave + target.div_euclid(7);
453 let target_degree = target.rem_euclid(7) as usize;
454 (tonic + target_octave * 12 + scale[target_degree]).clamp(0, 127) as u8
455 }
456}
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
469pub enum Chord {
470 #[default]
471 None,
472 Fifth,
473 Octave,
474 Diatonic,
475 Diatonic7,
476 Maj,
477 Min,
478 Dim,
479 Sus2,
480 Sus4,
481 Maj6,
482 Min6,
483 Dom7,
484 Min7,
485 Maj7,
486 Quartal,
487}
488
489impl Chord {
490 pub const ALL: [Chord; 16] = [
491 Self::None,
492 Self::Fifth,
493 Self::Octave,
494 Self::Diatonic,
495 Self::Diatonic7,
496 Self::Maj,
497 Self::Min,
498 Self::Dim,
499 Self::Sus2,
500 Self::Sus4,
501 Self::Maj6,
502 Self::Min6,
503 Self::Dom7,
504 Self::Min7,
505 Self::Maj7,
506 Self::Quartal,
507 ];
508
509 #[must_use]
511 pub const fn index(self) -> u8 {
512 match self {
513 Self::None => 0,
514 Self::Fifth => 1,
515 Self::Octave => 2,
516 Self::Diatonic => 3,
517 Self::Diatonic7 => 4,
518 Self::Maj => 5,
519 Self::Min => 6,
520 Self::Dim => 7,
521 Self::Sus2 => 8,
522 Self::Sus4 => 9,
523 Self::Maj6 => 10,
524 Self::Min6 => 11,
525 Self::Dom7 => 12,
526 Self::Min7 => 13,
527 Self::Maj7 => 14,
528 Self::Quartal => 15,
529 }
530 }
531
532 #[must_use]
533 pub fn from_index(index: u8) -> Self {
534 Self::ALL.get(index as usize).copied().unwrap_or_default()
535 }
536
537 #[must_use]
538 pub fn stepped(self, delta: i32) -> Self {
539 let target = (i32::from(self.index()) + delta).clamp(0, Self::ALL.len() as i32 - 1);
540 Self::ALL[target as usize]
541 }
542
543 fn intervals(self, root: u8, mode: Mode, tonic: u8, out: &mut [i32; 4]) -> usize {
549 let fixed: &[i32] = match self {
550 Self::None => &[0],
551 Self::Fifth => &[0, 7],
552 Self::Octave => &[0, 12],
553 Self::Maj => &[0, 4, 7],
554 Self::Min => &[0, 3, 7],
555 Self::Dim => &[0, 3, 6],
556 Self::Sus2 => &[0, 2, 7],
557 Self::Sus4 => &[0, 5, 7],
558 Self::Maj6 => &[0, 4, 7, 9],
559 Self::Min6 => &[0, 3, 7, 9],
560 Self::Dom7 => &[0, 4, 7, 10],
561 Self::Min7 => &[0, 3, 7, 10],
562 Self::Maj7 => &[0, 4, 7, 11],
563 Self::Quartal => &[0, 5, 10],
566 Self::Diatonic | Self::Diatonic7 => {
567 let seventh = self == Self::Diatonic7;
568 let quality = mode
569 .degree_of(root, tonic)
570 .map(|degree| (degree + mode.rotation().unwrap_or(0)) % 7);
571 return match (quality, seventh) {
572 (Some(d), false) => IONIAN_TRIADS[d].intervals(root, mode, tonic, out),
573 (Some(d), true) => {
574 out.copy_from_slice(&IONIAN_SEVENTHS[d]);
575 4
576 }
577 (None, false) => Self::Maj.intervals(root, mode, tonic, out),
578 (None, true) => Self::Maj7.intervals(root, mode, tonic, out),
579 };
580 }
581 };
582 out[..fixed.len()].copy_from_slice(fixed);
583 fixed.len()
584 }
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
592pub enum Voicing {
593 #[default]
594 Close,
595 Drop2,
599 First,
601 Second,
603}
604
605impl Voicing {
606 pub const ALL: [Voicing; 4] = [Self::Close, Self::Drop2, Self::First, Self::Second];
607
608 #[must_use]
609 pub const fn label(self) -> &'static str {
610 match self {
611 Self::Close => "close",
612 Self::Drop2 => "drop-2",
613 Self::First => "1st inv",
614 Self::Second => "2nd inv",
615 }
616 }
617
618 #[must_use]
619 pub const fn index(self) -> u8 {
620 match self {
621 Self::Close => 0,
622 Self::Drop2 => 1,
623 Self::First => 2,
624 Self::Second => 3,
625 }
626 }
627
628 #[must_use]
629 pub fn from_index(index: u8) -> Self {
630 Self::ALL.get(index as usize).copied().unwrap_or_default()
631 }
632
633 #[must_use]
634 pub fn stepped(self, delta: i32) -> Self {
635 let target = (i32::from(self.index()) + delta).clamp(0, Self::ALL.len() as i32 - 1);
636 Self::ALL[target as usize]
637 }
638}
639
640pub const MAX_CHORD_NOTES: usize = 5;
643
644#[must_use]
653pub fn chord_notes(
654 root: u8,
655 chord: Chord,
656 voicing: Voicing,
657 root_below: bool,
658 mode: Mode,
659 tonic: u8,
660 out: &mut [u8; MAX_CHORD_NOTES],
661) -> usize {
662 let mut intervals = [0i32; 4];
663 let count = chord.intervals(root, mode, tonic, &mut intervals);
664
665 let mut voices = [0i32; MAX_CHORD_NOTES];
666 for (slot, interval) in voices.iter_mut().zip(&intervals[..count]) {
667 *slot = i32::from(root) + interval;
668 }
669 let mut len = count;
670
671 match voicing {
674 Voicing::Close => {}
675 Voicing::Drop2 => {
676 if len >= 2 {
677 voices[len - 2] -= 12;
678 }
679 }
680 Voicing::First => {
681 if len >= 2 {
682 voices[0] += 12;
683 }
684 }
685 Voicing::Second => {
686 if len >= 3 {
687 voices[0] += 12;
688 voices[1] += 12;
689 } else if len >= 2 {
690 voices[0] += 12;
691 }
692 }
693 }
694
695 if root_below && len < MAX_CHORD_NOTES {
696 voices[len] = i32::from(root) - 12;
697 len += 1;
698 }
699
700 for voice in &mut voices[..len] {
702 while *voice < 0 {
703 *voice += 12;
704 }
705 while *voice > 127 {
706 *voice -= 12;
707 }
708 }
709 voices[..len].sort_unstable();
710
711 let mut written = 0;
712 for i in 0..len {
713 if i > 0 && voices[i] == voices[i - 1] {
714 continue;
715 }
716 out[written] = voices[i] as u8;
717 written += 1;
718 }
719 written
720}
721
722#[derive(Debug, Clone, Copy, PartialEq, Eq)]
732pub struct Step {
733 pub on: bool,
735 pub octave: u8,
739 pub key: u8,
741 pub chord: u8,
743 pub voicing: u8,
745 pub accent: bool,
749 pub gate: u8,
751 pub reserved: [u8; 2],
753}
754
755impl Step {
756 pub const TIE: u8 = 255;
762
763 pub const MIN_GATE: u8 = 5;
766
767 pub const MAX_GATE: u8 = 200;
769
770 pub const ROOT_BELOW: u8 = 0b0000_0100;
775
776 #[must_use]
778 pub const fn silent() -> Self {
779 Self {
780 on: false,
781 octave: 5,
782 key: 0,
783 chord: 0,
784 voicing: 0,
785 accent: false,
786 gate: 50,
787 reserved: [0; 2],
788 }
789 }
790
791 #[must_use]
793 pub fn root(self) -> u8 {
794 (u32::from(self.octave) * 12 + u32::from(self.key)).min(127) as u8
795 }
796
797 #[must_use]
798 pub fn chord_kind(self) -> Chord {
799 Chord::from_index(self.chord)
800 }
801
802 #[must_use]
803 pub fn voicing_kind(self) -> Voicing {
804 Voicing::from_index(self.voicing & 0b11)
805 }
806
807 #[must_use]
808 pub fn root_below(self) -> bool {
809 self.voicing & Self::ROOT_BELOW != 0
810 }
811
812 #[must_use]
819 pub fn gate_ticks(self, ticks_per_step: i64) -> Option<i64> {
820 if self.gate == Self::TIE {
821 return None;
822 }
823 let percent = i64::from(self.gate.clamp(Self::MIN_GATE, Self::MAX_GATE));
824 Some((ticks_per_step * percent / 100).max(1))
825 }
826}
827
828impl Default for Step {
829 fn default() -> Self {
830 Self::silent()
831 }
832}
833
834#[derive(Debug, Clone, Copy, PartialEq, Eq)]
838pub struct Lane {
839 pub note: u8,
846 pub muted: bool,
847 pub soloed: bool,
848 pub steps: [Step; MAX_STEPS],
849}
850
851impl Lane {
852 pub const FROM_STEP: u8 = 0xFF;
855
856 #[must_use]
858 pub const fn empty() -> Self {
859 Self {
860 note: Self::FROM_STEP,
861 muted: false,
862 soloed: false,
863 steps: [Step::silent(); MAX_STEPS],
864 }
865 }
866
867 #[must_use]
869 pub const fn drum(note: u8) -> Self {
870 Self { note, ..Self::empty() }
871 }
872
873 #[must_use]
875 pub const fn is_pitched(&self) -> bool {
876 self.note == Self::FROM_STEP
877 }
878}
879
880impl Default for Lane {
881 fn default() -> Self {
882 Self::empty()
883 }
884}
885
886#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
890pub struct ChainEntry {
891 pub slot: u8,
892 pub repeats: u8,
894}
895
896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
911pub struct PatternBlock {
912 pub steps: u8,
917 pub rate: Rate,
918 pub swing: u8,
920 pub base_vel: u8,
922 pub accent_vel: u8,
924 pub default_gate: u8,
926 pub mode: Mode,
928 pub tonic: u8,
931 pub lanes: [Lane; LANES],
932 pub playing: bool,
934 pub pending_slot: Option<u8>,
939 pub switch_quant: SwitchQuant,
940 pub chain: [ChainEntry; MAX_CHAIN],
941 pub chain_len: u8,
944}
945
946impl PatternBlock {
947 pub const SIZE: usize = std::mem::size_of::<Self>();
950
951 pub const MIN_SWING: u8 = 50;
953
954 pub const MAX_SWING: u8 = 75;
958
959 #[must_use]
968 pub const fn empty() -> Self {
969 Self {
970 steps: 16,
971 rate: Rate::Sixteenth,
972 swing: Self::MIN_SWING,
973 base_vel: 100,
974 accent_vel: 127,
975 default_gate: 50,
976 mode: Mode::Chromatic,
977 tonic: 0,
978 lanes: [Lane::empty(); LANES],
979 playing: true,
980 pending_slot: None,
981 switch_quant: SwitchQuant::PatternEnd,
982 chain: [ChainEntry { slot: 0, repeats: 1 }; MAX_CHAIN],
983 chain_len: 0,
984 }
985 }
986
987 #[must_use]
991 pub fn step_count(&self) -> usize {
992 (self.steps as usize).clamp(1, MAX_STEPS)
993 }
994
995 #[must_use]
996 pub fn ticks_per_step(&self) -> i64 {
997 self.rate.ticks()
998 }
999
1000 #[must_use]
1002 pub fn length_ticks(&self) -> i64 {
1003 self.ticks_per_step() * self.step_count() as i64
1004 }
1005
1006 #[must_use]
1017 pub fn swing_offset(&self, step_index: usize) -> i64 {
1018 if step_index % 2 == 0 {
1019 return 0;
1020 }
1021 let swing = i64::from(self.swing.clamp(Self::MIN_SWING, Self::MAX_SWING));
1022 (swing - i64::from(Self::MIN_SWING)) * 2 * self.ticks_per_step() / 100
1023 }
1024
1025 fn max_swing_offset(&self) -> i64 {
1028 let swing = i64::from(self.swing.clamp(Self::MIN_SWING, Self::MAX_SWING));
1029 (swing - i64::from(Self::MIN_SWING)) * 2 * self.ticks_per_step() / 100
1030 }
1031
1032 #[must_use]
1038 pub fn onset(&self, origin: i64, index: i64) -> i64 {
1039 let steps = self.step_count() as i64;
1040 let in_pattern = index.rem_euclid(steps) as usize;
1041 origin + index * self.ticks_per_step() + self.swing_offset(in_pattern)
1042 }
1043
1044 #[must_use]
1047 pub fn step_at(&self, origin: i64, tick: i64) -> usize {
1048 let steps = self.step_count() as i64;
1049 (tick - origin).div_euclid(self.ticks_per_step()).rem_euclid(steps) as usize
1050 }
1051
1052 #[must_use]
1054 pub fn lane_audible(&self, lane: usize) -> bool {
1055 let Some(l) = self.lanes.get(lane) else { return false };
1056 if l.muted {
1057 return false;
1058 }
1059 let any_solo = self.lanes.iter().any(|l| l.soloed);
1060 !any_solo || l.soloed
1061 }
1062
1063 #[must_use]
1065 pub fn chain_entries(&self) -> &[ChainEntry] {
1066 &self.chain[..(self.chain_len as usize).min(MAX_CHAIN)]
1067 }
1068}
1069
1070impl Default for PatternBlock {
1071 fn default() -> Self {
1072 Self::empty()
1073 }
1074}
1075
1076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1085pub struct PatternEvent {
1086 pub tick: i64,
1087 pub status: u8,
1088 pub data1: u8,
1089 pub data2: u8,
1090}
1091
1092impl PatternEvent {
1093 #[must_use]
1094 pub const fn note_on(tick: i64, note: u8, velocity: u8) -> Self {
1095 Self { tick, status: 0x90, data1: note, data2: velocity }
1096 }
1097
1098 #[must_use]
1099 pub const fn note_off(tick: i64, note: u8) -> Self {
1100 Self { tick, status: 0x80, data1: note, data2: 0 }
1101 }
1102
1103 #[must_use]
1104 pub const fn is_note_on(&self) -> bool {
1105 self.status == 0x90 && self.data2 > 0
1106 }
1107}
1108
1109pub trait EventSink {
1124 fn accept(&mut self, event: PatternEvent) -> bool;
1128}
1129
1130impl EventSink for Vec<PatternEvent> {
1131 fn accept(&mut self, event: PatternEvent) -> bool {
1132 self.push(event);
1133 true
1134 }
1135}
1136
1137#[derive(Debug, Clone, Copy)]
1155pub struct PlaybackWindow {
1156 from: i64,
1157 to: i64,
1158 position: i64,
1163 ticks_per_sample: f64,
1164 frames: u32,
1165 continuous: bool,
1166}
1167
1168impl PlaybackWindow {
1169 pub const MAX_TICK_GAP: i64 = 1;
1178
1179 #[must_use]
1198 pub fn for_block(
1199 position: i64,
1200 frames: u32,
1201 ticks_per_sample: f64,
1202 loop_region: Option<(i64, i64)>,
1203 previous: Option<Self>,
1204 ) -> Self {
1205 let span = (f64::from(frames) * ticks_per_sample) as i64;
1206
1207 let (from, continuous) = match (previous, loop_region) {
1208 (Some(prev), Some((loop_start, _))) if position < prev.position => (loop_start, false),
1209 (Some(prev), _) if prev.to <= position && position - prev.to <= Self::MAX_TICK_GAP => {
1210 (prev.to, true)
1211 }
1212 _ => (position, false),
1213 };
1214
1215 let mut to = position + span;
1216 if let Some((_, loop_end)) = loop_region {
1217 if loop_end > from {
1218 to = to.min(loop_end);
1219 }
1220 }
1221
1222 Self {
1223 from,
1224 to: to.max(from),
1225 position,
1226 ticks_per_sample,
1227 frames,
1228 continuous,
1229 }
1230 }
1231
1232 #[must_use]
1236 pub fn narrowed(&self, from: i64, to: i64) -> Self {
1237 Self { from, to: to.max(from), ..*self }
1238 }
1239
1240 #[must_use]
1241 pub const fn from(&self) -> i64 {
1242 self.from
1243 }
1244
1245 #[must_use]
1246 pub const fn to(&self) -> i64 {
1247 self.to
1248 }
1249
1250 #[must_use]
1254 pub const fn is_continuous(&self) -> bool {
1255 self.continuous
1256 }
1257
1258 #[must_use]
1259 pub const fn is_empty(&self) -> bool {
1260 self.to <= self.from
1261 }
1262
1263 #[must_use]
1264 pub const fn contains(&self, tick: i64) -> bool {
1265 tick >= self.from && tick < self.to
1266 }
1267
1268 #[must_use]
1276 pub fn sample_offset(&self, tick: i64) -> u32 {
1277 let last = self.frames.saturating_sub(1);
1278 let offset = tick - self.from;
1279 if offset <= 0 || self.ticks_per_sample <= 0.0 {
1280 return 0;
1281 }
1282 let samples = (offset as f64 / self.ticks_per_sample) as i64;
1283 u32::try_from(samples).unwrap_or(last).min(last)
1284 }
1285}
1286
1287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1291struct PendingOff {
1292 note: u8,
1293 lane: u8,
1294 due: Option<i64>,
1297}
1298
1299#[derive(Debug, Clone, Copy)]
1305pub struct PendingOffs {
1306 entries: [PendingOff; MAX_PENDING_OFFS],
1307 len: usize,
1308}
1309
1310impl PendingOffs {
1311 #[must_use]
1312 pub const fn new() -> Self {
1313 Self {
1314 entries: [PendingOff { note: 0, lane: 0, due: None }; MAX_PENDING_OFFS],
1315 len: 0,
1316 }
1317 }
1318
1319 #[must_use]
1320 pub const fn len(&self) -> usize {
1321 self.len
1322 }
1323
1324 #[must_use]
1325 pub const fn is_empty(&self) -> bool {
1326 self.len == 0
1327 }
1328
1329 pub fn clear(&mut self) {
1332 self.len = 0;
1333 }
1334
1335 fn remove(&mut self, index: usize) -> PendingOff {
1336 let gone = self.entries[index];
1337 for i in index..self.len - 1 {
1338 self.entries[i] = self.entries[i + 1];
1339 }
1340 self.len -= 1;
1341 gone
1342 }
1343
1344 pub fn hold(
1350 &mut self,
1351 lane: usize,
1352 note: u8,
1353 due: Option<i64>,
1354 now: i64,
1355 out: &mut impl EventSink,
1356 ) {
1357 if self.len == MAX_PENDING_OFFS {
1358 let oldest = self.remove(0);
1359 out.accept(PatternEvent::note_off(now, oldest.note));
1360 }
1361 self.entries[self.len] = PendingOff { note, lane: lane as u8, due };
1362 self.len += 1;
1363 }
1364
1365 pub fn end_lane(&mut self, lane: usize, at: i64, out: &mut impl EventSink) {
1371 let lane = lane as u8;
1372 let mut i = 0;
1373 while i < self.len {
1374 if self.entries[i].lane == lane {
1375 let gone = self.remove(i);
1376 out.accept(PatternEvent::note_off(at, gone.note));
1377 } else {
1378 i += 1;
1379 }
1380 }
1381 }
1382
1383 pub fn emit_due_before(&mut self, tick: i64, out: &mut impl EventSink) {
1386 let mut i = 0;
1387 while i < self.len {
1388 match self.entries[i].due {
1389 Some(due) if due < tick => {
1390 let gone = self.remove(i);
1391 out.accept(PatternEvent::note_off(due, gone.note));
1392 }
1393 _ => i += 1,
1394 }
1395 }
1396 }
1397
1398 pub fn flush(&mut self, at: i64, out: &mut impl EventSink) {
1401 for i in 0..self.len {
1402 out.accept(PatternEvent::note_off(at, self.entries[i].note));
1403 }
1404 self.len = 0;
1405 }
1406}
1407
1408impl Default for PendingOffs {
1409 fn default() -> Self {
1410 Self::new()
1411 }
1412}
1413
1414pub fn generate(
1428 block: &PatternBlock,
1429 origin: i64,
1430 from: i64,
1431 to: i64,
1432 pending: &mut PendingOffs,
1433 out: &mut impl EventSink,
1434) {
1435 if to <= from {
1436 return;
1437 }
1438 let tps = block.ticks_per_step();
1439 let steps = block.step_count() as i64;
1440
1441 let first = (from - origin - block.max_swing_offset()).div_euclid(tps);
1445 let last = (to - origin).div_euclid(tps) + 1;
1446 let last = last.min(first + MAX_STEP_SCAN);
1447
1448 let mut chord = [0u8; MAX_CHORD_NOTES];
1449 for index in first..last {
1450 let onset = block.onset(origin, index);
1451 if onset < from || onset >= to {
1452 continue;
1453 }
1454 pending.emit_due_before(onset, out);
1458
1459 let step_index = index.rem_euclid(steps) as usize;
1460 for lane_index in 0..LANES {
1461 if !block.lane_audible(lane_index) {
1462 continue;
1463 }
1464 let lane = &block.lanes[lane_index];
1465 let step = lane.steps[step_index];
1466 if !step.on {
1467 continue;
1468 }
1469
1470 pending.end_lane(lane_index, onset, out);
1473
1474 let velocity = if step.accent { block.accent_vel } else { block.base_vel };
1475 let velocity = velocity.clamp(1, 127);
1476 let due = step.gate_ticks(tps).map(|len| onset + len);
1477
1478 let count = if lane.is_pitched() {
1479 chord_notes(
1480 step.root(),
1481 step.chord_kind(),
1482 step.voicing_kind(),
1483 step.root_below(),
1484 block.mode,
1485 block.tonic,
1486 &mut chord,
1487 )
1488 } else {
1489 chord[0] = lane.note;
1490 1
1491 };
1492
1493 for ¬e in &chord[..count] {
1494 if !out.accept(PatternEvent::note_on(onset, note, velocity)) {
1495 return;
1496 }
1497 pending.hold(lane_index, note, due, onset, out);
1498 }
1499 }
1500 }
1501
1502 pending.emit_due_before(to, out);
1503}
1504
1505pub fn compile_cycle(block: &PatternBlock, origin: i64, out: &mut Vec<PatternEvent>) {
1514 let length = block.length_ticks();
1515 let mut pending = PendingOffs::new();
1516 generate(block, origin, origin, origin + length, &mut pending, out);
1517 pending.flush(origin + length, out);
1518 out.sort_by_key(|e| e.tick);
1519}
1520
1521#[derive(Debug, Clone, Copy)]
1536pub struct PatternPlayer {
1537 slots: [PatternBlock; SLOTS],
1538 live: u8,
1540 playing: bool,
1543 pending_slot: Option<u8>,
1544 switch_quant: SwitchQuant,
1545 chain: [ChainEntry; MAX_CHAIN],
1546 chain_len: u8,
1547 pending: PendingOffs,
1549 active: bool,
1552 step: u8,
1554}
1555
1556impl PatternPlayer {
1557 #[must_use]
1558 pub fn new() -> Self {
1559 Self {
1560 slots: [PatternBlock::empty(); SLOTS],
1561 live: 0,
1562 playing: false,
1563 pending_slot: None,
1564 switch_quant: SwitchQuant::PatternEnd,
1565 chain: [ChainEntry { slot: 0, repeats: 1 }; MAX_CHAIN],
1566 chain_len: 0,
1567 pending: PendingOffs::new(),
1568 active: false,
1569 step: 0,
1570 }
1571 }
1572
1573 pub fn apply(&mut self, slot: u8, block: PatternBlock) {
1576 let slot = (slot as usize).min(SLOTS - 1);
1577 self.slots[slot] = block;
1578 self.playing = block.playing;
1579 self.switch_quant = block.switch_quant;
1580 self.chain = block.chain;
1581 self.chain_len = block.chain_len;
1582 self.pending_slot = block
1587 .pending_slot
1588 .filter(|&s| s != self.live && block.chain_len == 0);
1589 }
1590
1591 #[must_use]
1592 pub fn slot(&self, index: usize) -> &PatternBlock {
1593 &self.slots[index.min(SLOTS - 1)]
1594 }
1595
1596 #[must_use]
1597 pub fn live_slot(&self) -> u8 {
1598 self.live
1599 }
1600
1601 #[must_use]
1602 pub fn queued_slot(&self) -> Option<u8> {
1603 self.pending_slot
1604 }
1605
1606 #[must_use]
1609 pub fn current_step(&self) -> u8 {
1610 self.step
1611 }
1612
1613 #[must_use]
1614 pub fn is_playing(&self) -> bool {
1615 self.playing
1616 }
1617
1618 #[must_use]
1619 pub fn held_notes(&self) -> usize {
1620 self.pending.len()
1621 }
1622
1623 pub fn silence(&mut self) {
1626 self.pending.clear();
1627 self.active = false;
1628 }
1629
1630 #[must_use]
1637 pub fn countdown(&self, now: i64) -> Option<(u8, i64)> {
1638 let slot = self.pending_slot?;
1639 let block = &self.slots[self.live as usize];
1640 let at = self.switch_quant.boundary(now, block.length_ticks());
1641 Some((slot, (at - now).div_euclid(block.ticks_per_step())))
1642 }
1643
1644 fn locate(&self, tick: i64) -> (u8, i64, i64) {
1653 if let Some(found) = self.chain_at(tick) {
1654 return found;
1655 }
1656 let boundary = match self.pending_slot {
1657 Some(_) => {
1658 let block = &self.slots[self.live as usize];
1659 self.switch_quant.boundary(tick, block.length_ticks())
1660 }
1661 None => i64::MAX,
1662 };
1663 (self.live, 0, boundary)
1664 }
1665
1666 fn chain_at(&self, tick: i64) -> Option<(u8, i64, i64)> {
1668 let entries = &self.chain[..(self.chain_len as usize).min(MAX_CHAIN)];
1669 if entries.is_empty() {
1670 return None;
1671 }
1672 let mut total = 0i64;
1673 for entry in entries {
1674 let slot = (entry.slot as usize).min(SLOTS - 1);
1675 total += i64::from(entry.repeats.max(1)) * self.slots[slot].length_ticks();
1676 }
1677 if total <= 0 {
1678 return None;
1679 }
1680
1681 let base = tick.div_euclid(total) * total;
1682 let mut offset = tick.rem_euclid(total);
1683 let mut start = base;
1684 for entry in entries {
1685 let slot = (entry.slot as usize).min(SLOTS - 1);
1686 let span = i64::from(entry.repeats.max(1)) * self.slots[slot].length_ticks();
1687 if offset < span {
1688 return Some((slot as u8, start, start + span));
1689 }
1690 offset -= span;
1691 start += span;
1692 }
1693 None
1694 }
1695
1696 pub fn render(
1702 &mut self,
1703 window: &PlaybackWindow,
1704 transport_playing: bool,
1705 out: &mut impl EventSink,
1706 ) {
1707 if !transport_playing || !self.playing {
1708 if self.active {
1709 self.pending.flush(window.from(), out);
1710 self.active = false;
1711 }
1712 return;
1713 }
1714
1715 if !window.is_continuous() && self.active {
1718 self.pending.flush(window.from(), out);
1719 }
1720 self.active = true;
1721
1722 let mut cursor = window.from();
1723 for _ in 0..MAX_SEGMENTS {
1724 if cursor >= window.to() {
1725 break;
1726 }
1727 let (slot, origin, boundary) = self.locate(cursor);
1728
1729 if boundary <= cursor {
1734 self.pending.flush(cursor, out);
1735 self.switch_at(cursor);
1736 continue;
1737 }
1738
1739 self.live = slot;
1740 let end = boundary.min(window.to());
1741 let block = self.slots[slot as usize];
1742 generate(&block, origin, cursor, end, &mut self.pending, out);
1743
1744 if boundary < window.to() {
1748 self.pending.flush(boundary, out);
1749 self.switch_at(boundary);
1750 }
1751 cursor = end;
1752 }
1753
1754 let block = &self.slots[self.live as usize];
1755 let origin = self.chain_at(window.from()).map_or(0, |(_, start, _)| start);
1756 self.step = block.step_at(origin, window.from()) as u8;
1757 }
1758
1759 fn switch_at(&mut self, boundary: i64) {
1764 if self.chain_at(boundary).is_some() {
1765 return;
1766 }
1767 if let Some(slot) = self.pending_slot.take() {
1768 self.live = (slot as usize).min(SLOTS - 1) as u8;
1769 }
1770 }
1771}
1772
1773impl Default for PatternPlayer {
1774 fn default() -> Self {
1775 Self::new()
1776 }
1777}
1778
1779#[cfg(test)]
1780mod tests {
1781 use super::*;
1782
1783 fn drum_pattern(steps: u8) -> PatternBlock {
1787 let mut block = PatternBlock::empty();
1788 block.steps = steps;
1789 block.playing = true;
1790 block.lanes[0] = Lane::drum(36);
1791 for step in &mut block.lanes[0].steps {
1792 step.on = true;
1793 }
1794 block
1795 }
1796
1797 fn melodic_pattern(on: &[usize]) -> PatternBlock {
1799 let mut block = PatternBlock::empty();
1800 block.playing = true;
1801 for &index in on {
1802 block.lanes[0].steps[index].on = true;
1803 }
1804 block
1805 }
1806
1807 fn onsets(events: &[PatternEvent]) -> Vec<i64> {
1808 events.iter().filter(|e| e.is_note_on()).map(|e| e.tick).collect()
1809 }
1810
1811 fn run(block: &PatternBlock, from: i64, to: i64) -> Vec<PatternEvent> {
1812 let mut out = Vec::new();
1813 let mut pending = PendingOffs::new();
1814 generate(block, 0, from, to, &mut pending, &mut out);
1815 out
1816 }
1817
1818 #[test]
1824 fn the_block_is_the_size_it_is_supposed_to_be() {
1825 assert_eq!(std::mem::size_of::<Step>(), 9);
1826 assert_eq!(std::mem::size_of::<Lane>(), 3 + 32 * 9);
1827 assert_eq!(std::mem::size_of::<PatternBlock>(), PatternBlock::SIZE);
1828 assert_eq!(
1829 PatternBlock::SIZE, 2_373,
1830 "a pattern changed size; every queued SetPattern costs this many bytes"
1831 );
1832 assert_eq!(std::mem::align_of::<PatternBlock>(), 1);
1835 }
1836
1837 #[test]
1842 fn rate_ticks_are_the_960_ppq_table() {
1843 assert_eq!(Rate::Quarter.ticks(), 960);
1844 assert_eq!(Rate::Eighth.ticks(), 480);
1845 assert_eq!(Rate::Sixteenth.ticks(), 240);
1846 assert_eq!(Rate::ThirtySecond.ticks(), 120);
1847 assert_eq!(Rate::EighthTriplet.ticks(), 320);
1848 assert_eq!(Rate::SixteenthTriplet.ticks(), 160);
1849 assert_eq!(Rate::EighthTriplet.ticks() * 3, Rate::Quarter.ticks());
1851 assert_eq!(Rate::SixteenthTriplet.ticks() * 3, Rate::Eighth.ticks());
1852 }
1853
1854 #[test]
1855 fn straight_swing_moves_nothing() {
1856 let block = drum_pattern(16);
1857 assert_eq!(block.swing, PatternBlock::MIN_SWING);
1858 for step in 0..16 {
1859 assert_eq!(block.swing_offset(step), 0);
1860 }
1861 }
1862
1863 #[test]
1866 fn full_swing_is_a_triplet_feel() {
1867 let mut block = drum_pattern(16);
1868 block.swing = 75;
1869 assert_eq!(block.swing_offset(0), 0);
1870 assert_eq!(block.swing_offset(1), block.ticks_per_step() / 2);
1871 assert_eq!(block.swing_offset(2), 0);
1872 assert_eq!(block.swing_offset(15), block.ticks_per_step() / 2);
1873 }
1874
1875 #[test]
1879 fn swing_is_exact_integer_ticks() {
1880 let mut block = drum_pattern(16);
1881 block.swing = 62;
1882 assert_eq!(block.swing_offset(1), 57); block.rate = Rate::Eighth;
1884 assert_eq!(block.swing_offset(1), 115); }
1886
1887 #[test]
1890 fn swing_never_reorders_the_steps() {
1891 for swing in PatternBlock::MIN_SWING..=PatternBlock::MAX_SWING {
1892 let mut block = drum_pattern(16);
1893 block.swing = swing;
1894 let mut previous = i64::MIN;
1895 for index in 0..32 {
1896 let onset = block.onset(0, index);
1897 assert!(onset > previous, "swing {swing} reordered step {index}");
1898 previous = onset;
1899 }
1900 }
1901 }
1902
1903 #[test]
1909 fn starting_mid_pattern_fires_only_the_remaining_onsets() {
1910 let block = drum_pattern(16);
1911 let cycle = block.length_ticks();
1912 assert_eq!(cycle, 3840);
1913
1914 let whole = onsets(&run(&block, 0, cycle));
1915 assert_eq!(whole.len(), 16);
1916 assert_eq!(whole[0], 0);
1917
1918 let late = onsets(&run(&block, 1200, cycle));
1919 assert_eq!(late.len(), 11, "steps 5..=15 remain");
1920 assert_eq!(late[0], 1200);
1921 assert_eq!(late, whole[5..]);
1922 }
1923
1924 #[test]
1927 fn the_step_is_a_function_of_the_position() {
1928 let block = drum_pattern(16);
1929 assert_eq!(block.step_at(0, 0), 0);
1930 assert_eq!(block.step_at(0, 239), 0);
1931 assert_eq!(block.step_at(0, 240), 1);
1932 assert_eq!(block.step_at(0, 3840), 0);
1933 assert_eq!(block.step_at(0, 3840 * 4 + 720), 3);
1934 }
1935
1936 #[test]
1940 fn a_twelve_step_pattern_drifts_against_the_bar() {
1941 let block = drum_pattern(12);
1942 let bar = 3840;
1943 assert_eq!(block.length_ticks(), 2880);
1944 assert_eq!(block.step_at(0, 0), 0);
1945 assert_eq!(block.step_at(0, bar), 4);
1946 assert_eq!(block.step_at(0, bar * 2), 8);
1947 assert_eq!(block.step_at(0, bar * 3), 0, "back in phase after three bars");
1948 }
1949
1950 #[test]
1952 fn a_shorter_pattern_masks_rather_than_truncates() {
1953 let mut block = drum_pattern(32);
1954 assert_eq!(onsets(&run(&block, 0, block.length_ticks())).len(), 32);
1955
1956 block.steps = 16;
1957 let short = run(&block, 0, block.length_ticks());
1958 assert_eq!(onsets(&short).len(), 16);
1959
1960 block.steps = 32;
1961 assert_eq!(
1962 onsets(&run(&block, 0, block.length_ticks())).len(),
1963 32,
1964 "the steps past 16 were cleared rather than masked"
1965 );
1966 }
1967
1968 #[test]
1971 fn tiling_a_cycle_with_windows_fires_every_step_once() {
1972 let block = drum_pattern(16);
1973 let cycle = block.length_ticks();
1974 for span in [1, 7, 240, 241, 1000] {
1975 let mut all = Vec::new();
1976 let mut pending = PendingOffs::new();
1977 let mut from = 0;
1978 while from < cycle {
1979 let to = (from + span).min(cycle);
1980 generate(&block, 0, from, to, &mut pending, &mut all);
1981 from = to;
1982 }
1983 assert_eq!(
1984 onsets(&all).len(),
1985 16,
1986 "span {span} produced the wrong number of onsets"
1987 );
1988 }
1989 }
1990
1991 #[test]
1994 fn a_gate_is_a_percentage_of_the_step() {
1995 let step = Step { gate: 50, ..Step::silent() };
1996 assert_eq!(step.gate_ticks(240), Some(120));
1997 let step = Step { gate: 200, ..Step::silent() };
1998 assert_eq!(step.gate_ticks(240), Some(480));
1999 let step = Step { gate: 0, ..Step::silent() };
2001 assert_eq!(step.gate_ticks(240), Some(12));
2002 let step = Step { gate: Step::TIE, ..Step::silent() };
2003 assert_eq!(step.gate_ticks(240), None, "a tie has no due tick");
2004 }
2005
2006 #[test]
2007 fn every_note_gets_an_off() {
2008 let block = drum_pattern(16);
2009 let events = run(&block, 0, block.length_ticks() + 240);
2010 let ons = events.iter().filter(|e| e.is_note_on()).count();
2011 let offs = events.iter().filter(|e| e.status == 0x80).count();
2012 assert_eq!(ons, 17);
2013 assert_eq!(offs, 17, "a note was left sounding");
2014 }
2015
2016 #[test]
2019 fn a_tie_holds_to_the_next_onset() {
2020 let mut block = melodic_pattern(&[0, 4]);
2021 block.lanes[0].steps[0].gate = Step::TIE;
2022 let events = run(&block, 0, block.length_ticks());
2023
2024 let offs: Vec<i64> = events.iter().filter(|e| e.status == 0x80).map(|e| e.tick).collect();
2025 assert_eq!(offs[0], 960, "the tie ended somewhere other than step 4");
2026
2027 let at_960: Vec<u8> = events.iter().filter(|e| e.tick == 960).map(|e| e.status).collect();
2029 assert_eq!(at_960, vec![0x80, 0x90], "the off has to be pushed first");
2030 }
2031
2032 #[test]
2035 fn a_long_gate_is_cut_by_the_next_onset() {
2036 let mut block = melodic_pattern(&[0, 1]);
2037 block.lanes[0].steps[0].gate = 200;
2038 let events = run(&block, 0, 960);
2039 let at_240: Vec<u8> = events.iter().filter(|e| e.tick == 240).map(|e| e.status).collect();
2040 assert_eq!(at_240, vec![0x80, 0x90]);
2041 }
2042
2043 #[test]
2047 fn the_pending_table_forces_off_the_oldest_on_overflow() {
2048 let mut pending = PendingOffs::new();
2049 let mut out = Vec::new();
2050 for i in 0..MAX_PENDING_OFFS {
2051 pending.hold(0, 40 + i as u8, None, 0, &mut out);
2052 }
2053 assert_eq!(pending.len(), MAX_PENDING_OFFS);
2054 assert!(out.is_empty());
2055
2056 pending.hold(1, 99, None, 100, &mut out);
2057 assert_eq!(out.len(), 1);
2058 assert_eq!(out[0].data1, 40, "the oldest note was not the one forced off");
2059 assert_eq!(out[0].tick, 100);
2060 assert_eq!(pending.len(), MAX_PENDING_OFFS);
2061 }
2062
2063 #[test]
2064 fn a_flush_ends_everything_at_one_tick() {
2065 let mut pending = PendingOffs::new();
2066 let mut out = Vec::new();
2067 pending.hold(0, 60, Some(500), 0, &mut out);
2068 pending.hold(1, 64, None, 0, &mut out);
2069 pending.flush(300, &mut out);
2070 assert_eq!(out.len(), 2);
2071 assert!(out.iter().all(|e| e.tick == 300 && e.status == 0x80));
2072 assert!(pending.is_empty());
2073 }
2074
2075 #[test]
2078 fn a_muted_lane_is_silent_and_a_soloed_one_is_the_only_one() {
2079 let mut block = drum_pattern(16);
2080 block.lanes[1] = Lane::drum(42);
2081 for step in &mut block.lanes[1].steps {
2082 step.on = true;
2083 }
2084 assert_eq!(onsets(&run(&block, 0, 240)).len(), 2);
2085
2086 block.lanes[1].muted = true;
2087 assert_eq!(onsets(&run(&block, 0, 240)).len(), 1);
2088
2089 block.lanes[1].muted = false;
2090 block.lanes[1].soloed = true;
2091 let solo = run(&block, 0, 240);
2092 assert_eq!(onsets(&solo).len(), 1);
2093 assert_eq!(solo[0].data1, 42);
2094 }
2095
2096 #[test]
2099 fn accent_picks_the_patterns_accent_velocity() {
2100 let mut block = melodic_pattern(&[0, 1]);
2101 block.lanes[0].steps[1].accent = true;
2102 let events = run(&block, 0, 480);
2103 let ons: Vec<u8> = events.iter().filter(|e| e.is_note_on()).map(|e| e.data2).collect();
2104 assert_eq!(ons, vec![100, 127]);
2105 }
2106
2107 fn notes_of(root: u8, chord: Chord, voicing: Voicing, below: bool, mode: Mode) -> Vec<u8> {
2110 let mut out = [0u8; MAX_CHORD_NOTES];
2111 let n = chord_notes(root, chord, voicing, below, mode, 0, &mut out);
2112 out[..n].to_vec()
2113 }
2114
2115 #[test]
2116 fn the_chord_table_is_the_shapes_it_names() {
2117 assert_eq!(notes_of(60, Chord::None, Voicing::Close, false, Mode::Chromatic), vec![60]);
2118 assert_eq!(notes_of(60, Chord::Fifth, Voicing::Close, false, Mode::Chromatic), vec![60, 67]);
2119 assert_eq!(notes_of(60, Chord::Octave, Voicing::Close, false, Mode::Chromatic), vec![60, 72]);
2120 assert_eq!(notes_of(60, Chord::Maj, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67]);
2121 assert_eq!(notes_of(60, Chord::Min, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 67]);
2122 assert_eq!(notes_of(60, Chord::Dim, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 66]);
2123 assert_eq!(notes_of(60, Chord::Sus2, Voicing::Close, false, Mode::Chromatic), vec![60, 62, 67]);
2124 assert_eq!(notes_of(60, Chord::Sus4, Voicing::Close, false, Mode::Chromatic), vec![60, 65, 67]);
2125 assert_eq!(notes_of(60, Chord::Maj6, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67, 69]);
2126 assert_eq!(notes_of(60, Chord::Min6, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 67, 69]);
2127 assert_eq!(notes_of(60, Chord::Dom7, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67, 70]);
2128 assert_eq!(notes_of(60, Chord::Min7, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 67, 70]);
2129 assert_eq!(notes_of(60, Chord::Maj7, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67, 71]);
2130 assert_eq!(notes_of(60, Chord::Quartal, Voicing::Close, false, Mode::Chromatic), vec![60, 65, 70]);
2131 }
2132
2133 #[test]
2136 fn chord_identities_are_the_documented_order() {
2137 let order = [
2138 Chord::None, Chord::Fifth, Chord::Octave, Chord::Diatonic, Chord::Diatonic7,
2139 Chord::Maj, Chord::Min, Chord::Dim, Chord::Sus2, Chord::Sus4, Chord::Maj6,
2140 Chord::Min6, Chord::Dom7, Chord::Min7, Chord::Maj7, Chord::Quartal,
2141 ];
2142 for (index, chord) in order.iter().enumerate() {
2143 assert_eq!(chord.index() as usize, index);
2144 assert_eq!(Chord::from_index(index as u8), *chord);
2145 }
2146 assert_eq!(Chord::from_index(200), Chord::None, "an unknown id is one note");
2147 }
2148
2149 #[test]
2152 fn drop_two_lowers_the_second_voice_from_the_top() {
2153 assert_eq!(
2155 notes_of(60, Chord::Maj, Voicing::Drop2, false, Mode::Chromatic),
2156 vec![52, 60, 67]
2157 );
2158 assert_eq!(
2160 notes_of(60, Chord::Maj7, Voicing::Drop2, false, Mode::Chromatic),
2161 vec![55, 60, 64, 71]
2162 );
2163 }
2164
2165 #[test]
2166 fn inversions_lift_the_bottom_voices() {
2167 assert_eq!(
2168 notes_of(60, Chord::Maj, Voicing::First, false, Mode::Chromatic),
2169 vec![64, 67, 72]
2170 );
2171 assert_eq!(
2172 notes_of(60, Chord::Maj, Voicing::Second, false, Mode::Chromatic),
2173 vec![67, 72, 76]
2174 );
2175 }
2176
2177 #[test]
2178 fn root_below_adds_the_bass_double() {
2179 assert_eq!(
2180 notes_of(60, Chord::Maj, Voicing::Close, true, Mode::Chromatic),
2181 vec![48, 60, 64, 67]
2182 );
2183 }
2184
2185 #[test]
2190 fn every_chord_and_voicing_is_playable() {
2191 for &chord in &Chord::ALL {
2192 for &voicing in &Voicing::ALL {
2193 for below in [false, true] {
2194 for &mode in &Mode::ALL {
2195 for root in 24..=96u8 {
2196 let notes = notes_of(root, chord, voicing, below, mode);
2197 assert!(!notes.is_empty(), "{chord:?} produced nothing");
2198 assert!(notes.len() <= MAX_CHORD_NOTES);
2199 let mut seen = notes.clone();
2200 seen.dedup();
2201 assert_eq!(seen, notes, "{chord:?}/{voicing:?} doubled a note");
2202 for window in notes.windows(2) {
2203 assert!(window[0] < window[1], "not ascending");
2204 }
2205 }
2206 }
2207 }
2208 }
2209 }
2210 }
2211
2212 #[test]
2217 fn voicings_preserve_the_pitch_class_set() {
2218 for &chord in &Chord::ALL {
2219 for &mode in &Mode::ALL {
2220 for root in 36..=84u8 {
2221 let classes = |notes: Vec<u8>| {
2222 let mut c: Vec<u8> = notes.iter().map(|n| n % 12).collect();
2223 c.sort_unstable();
2224 c.dedup();
2225 c
2226 };
2227 let close = classes(notes_of(root, chord, Voicing::Close, false, mode));
2228 for &voicing in &Voicing::ALL {
2229 for below in [false, true] {
2230 assert_eq!(
2231 classes(notes_of(root, chord, voicing, below, mode)),
2232 close,
2233 "{chord:?} changed identity under {voicing:?} below={below}"
2234 );
2235 }
2236 }
2237 }
2238 }
2239 }
2240 }
2241
2242 #[test]
2246 fn diatonic_triads_have_the_textbook_qualities_in_every_mode() {
2247 let expected: [(Mode, [Chord; 7]); 7] = [
2248 (Mode::Ionian, [Chord::Maj, Chord::Min, Chord::Min, Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim]),
2249 (Mode::Dorian, [Chord::Min, Chord::Min, Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj]),
2250 (Mode::Phrygian, [Chord::Min, Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj, Chord::Min]),
2251 (Mode::Lydian, [Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj, Chord::Min, Chord::Min]),
2252 (Mode::Mixolydian, [Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj, Chord::Min, Chord::Min, Chord::Maj]),
2253 (Mode::Aeolian, [Chord::Min, Chord::Dim, Chord::Maj, Chord::Min, Chord::Min, Chord::Maj, Chord::Maj]),
2254 (Mode::Locrian, [Chord::Dim, Chord::Maj, Chord::Min, Chord::Min, Chord::Maj, Chord::Maj, Chord::Min]),
2255 ];
2256
2257 for tonic in 0..12u8 {
2258 for (mode, qualities) in &expected {
2259 let scale = mode.scale().expect("a mode has a scale");
2260 for (degree, &quality) in qualities.iter().enumerate() {
2261 let root = 60 + i32::from(tonic) + scale[degree];
2262 let root = root as u8;
2263 let mut derived = [0u8; MAX_CHORD_NOTES];
2264 let n = chord_notes(
2265 root, Chord::Diatonic, Voicing::Close, false, *mode, tonic, &mut derived,
2266 );
2267 let mut explicit = [0u8; MAX_CHORD_NOTES];
2268 let m = chord_notes(
2269 root, quality, Voicing::Close, false, *mode, tonic, &mut explicit,
2270 );
2271 assert_eq!(
2272 derived[..n],
2273 explicit[..m],
2274 "{mode:?} degree {} in tonic {tonic} should be {quality:?}",
2275 degree + 1
2276 );
2277 }
2278 }
2279 }
2280 }
2281
2282 #[test]
2286 fn the_seventh_degree_is_half_diminished() {
2287 let mut out = [0u8; MAX_CHORD_NOTES];
2288 let n = chord_notes(71, Chord::Diatonic7, Voicing::Close, false, Mode::Ionian, 0, &mut out);
2289 assert_eq!(&out[..n], &[71, 74, 77, 81], "B D F A is not m7♭5");
2290 }
2291
2292 #[test]
2296 fn the_diatonic_chords_collapse_under_chromatic() {
2297 assert_eq!(
2298 notes_of(60, Chord::Diatonic, Voicing::Close, false, Mode::Chromatic),
2299 notes_of(60, Chord::Maj, Voicing::Close, false, Mode::Chromatic)
2300 );
2301 assert_eq!(
2302 notes_of(60, Chord::Diatonic7, Voicing::Close, false, Mode::Chromatic),
2303 notes_of(60, Chord::Maj7, Voicing::Close, false, Mode::Chromatic)
2304 );
2305 }
2306
2307 #[test]
2311 fn a_borrowed_root_falls_back_to_major() {
2312 assert_eq!(Mode::Ionian.degree_of(61, 0), None);
2313 assert_eq!(
2314 notes_of(61, Chord::Diatonic, Voicing::Close, false, Mode::Ionian),
2315 vec![61, 65, 68]
2316 );
2317 }
2318
2319 #[test]
2322 fn chromatic_walking_is_semitones() {
2323 assert_eq!(Mode::Chromatic.walk(60, 0, 1), 61);
2324 assert_eq!(Mode::Chromatic.walk(60, 0, -1), 59);
2325 assert_eq!(Mode::Chromatic.walk(0, 0, -1), 0, "the bottom of the range holds");
2326 assert_eq!(Mode::Chromatic.walk(127, 0, 1), 127);
2327 }
2328
2329 #[test]
2330 fn mode_walking_is_scale_degrees() {
2331 let mut note = 60;
2333 for expected in [62, 64, 65, 67, 69, 71, 72, 74] {
2334 note = Mode::Ionian.walk(note, 0, 1);
2335 assert_eq!(note, expected);
2336 }
2337 let mut note = 60;
2338 for expected in [59, 57, 55, 53, 52, 50, 48] {
2339 note = Mode::Ionian.walk(note, 0, -1);
2340 assert_eq!(note, expected);
2341 }
2342 }
2343
2344 #[test]
2348 fn walking_snaps_a_borrowed_note_onto_the_scale() {
2349 assert_eq!(Mode::Ionian.walk(61, 0, 1), 62, "C# up lands on D");
2350 assert_eq!(Mode::Ionian.walk(61, 0, -1), 60, "C# down lands on C");
2351 }
2352
2353 #[test]
2354 fn every_mode_walks_a_full_octave_in_seven_degrees() {
2355 for &mode in &Mode::ALL {
2356 if mode == Mode::Chromatic {
2357 continue;
2358 }
2359 for tonic in 0..12u8 {
2360 let start = 60 + tonic;
2361 let start = mode.walk(start, tonic, 0);
2362 let mut note = start;
2363 for _ in 0..7 {
2364 note = mode.walk(note, tonic, 1);
2365 }
2366 assert_eq!(note, start + 12, "{mode:?} in {tonic} did not close");
2367 }
2368 }
2369 }
2370
2371 #[test]
2374 fn switch_boundaries_are_the_next_grid_line() {
2375 let pattern = 3840;
2376 assert_eq!(SwitchQuant::Immediate.boundary(1234, pattern), 1234);
2377 assert_eq!(SwitchQuant::Beat.boundary(1234, pattern), 1920);
2378 assert_eq!(SwitchQuant::Bar.boundary(1234, pattern), 3840);
2379 assert_eq!(SwitchQuant::PatternEnd.boundary(1234, pattern), 3840);
2380 assert_eq!(SwitchQuant::PatternEnd.boundary(4000, 2880), 5760);
2381 }
2382
2383 #[test]
2387 fn a_boundary_already_reached_is_the_answer() {
2388 assert_eq!(SwitchQuant::Bar.boundary(3840, 3840), 3840);
2389 assert_eq!(SwitchQuant::Beat.boundary(960, 3840), 960);
2390 assert_eq!(SwitchQuant::PatternEnd.boundary(0, 3840), 0);
2391 }
2392
2393 const TPS: f64 = 120.0 * 960.0 / (60.0 * 44_100.0);
2397
2398 fn window(position: i64, frames: u32, previous: Option<PlaybackWindow>) -> PlaybackWindow {
2399 PlaybackWindow::for_block(position, frames, TPS, None, previous)
2400 }
2401
2402 #[test]
2403 fn the_first_window_starts_where_the_transport_is() {
2404 let w = window(1000, 512, None);
2405 assert_eq!(w.from(), 1000);
2406 assert!(!w.is_continuous(), "there is nothing for it to continue from");
2407 }
2408
2409 #[test]
2413 fn a_window_continues_from_the_last_one_across_a_rounding_gap() {
2414 let first = window(0, 470, None);
2415 let span = first.to();
2416 let second = window(span + 1, 470, Some(first));
2418 assert_eq!(second.from(), span, "a tick of song time was skipped");
2419 assert!(second.is_continuous());
2420 assert_eq!(second.to(), span + 1 + span);
2421 }
2422
2423 #[test]
2426 fn a_jump_breaks_continuity() {
2427 let first = window(0, 512, None);
2428 let jumped = window(100_000, 512, Some(first));
2429 assert_eq!(jumped.from(), 100_000);
2430 assert!(!jumped.is_continuous());
2431 }
2432
2433 #[test]
2437 fn a_loop_wrap_starts_the_window_at_the_loop_point() {
2438 let previous = PlaybackWindow::for_block(3800, 512, TPS, Some((0, 3840)), None);
2439 let wrapped = PlaybackWindow::for_block(3, 512, TPS, Some((0, 3840)), Some(previous));
2440 assert_eq!(wrapped.from(), 0);
2441 assert!(!wrapped.is_continuous());
2442 }
2443
2444 #[test]
2447 fn a_window_never_reaches_past_the_loop_end() {
2448 let w = PlaybackWindow::for_block(3830, 4096, TPS, Some((0, 3840)), None);
2449 assert_eq!(w.to(), 3840);
2450 assert!(!w.contains(3840));
2451 }
2452
2453 #[test]
2456 fn sample_offsets_come_from_ticks_and_nothing_else() {
2457 let w = window(1000, 512, None);
2458 assert_eq!(w.sample_offset(1000), 0);
2459 assert_eq!(w.sample_offset(999), 0, "before the window is the first sample");
2460 assert_eq!(w.sample_offset(1000 + 22), (22.0 / TPS) as u32);
2461 assert_eq!(w.sample_offset(i64::MAX), 511, "past the block is the last sample");
2462 }
2463
2464 #[test]
2465 fn a_zero_length_block_has_no_samples_to_land_on() {
2466 let w = window(0, 0, None);
2467 assert_eq!(w.sample_offset(1000), 0);
2468 }
2469
2470 fn player_with(slot0: PatternBlock, slot1: PatternBlock) -> PatternPlayer {
2473 let mut player = PatternPlayer::new();
2474 player.apply(1, slot1);
2475 player.apply(0, slot0);
2476 player
2477 }
2478
2479 fn run_player(
2483 player: &mut PatternPlayer,
2484 start: i64,
2485 frames: u32,
2486 until: i64,
2487 ) -> Vec<PatternEvent> {
2488 let mut out = Vec::new();
2489 let mut position = start;
2490 let mut previous = None;
2491 while position < until {
2492 let w = window(position, frames, previous);
2493 player.render(&w, true, &mut out);
2494 position = w.to();
2495 previous = Some(w);
2496 }
2497 out
2498 }
2499
2500 fn tick_player(
2502 player: &mut PatternPlayer,
2503 position: i64,
2504 frames: u32,
2505 previous: Option<PlaybackWindow>,
2506 ) -> (PlaybackWindow, Vec<PatternEvent>) {
2507 let w = window(position, frames, previous);
2508 let mut out = Vec::new();
2509 player.render(&w, true, &mut out);
2510 (w, out)
2511 }
2512
2513 #[test]
2514 fn a_stopped_transport_produces_nothing_and_then_flushes_once() {
2515 let mut player = player_with(drum_pattern(16), PatternBlock::empty());
2516 let (w, events) = tick_player(&mut player, 0, 512, None);
2517 assert!(!events.is_empty());
2518 assert!(player.held_notes() > 0);
2519
2520 let mut out = Vec::new();
2521 player.render(&w, false, &mut out);
2522 assert_eq!(out.len(), 1, "the sounding note was not turned off");
2523 assert_eq!(out[0].status, 0x80);
2524 assert_eq!(player.held_notes(), 0);
2525
2526 let mut again = Vec::new();
2527 player.render(&w, false, &mut again);
2528 assert!(again.is_empty(), "the flush repeated");
2529 }
2530
2531 #[test]
2534 fn a_pattern_switch_ends_the_old_notes_before_starting_the_new_ones() {
2535 let mut a = drum_pattern(16);
2536 a.lanes[0].steps[15].gate = Step::TIE;
2537 let mut b = drum_pattern(16);
2538 b.lanes[0] = Lane::drum(42);
2539 for step in &mut b.lanes[0].steps {
2540 step.on = true;
2541 }
2542
2543 let mut player = player_with(a, b);
2544 let mut queue = a;
2546 queue.pending_slot = Some(1);
2547 player.apply(0, queue);
2548 assert_eq!(player.countdown(3600), Some((1, 1)), "one step to go");
2549
2550 let out = run_player(&mut player, 3500, 512, 3900);
2553
2554 let at_boundary: Vec<(u8, u8)> = out
2555 .iter()
2556 .filter(|e| e.tick == 3840)
2557 .map(|e| (e.status, e.data1))
2558 .collect();
2559 assert_eq!(
2560 at_boundary,
2561 vec![(0x80, 36), (0x90, 42)],
2562 "the old note has to be ended before the new one starts"
2563 );
2564 assert_eq!(player.live_slot(), 1);
2565 assert_eq!(player.queued_slot(), None);
2566 }
2567
2568 #[test]
2572 fn an_immediate_switch_takes_effect_at_the_start_of_the_block() {
2573 let a = drum_pattern(16);
2574 let mut b = drum_pattern(16);
2575 b.lanes[0] = Lane::drum(42);
2576 for step in &mut b.lanes[0].steps {
2577 step.on = true;
2578 }
2579
2580 let mut player = player_with(a, b);
2581 let mut queued = a;
2582 queued.pending_slot = Some(1);
2583 queued.switch_quant = SwitchQuant::Immediate;
2584 player.apply(0, queued);
2585
2586 let w = window(480, 512, None);
2589 let mut out = Vec::new();
2590 player.render(&w, true, &mut out);
2591 assert_eq!(player.live_slot(), 1);
2592 let first = out.iter().find(|e| e.is_note_on()).expect("a note");
2593 assert_eq!(first.data1, 42, "the old pattern played after an immediate switch");
2594 assert_eq!(first.tick, 480);
2595 }
2596
2597 #[test]
2600 fn a_beat_quantized_switch_splits_the_block_at_the_beat() {
2601 let a = drum_pattern(16);
2602 let mut b = drum_pattern(16);
2603 b.lanes[0] = Lane::drum(42);
2604 for step in &mut b.lanes[0].steps {
2605 step.on = true;
2606 }
2607
2608 let mut player = player_with(a, b);
2609 let mut queued = a;
2610 queued.pending_slot = Some(1);
2611 queued.switch_quant = SwitchQuant::Beat;
2612 player.apply(0, queued);
2613
2614 let out = run_player(&mut player, 700, 512, 1100);
2615 let switched: Vec<(i64, u8)> = out
2616 .iter()
2617 .filter(|e| e.is_note_on())
2618 .map(|e| (e.tick, e.data1))
2619 .collect();
2620 assert_eq!(
2621 switched,
2622 vec![(720, 36), (960, 42)],
2623 "the switch did not land on the beat"
2624 );
2625 assert_eq!(player.live_slot(), 1);
2626 }
2627
2628 #[test]
2632 fn queueing_the_live_slot_does_nothing() {
2633 let mut block = drum_pattern(16);
2634 block.pending_slot = Some(0);
2635 let player = player_with(block, PatternBlock::empty());
2636 assert_eq!(player.queued_slot(), None);
2637 assert_eq!(player.countdown(0), None);
2638 }
2639
2640 #[test]
2644 fn a_chain_is_derived_from_the_position() {
2645 let a = drum_pattern(16);
2646 let mut b = drum_pattern(16);
2647 b.lanes[0] = Lane::drum(42);
2648 for step in &mut b.lanes[0].steps {
2649 step.on = true;
2650 }
2651
2652 let mut chained = a;
2653 chained.chain[0] = ChainEntry { slot: 0, repeats: 2 };
2654 chained.chain[1] = ChainEntry { slot: 1, repeats: 1 };
2655 chained.chain_len = 2;
2656
2657 let mut player = PatternPlayer::new();
2658 player.apply(1, b);
2659 player.apply(0, chained);
2660
2661 let cycle = 3840;
2662 for (position, expected) in [
2664 (0, 36),
2665 (cycle, 36),
2666 (cycle * 2, 42),
2667 (cycle * 3, 36),
2668 (cycle * 5, 42),
2669 ] {
2670 let mut out = Vec::new();
2671 let w = window(position, 512, None);
2672 player.render(&w, true, &mut out);
2673 let first = out.iter().find(|e| e.is_note_on()).expect("a note");
2674 assert_eq!(first.data1, expected, "wrong chain entry at tick {position}");
2675 }
2676 }
2677
2678 #[test]
2681 fn a_chain_advance_ends_the_notes_it_replaces() {
2682 let mut a = drum_pattern(16);
2683 a.lanes[0].steps[15].gate = Step::TIE;
2684 let mut b = drum_pattern(16);
2685 b.lanes[0] = Lane::drum(42);
2686 b.lanes[0].steps[0].on = true;
2687
2688 let mut chained = a;
2689 chained.chain[0] = ChainEntry { slot: 0, repeats: 1 };
2690 chained.chain[1] = ChainEntry { slot: 1, repeats: 1 };
2691 chained.chain_len = 2;
2692
2693 let mut player = PatternPlayer::new();
2694 player.apply(1, b);
2695 player.apply(0, chained);
2696
2697 let out = run_player(&mut player, 3500, 512, 3900);
2698 let at_boundary: Vec<(u8, u8)> = out
2699 .iter()
2700 .filter(|e| e.tick == 3840)
2701 .map(|e| (e.status, e.data1))
2702 .collect();
2703 assert_eq!(at_boundary, vec![(0x80, 36), (0x90, 42)]);
2704 }
2705
2706 #[test]
2712 fn a_bounced_cycle_is_tick_identical_to_live_playback() {
2713 for swing in [50u8, 58, 62, 75] {
2714 for rate in Rate::ALL {
2715 let mut block = drum_pattern(16);
2716 block.rate = rate;
2717 block.swing = swing;
2718 block.lanes[0].steps[3].gate = 150;
2719 block.lanes[0].steps[7].gate = Step::TIE;
2720 block.lanes[0].steps[9].accent = true;
2721
2722 let mut bounced = Vec::new();
2723 compile_cycle(&block, 0, &mut bounced);
2724
2725 let cycle = block.length_ticks();
2727 let mut live = Vec::new();
2728 let mut pending = PendingOffs::new();
2729 let mut from = 0;
2730 while from < cycle {
2731 let to = (from + 97).min(cycle);
2732 generate(&block, 0, from, to, &mut pending, &mut live);
2733 from = to;
2734 }
2735 pending.flush(cycle, &mut live);
2736 live.sort_by_key(|e| e.tick);
2737
2738 let key = |e: &PatternEvent| (e.tick, e.status, e.data1, e.data2);
2739 let bounced: Vec<_> = bounced.iter().map(key).collect();
2740 let live: Vec<_> = live.iter().map(key).collect();
2741 assert_eq!(bounced, live, "swing {swing} at {}", rate.label());
2742 }
2743 }
2744 }
2745
2746 #[test]
2752 fn rendering_a_pattern_does_not_allocate() {
2753 let mut a = drum_pattern(16);
2754 a.lanes[0].steps[15].gate = Step::TIE;
2755 let mut b = drum_pattern(16);
2756 b.lanes[0] = Lane::drum(42);
2757
2758 let mut player = Box::new(player_with(a, b));
2759 let mut sink = Vec::with_capacity(1024);
2760 let mut queued = a;
2761 queued.pending_slot = Some(1);
2762
2763 let mut w = window(0, 512, None);
2765 player.render(&w, true, &mut sink);
2766
2767 let allocations = crate::alloc_count::allocations_during(|| {
2768 let mut position = 0;
2769 for block in 0..64 {
2770 w = window(position, 512, Some(w));
2771 sink.clear();
2772 player.render(&w, true, &mut sink);
2773 if block == 8 {
2774 player.apply(0, queued);
2775 }
2776 position = w.to();
2777 }
2778 });
2779 assert_eq!(allocations, 0, "the pattern player reached the allocator");
2780 }
2781
2782 #[test]
2786 fn a_full_sink_stops_the_generator() {
2787 struct Capped(Vec<PatternEvent>, usize);
2788 impl EventSink for Capped {
2789 fn accept(&mut self, event: PatternEvent) -> bool {
2790 if self.0.len() >= self.1 {
2791 return false;
2792 }
2793 self.0.push(event);
2794 true
2795 }
2796 }
2797
2798 let block = drum_pattern(16);
2799 let mut sink = Capped(Vec::new(), 3);
2800 let mut pending = PendingOffs::new();
2801 generate(&block, 0, 0, block.length_ticks(), &mut pending, &mut sink);
2802 assert_eq!(sink.0.len(), 3, "the sink was written past its cap");
2803 }
2804}
2805