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]
961 pub const fn empty() -> Self {
962 Self {
963 steps: 16,
964 rate: Rate::Sixteenth,
965 swing: Self::MIN_SWING,
966 base_vel: 100,
967 accent_vel: 127,
968 default_gate: 50,
969 mode: Mode::Chromatic,
970 tonic: 0,
971 lanes: [Lane::empty(); LANES],
972 playing: false,
973 pending_slot: None,
974 switch_quant: SwitchQuant::PatternEnd,
975 chain: [ChainEntry { slot: 0, repeats: 1 }; MAX_CHAIN],
976 chain_len: 0,
977 }
978 }
979
980 #[must_use]
984 pub fn step_count(&self) -> usize {
985 (self.steps as usize).clamp(1, MAX_STEPS)
986 }
987
988 #[must_use]
989 pub fn ticks_per_step(&self) -> i64 {
990 self.rate.ticks()
991 }
992
993 #[must_use]
995 pub fn length_ticks(&self) -> i64 {
996 self.ticks_per_step() * self.step_count() as i64
997 }
998
999 #[must_use]
1010 pub fn swing_offset(&self, step_index: usize) -> i64 {
1011 if step_index % 2 == 0 {
1012 return 0;
1013 }
1014 let swing = i64::from(self.swing.clamp(Self::MIN_SWING, Self::MAX_SWING));
1015 (swing - i64::from(Self::MIN_SWING)) * 2 * self.ticks_per_step() / 100
1016 }
1017
1018 fn max_swing_offset(&self) -> i64 {
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 #[must_use]
1031 pub fn onset(&self, origin: i64, index: i64) -> i64 {
1032 let steps = self.step_count() as i64;
1033 let in_pattern = index.rem_euclid(steps) as usize;
1034 origin + index * self.ticks_per_step() + self.swing_offset(in_pattern)
1035 }
1036
1037 #[must_use]
1040 pub fn step_at(&self, origin: i64, tick: i64) -> usize {
1041 let steps = self.step_count() as i64;
1042 (tick - origin).div_euclid(self.ticks_per_step()).rem_euclid(steps) as usize
1043 }
1044
1045 #[must_use]
1047 pub fn lane_audible(&self, lane: usize) -> bool {
1048 let Some(l) = self.lanes.get(lane) else { return false };
1049 if l.muted {
1050 return false;
1051 }
1052 let any_solo = self.lanes.iter().any(|l| l.soloed);
1053 !any_solo || l.soloed
1054 }
1055
1056 #[must_use]
1058 pub fn chain_entries(&self) -> &[ChainEntry] {
1059 &self.chain[..(self.chain_len as usize).min(MAX_CHAIN)]
1060 }
1061}
1062
1063impl Default for PatternBlock {
1064 fn default() -> Self {
1065 Self::empty()
1066 }
1067}
1068
1069#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1078pub struct PatternEvent {
1079 pub tick: i64,
1080 pub status: u8,
1081 pub data1: u8,
1082 pub data2: u8,
1083}
1084
1085impl PatternEvent {
1086 #[must_use]
1087 pub const fn note_on(tick: i64, note: u8, velocity: u8) -> Self {
1088 Self { tick, status: 0x90, data1: note, data2: velocity }
1089 }
1090
1091 #[must_use]
1092 pub const fn note_off(tick: i64, note: u8) -> Self {
1093 Self { tick, status: 0x80, data1: note, data2: 0 }
1094 }
1095
1096 #[must_use]
1097 pub const fn is_note_on(&self) -> bool {
1098 self.status == 0x90 && self.data2 > 0
1099 }
1100}
1101
1102pub trait EventSink {
1117 fn accept(&mut self, event: PatternEvent) -> bool;
1121}
1122
1123impl EventSink for Vec<PatternEvent> {
1124 fn accept(&mut self, event: PatternEvent) -> bool {
1125 self.push(event);
1126 true
1127 }
1128}
1129
1130#[derive(Debug, Clone, Copy)]
1148pub struct PlaybackWindow {
1149 from: i64,
1150 to: i64,
1151 position: i64,
1156 ticks_per_sample: f64,
1157 frames: u32,
1158 continuous: bool,
1159}
1160
1161impl PlaybackWindow {
1162 pub const MAX_TICK_GAP: i64 = 1;
1171
1172 #[must_use]
1191 pub fn for_block(
1192 position: i64,
1193 frames: u32,
1194 ticks_per_sample: f64,
1195 loop_region: Option<(i64, i64)>,
1196 previous: Option<Self>,
1197 ) -> Self {
1198 let span = (f64::from(frames) * ticks_per_sample) as i64;
1199
1200 let (from, continuous) = match (previous, loop_region) {
1201 (Some(prev), Some((loop_start, _))) if position < prev.position => (loop_start, false),
1202 (Some(prev), _) if prev.to <= position && position - prev.to <= Self::MAX_TICK_GAP => {
1203 (prev.to, true)
1204 }
1205 _ => (position, false),
1206 };
1207
1208 let mut to = position + span;
1209 if let Some((_, loop_end)) = loop_region {
1210 if loop_end > from {
1211 to = to.min(loop_end);
1212 }
1213 }
1214
1215 Self {
1216 from,
1217 to: to.max(from),
1218 position,
1219 ticks_per_sample,
1220 frames,
1221 continuous,
1222 }
1223 }
1224
1225 #[must_use]
1229 pub fn narrowed(&self, from: i64, to: i64) -> Self {
1230 Self { from, to: to.max(from), ..*self }
1231 }
1232
1233 #[must_use]
1234 pub const fn from(&self) -> i64 {
1235 self.from
1236 }
1237
1238 #[must_use]
1239 pub const fn to(&self) -> i64 {
1240 self.to
1241 }
1242
1243 #[must_use]
1247 pub const fn is_continuous(&self) -> bool {
1248 self.continuous
1249 }
1250
1251 #[must_use]
1252 pub const fn is_empty(&self) -> bool {
1253 self.to <= self.from
1254 }
1255
1256 #[must_use]
1257 pub const fn contains(&self, tick: i64) -> bool {
1258 tick >= self.from && tick < self.to
1259 }
1260
1261 #[must_use]
1269 pub fn sample_offset(&self, tick: i64) -> u32 {
1270 let last = self.frames.saturating_sub(1);
1271 let offset = tick - self.from;
1272 if offset <= 0 || self.ticks_per_sample <= 0.0 {
1273 return 0;
1274 }
1275 let samples = (offset as f64 / self.ticks_per_sample) as i64;
1276 u32::try_from(samples).unwrap_or(last).min(last)
1277 }
1278}
1279
1280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1284struct PendingOff {
1285 note: u8,
1286 lane: u8,
1287 due: Option<i64>,
1290}
1291
1292#[derive(Debug, Clone, Copy)]
1298pub struct PendingOffs {
1299 entries: [PendingOff; MAX_PENDING_OFFS],
1300 len: usize,
1301}
1302
1303impl PendingOffs {
1304 #[must_use]
1305 pub const fn new() -> Self {
1306 Self {
1307 entries: [PendingOff { note: 0, lane: 0, due: None }; MAX_PENDING_OFFS],
1308 len: 0,
1309 }
1310 }
1311
1312 #[must_use]
1313 pub const fn len(&self) -> usize {
1314 self.len
1315 }
1316
1317 #[must_use]
1318 pub const fn is_empty(&self) -> bool {
1319 self.len == 0
1320 }
1321
1322 pub fn clear(&mut self) {
1325 self.len = 0;
1326 }
1327
1328 fn remove(&mut self, index: usize) -> PendingOff {
1329 let gone = self.entries[index];
1330 for i in index..self.len - 1 {
1331 self.entries[i] = self.entries[i + 1];
1332 }
1333 self.len -= 1;
1334 gone
1335 }
1336
1337 pub fn hold(
1343 &mut self,
1344 lane: usize,
1345 note: u8,
1346 due: Option<i64>,
1347 now: i64,
1348 out: &mut impl EventSink,
1349 ) {
1350 if self.len == MAX_PENDING_OFFS {
1351 let oldest = self.remove(0);
1352 out.accept(PatternEvent::note_off(now, oldest.note));
1353 }
1354 self.entries[self.len] = PendingOff { note, lane: lane as u8, due };
1355 self.len += 1;
1356 }
1357
1358 pub fn end_lane(&mut self, lane: usize, at: i64, out: &mut impl EventSink) {
1364 let lane = lane as u8;
1365 let mut i = 0;
1366 while i < self.len {
1367 if self.entries[i].lane == lane {
1368 let gone = self.remove(i);
1369 out.accept(PatternEvent::note_off(at, gone.note));
1370 } else {
1371 i += 1;
1372 }
1373 }
1374 }
1375
1376 pub fn emit_due_before(&mut self, tick: i64, out: &mut impl EventSink) {
1379 let mut i = 0;
1380 while i < self.len {
1381 match self.entries[i].due {
1382 Some(due) if due < tick => {
1383 let gone = self.remove(i);
1384 out.accept(PatternEvent::note_off(due, gone.note));
1385 }
1386 _ => i += 1,
1387 }
1388 }
1389 }
1390
1391 pub fn flush(&mut self, at: i64, out: &mut impl EventSink) {
1394 for i in 0..self.len {
1395 out.accept(PatternEvent::note_off(at, self.entries[i].note));
1396 }
1397 self.len = 0;
1398 }
1399}
1400
1401impl Default for PendingOffs {
1402 fn default() -> Self {
1403 Self::new()
1404 }
1405}
1406
1407pub fn generate(
1421 block: &PatternBlock,
1422 origin: i64,
1423 from: i64,
1424 to: i64,
1425 pending: &mut PendingOffs,
1426 out: &mut impl EventSink,
1427) {
1428 if to <= from {
1429 return;
1430 }
1431 let tps = block.ticks_per_step();
1432 let steps = block.step_count() as i64;
1433
1434 let first = (from - origin - block.max_swing_offset()).div_euclid(tps);
1438 let last = (to - origin).div_euclid(tps) + 1;
1439 let last = last.min(first + MAX_STEP_SCAN);
1440
1441 let mut chord = [0u8; MAX_CHORD_NOTES];
1442 for index in first..last {
1443 let onset = block.onset(origin, index);
1444 if onset < from || onset >= to {
1445 continue;
1446 }
1447 pending.emit_due_before(onset, out);
1451
1452 let step_index = index.rem_euclid(steps) as usize;
1453 for lane_index in 0..LANES {
1454 if !block.lane_audible(lane_index) {
1455 continue;
1456 }
1457 let lane = &block.lanes[lane_index];
1458 let step = lane.steps[step_index];
1459 if !step.on {
1460 continue;
1461 }
1462
1463 pending.end_lane(lane_index, onset, out);
1466
1467 let velocity = if step.accent { block.accent_vel } else { block.base_vel };
1468 let velocity = velocity.clamp(1, 127);
1469 let due = step.gate_ticks(tps).map(|len| onset + len);
1470
1471 let count = if lane.is_pitched() {
1472 chord_notes(
1473 step.root(),
1474 step.chord_kind(),
1475 step.voicing_kind(),
1476 step.root_below(),
1477 block.mode,
1478 block.tonic,
1479 &mut chord,
1480 )
1481 } else {
1482 chord[0] = lane.note;
1483 1
1484 };
1485
1486 for ¬e in &chord[..count] {
1487 if !out.accept(PatternEvent::note_on(onset, note, velocity)) {
1488 return;
1489 }
1490 pending.hold(lane_index, note, due, onset, out);
1491 }
1492 }
1493 }
1494
1495 pending.emit_due_before(to, out);
1496}
1497
1498pub fn compile_cycle(block: &PatternBlock, origin: i64, out: &mut Vec<PatternEvent>) {
1507 let length = block.length_ticks();
1508 let mut pending = PendingOffs::new();
1509 generate(block, origin, origin, origin + length, &mut pending, out);
1510 pending.flush(origin + length, out);
1511 out.sort_by_key(|e| e.tick);
1512}
1513
1514#[derive(Debug, Clone, Copy)]
1529pub struct PatternPlayer {
1530 slots: [PatternBlock; SLOTS],
1531 live: u8,
1533 playing: bool,
1536 pending_slot: Option<u8>,
1537 switch_quant: SwitchQuant,
1538 chain: [ChainEntry; MAX_CHAIN],
1539 chain_len: u8,
1540 pending: PendingOffs,
1542 active: bool,
1545 step: u8,
1547}
1548
1549impl PatternPlayer {
1550 #[must_use]
1551 pub fn new() -> Self {
1552 Self {
1553 slots: [PatternBlock::empty(); SLOTS],
1554 live: 0,
1555 playing: false,
1556 pending_slot: None,
1557 switch_quant: SwitchQuant::PatternEnd,
1558 chain: [ChainEntry { slot: 0, repeats: 1 }; MAX_CHAIN],
1559 chain_len: 0,
1560 pending: PendingOffs::new(),
1561 active: false,
1562 step: 0,
1563 }
1564 }
1565
1566 pub fn apply(&mut self, slot: u8, block: PatternBlock) {
1569 let slot = (slot as usize).min(SLOTS - 1);
1570 self.slots[slot] = block;
1571 self.playing = block.playing;
1572 self.switch_quant = block.switch_quant;
1573 self.chain = block.chain;
1574 self.chain_len = block.chain_len;
1575 self.pending_slot = block
1580 .pending_slot
1581 .filter(|&s| s != self.live && block.chain_len == 0);
1582 }
1583
1584 #[must_use]
1585 pub fn slot(&self, index: usize) -> &PatternBlock {
1586 &self.slots[index.min(SLOTS - 1)]
1587 }
1588
1589 #[must_use]
1590 pub fn live_slot(&self) -> u8 {
1591 self.live
1592 }
1593
1594 #[must_use]
1595 pub fn queued_slot(&self) -> Option<u8> {
1596 self.pending_slot
1597 }
1598
1599 #[must_use]
1602 pub fn current_step(&self) -> u8 {
1603 self.step
1604 }
1605
1606 #[must_use]
1607 pub fn is_playing(&self) -> bool {
1608 self.playing
1609 }
1610
1611 #[must_use]
1612 pub fn held_notes(&self) -> usize {
1613 self.pending.len()
1614 }
1615
1616 pub fn silence(&mut self) {
1619 self.pending.clear();
1620 self.active = false;
1621 }
1622
1623 #[must_use]
1630 pub fn countdown(&self, now: i64) -> Option<(u8, i64)> {
1631 let slot = self.pending_slot?;
1632 let block = &self.slots[self.live as usize];
1633 let at = self.switch_quant.boundary(now, block.length_ticks());
1634 Some((slot, (at - now).div_euclid(block.ticks_per_step())))
1635 }
1636
1637 fn locate(&self, tick: i64) -> (u8, i64, i64) {
1646 if let Some(found) = self.chain_at(tick) {
1647 return found;
1648 }
1649 let boundary = match self.pending_slot {
1650 Some(_) => {
1651 let block = &self.slots[self.live as usize];
1652 self.switch_quant.boundary(tick, block.length_ticks())
1653 }
1654 None => i64::MAX,
1655 };
1656 (self.live, 0, boundary)
1657 }
1658
1659 fn chain_at(&self, tick: i64) -> Option<(u8, i64, i64)> {
1661 let entries = &self.chain[..(self.chain_len as usize).min(MAX_CHAIN)];
1662 if entries.is_empty() {
1663 return None;
1664 }
1665 let mut total = 0i64;
1666 for entry in entries {
1667 let slot = (entry.slot as usize).min(SLOTS - 1);
1668 total += i64::from(entry.repeats.max(1)) * self.slots[slot].length_ticks();
1669 }
1670 if total <= 0 {
1671 return None;
1672 }
1673
1674 let base = tick.div_euclid(total) * total;
1675 let mut offset = tick.rem_euclid(total);
1676 let mut start = base;
1677 for entry in entries {
1678 let slot = (entry.slot as usize).min(SLOTS - 1);
1679 let span = i64::from(entry.repeats.max(1)) * self.slots[slot].length_ticks();
1680 if offset < span {
1681 return Some((slot as u8, start, start + span));
1682 }
1683 offset -= span;
1684 start += span;
1685 }
1686 None
1687 }
1688
1689 pub fn render(
1695 &mut self,
1696 window: &PlaybackWindow,
1697 transport_playing: bool,
1698 out: &mut impl EventSink,
1699 ) {
1700 if !transport_playing || !self.playing {
1701 if self.active {
1702 self.pending.flush(window.from(), out);
1703 self.active = false;
1704 }
1705 return;
1706 }
1707
1708 if !window.is_continuous() && self.active {
1711 self.pending.flush(window.from(), out);
1712 }
1713 self.active = true;
1714
1715 let mut cursor = window.from();
1716 for _ in 0..MAX_SEGMENTS {
1717 if cursor >= window.to() {
1718 break;
1719 }
1720 let (slot, origin, boundary) = self.locate(cursor);
1721
1722 if boundary <= cursor {
1727 self.pending.flush(cursor, out);
1728 self.switch_at(cursor);
1729 continue;
1730 }
1731
1732 self.live = slot;
1733 let end = boundary.min(window.to());
1734 let block = self.slots[slot as usize];
1735 generate(&block, origin, cursor, end, &mut self.pending, out);
1736
1737 if boundary < window.to() {
1741 self.pending.flush(boundary, out);
1742 self.switch_at(boundary);
1743 }
1744 cursor = end;
1745 }
1746
1747 let block = &self.slots[self.live as usize];
1748 let origin = self.chain_at(window.from()).map_or(0, |(_, start, _)| start);
1749 self.step = block.step_at(origin, window.from()) as u8;
1750 }
1751
1752 fn switch_at(&mut self, boundary: i64) {
1757 if self.chain_at(boundary).is_some() {
1758 return;
1759 }
1760 if let Some(slot) = self.pending_slot.take() {
1761 self.live = (slot as usize).min(SLOTS - 1) as u8;
1762 }
1763 }
1764}
1765
1766impl Default for PatternPlayer {
1767 fn default() -> Self {
1768 Self::new()
1769 }
1770}
1771
1772#[cfg(test)]
1773mod tests {
1774 use super::*;
1775
1776 fn drum_pattern(steps: u8) -> PatternBlock {
1780 let mut block = PatternBlock::empty();
1781 block.steps = steps;
1782 block.playing = true;
1783 block.lanes[0] = Lane::drum(36);
1784 for step in &mut block.lanes[0].steps {
1785 step.on = true;
1786 }
1787 block
1788 }
1789
1790 fn melodic_pattern(on: &[usize]) -> PatternBlock {
1792 let mut block = PatternBlock::empty();
1793 block.playing = true;
1794 for &index in on {
1795 block.lanes[0].steps[index].on = true;
1796 }
1797 block
1798 }
1799
1800 fn onsets(events: &[PatternEvent]) -> Vec<i64> {
1801 events.iter().filter(|e| e.is_note_on()).map(|e| e.tick).collect()
1802 }
1803
1804 fn run(block: &PatternBlock, from: i64, to: i64) -> Vec<PatternEvent> {
1805 let mut out = Vec::new();
1806 let mut pending = PendingOffs::new();
1807 generate(block, 0, from, to, &mut pending, &mut out);
1808 out
1809 }
1810
1811 #[test]
1817 fn the_block_is_the_size_it_is_supposed_to_be() {
1818 assert_eq!(std::mem::size_of::<Step>(), 9);
1819 assert_eq!(std::mem::size_of::<Lane>(), 3 + 32 * 9);
1820 assert_eq!(std::mem::size_of::<PatternBlock>(), PatternBlock::SIZE);
1821 assert_eq!(
1822 PatternBlock::SIZE, 2_373,
1823 "a pattern changed size; every queued SetPattern costs this many bytes"
1824 );
1825 assert_eq!(std::mem::align_of::<PatternBlock>(), 1);
1828 }
1829
1830 #[test]
1835 fn rate_ticks_are_the_960_ppq_table() {
1836 assert_eq!(Rate::Quarter.ticks(), 960);
1837 assert_eq!(Rate::Eighth.ticks(), 480);
1838 assert_eq!(Rate::Sixteenth.ticks(), 240);
1839 assert_eq!(Rate::ThirtySecond.ticks(), 120);
1840 assert_eq!(Rate::EighthTriplet.ticks(), 320);
1841 assert_eq!(Rate::SixteenthTriplet.ticks(), 160);
1842 assert_eq!(Rate::EighthTriplet.ticks() * 3, Rate::Quarter.ticks());
1844 assert_eq!(Rate::SixteenthTriplet.ticks() * 3, Rate::Eighth.ticks());
1845 }
1846
1847 #[test]
1848 fn straight_swing_moves_nothing() {
1849 let block = drum_pattern(16);
1850 assert_eq!(block.swing, PatternBlock::MIN_SWING);
1851 for step in 0..16 {
1852 assert_eq!(block.swing_offset(step), 0);
1853 }
1854 }
1855
1856 #[test]
1859 fn full_swing_is_a_triplet_feel() {
1860 let mut block = drum_pattern(16);
1861 block.swing = 75;
1862 assert_eq!(block.swing_offset(0), 0);
1863 assert_eq!(block.swing_offset(1), block.ticks_per_step() / 2);
1864 assert_eq!(block.swing_offset(2), 0);
1865 assert_eq!(block.swing_offset(15), block.ticks_per_step() / 2);
1866 }
1867
1868 #[test]
1872 fn swing_is_exact_integer_ticks() {
1873 let mut block = drum_pattern(16);
1874 block.swing = 62;
1875 assert_eq!(block.swing_offset(1), 57); block.rate = Rate::Eighth;
1877 assert_eq!(block.swing_offset(1), 115); }
1879
1880 #[test]
1883 fn swing_never_reorders_the_steps() {
1884 for swing in PatternBlock::MIN_SWING..=PatternBlock::MAX_SWING {
1885 let mut block = drum_pattern(16);
1886 block.swing = swing;
1887 let mut previous = i64::MIN;
1888 for index in 0..32 {
1889 let onset = block.onset(0, index);
1890 assert!(onset > previous, "swing {swing} reordered step {index}");
1891 previous = onset;
1892 }
1893 }
1894 }
1895
1896 #[test]
1902 fn starting_mid_pattern_fires_only_the_remaining_onsets() {
1903 let block = drum_pattern(16);
1904 let cycle = block.length_ticks();
1905 assert_eq!(cycle, 3840);
1906
1907 let whole = onsets(&run(&block, 0, cycle));
1908 assert_eq!(whole.len(), 16);
1909 assert_eq!(whole[0], 0);
1910
1911 let late = onsets(&run(&block, 1200, cycle));
1912 assert_eq!(late.len(), 11, "steps 5..=15 remain");
1913 assert_eq!(late[0], 1200);
1914 assert_eq!(late, whole[5..]);
1915 }
1916
1917 #[test]
1920 fn the_step_is_a_function_of_the_position() {
1921 let block = drum_pattern(16);
1922 assert_eq!(block.step_at(0, 0), 0);
1923 assert_eq!(block.step_at(0, 239), 0);
1924 assert_eq!(block.step_at(0, 240), 1);
1925 assert_eq!(block.step_at(0, 3840), 0);
1926 assert_eq!(block.step_at(0, 3840 * 4 + 720), 3);
1927 }
1928
1929 #[test]
1933 fn a_twelve_step_pattern_drifts_against_the_bar() {
1934 let block = drum_pattern(12);
1935 let bar = 3840;
1936 assert_eq!(block.length_ticks(), 2880);
1937 assert_eq!(block.step_at(0, 0), 0);
1938 assert_eq!(block.step_at(0, bar), 4);
1939 assert_eq!(block.step_at(0, bar * 2), 8);
1940 assert_eq!(block.step_at(0, bar * 3), 0, "back in phase after three bars");
1941 }
1942
1943 #[test]
1945 fn a_shorter_pattern_masks_rather_than_truncates() {
1946 let mut block = drum_pattern(32);
1947 assert_eq!(onsets(&run(&block, 0, block.length_ticks())).len(), 32);
1948
1949 block.steps = 16;
1950 let short = run(&block, 0, block.length_ticks());
1951 assert_eq!(onsets(&short).len(), 16);
1952
1953 block.steps = 32;
1954 assert_eq!(
1955 onsets(&run(&block, 0, block.length_ticks())).len(),
1956 32,
1957 "the steps past 16 were cleared rather than masked"
1958 );
1959 }
1960
1961 #[test]
1964 fn tiling_a_cycle_with_windows_fires_every_step_once() {
1965 let block = drum_pattern(16);
1966 let cycle = block.length_ticks();
1967 for span in [1, 7, 240, 241, 1000] {
1968 let mut all = Vec::new();
1969 let mut pending = PendingOffs::new();
1970 let mut from = 0;
1971 while from < cycle {
1972 let to = (from + span).min(cycle);
1973 generate(&block, 0, from, to, &mut pending, &mut all);
1974 from = to;
1975 }
1976 assert_eq!(
1977 onsets(&all).len(),
1978 16,
1979 "span {span} produced the wrong number of onsets"
1980 );
1981 }
1982 }
1983
1984 #[test]
1987 fn a_gate_is_a_percentage_of_the_step() {
1988 let step = Step { gate: 50, ..Step::silent() };
1989 assert_eq!(step.gate_ticks(240), Some(120));
1990 let step = Step { gate: 200, ..Step::silent() };
1991 assert_eq!(step.gate_ticks(240), Some(480));
1992 let step = Step { gate: 0, ..Step::silent() };
1994 assert_eq!(step.gate_ticks(240), Some(12));
1995 let step = Step { gate: Step::TIE, ..Step::silent() };
1996 assert_eq!(step.gate_ticks(240), None, "a tie has no due tick");
1997 }
1998
1999 #[test]
2000 fn every_note_gets_an_off() {
2001 let block = drum_pattern(16);
2002 let events = run(&block, 0, block.length_ticks() + 240);
2003 let ons = events.iter().filter(|e| e.is_note_on()).count();
2004 let offs = events.iter().filter(|e| e.status == 0x80).count();
2005 assert_eq!(ons, 17);
2006 assert_eq!(offs, 17, "a note was left sounding");
2007 }
2008
2009 #[test]
2012 fn a_tie_holds_to_the_next_onset() {
2013 let mut block = melodic_pattern(&[0, 4]);
2014 block.lanes[0].steps[0].gate = Step::TIE;
2015 let events = run(&block, 0, block.length_ticks());
2016
2017 let offs: Vec<i64> = events.iter().filter(|e| e.status == 0x80).map(|e| e.tick).collect();
2018 assert_eq!(offs[0], 960, "the tie ended somewhere other than step 4");
2019
2020 let at_960: Vec<u8> = events.iter().filter(|e| e.tick == 960).map(|e| e.status).collect();
2022 assert_eq!(at_960, vec![0x80, 0x90], "the off has to be pushed first");
2023 }
2024
2025 #[test]
2028 fn a_long_gate_is_cut_by_the_next_onset() {
2029 let mut block = melodic_pattern(&[0, 1]);
2030 block.lanes[0].steps[0].gate = 200;
2031 let events = run(&block, 0, 960);
2032 let at_240: Vec<u8> = events.iter().filter(|e| e.tick == 240).map(|e| e.status).collect();
2033 assert_eq!(at_240, vec![0x80, 0x90]);
2034 }
2035
2036 #[test]
2040 fn the_pending_table_forces_off_the_oldest_on_overflow() {
2041 let mut pending = PendingOffs::new();
2042 let mut out = Vec::new();
2043 for i in 0..MAX_PENDING_OFFS {
2044 pending.hold(0, 40 + i as u8, None, 0, &mut out);
2045 }
2046 assert_eq!(pending.len(), MAX_PENDING_OFFS);
2047 assert!(out.is_empty());
2048
2049 pending.hold(1, 99, None, 100, &mut out);
2050 assert_eq!(out.len(), 1);
2051 assert_eq!(out[0].data1, 40, "the oldest note was not the one forced off");
2052 assert_eq!(out[0].tick, 100);
2053 assert_eq!(pending.len(), MAX_PENDING_OFFS);
2054 }
2055
2056 #[test]
2057 fn a_flush_ends_everything_at_one_tick() {
2058 let mut pending = PendingOffs::new();
2059 let mut out = Vec::new();
2060 pending.hold(0, 60, Some(500), 0, &mut out);
2061 pending.hold(1, 64, None, 0, &mut out);
2062 pending.flush(300, &mut out);
2063 assert_eq!(out.len(), 2);
2064 assert!(out.iter().all(|e| e.tick == 300 && e.status == 0x80));
2065 assert!(pending.is_empty());
2066 }
2067
2068 #[test]
2071 fn a_muted_lane_is_silent_and_a_soloed_one_is_the_only_one() {
2072 let mut block = drum_pattern(16);
2073 block.lanes[1] = Lane::drum(42);
2074 for step in &mut block.lanes[1].steps {
2075 step.on = true;
2076 }
2077 assert_eq!(onsets(&run(&block, 0, 240)).len(), 2);
2078
2079 block.lanes[1].muted = true;
2080 assert_eq!(onsets(&run(&block, 0, 240)).len(), 1);
2081
2082 block.lanes[1].muted = false;
2083 block.lanes[1].soloed = true;
2084 let solo = run(&block, 0, 240);
2085 assert_eq!(onsets(&solo).len(), 1);
2086 assert_eq!(solo[0].data1, 42);
2087 }
2088
2089 #[test]
2092 fn accent_picks_the_patterns_accent_velocity() {
2093 let mut block = melodic_pattern(&[0, 1]);
2094 block.lanes[0].steps[1].accent = true;
2095 let events = run(&block, 0, 480);
2096 let ons: Vec<u8> = events.iter().filter(|e| e.is_note_on()).map(|e| e.data2).collect();
2097 assert_eq!(ons, vec![100, 127]);
2098 }
2099
2100 fn notes_of(root: u8, chord: Chord, voicing: Voicing, below: bool, mode: Mode) -> Vec<u8> {
2103 let mut out = [0u8; MAX_CHORD_NOTES];
2104 let n = chord_notes(root, chord, voicing, below, mode, 0, &mut out);
2105 out[..n].to_vec()
2106 }
2107
2108 #[test]
2109 fn the_chord_table_is_the_shapes_it_names() {
2110 assert_eq!(notes_of(60, Chord::None, Voicing::Close, false, Mode::Chromatic), vec![60]);
2111 assert_eq!(notes_of(60, Chord::Fifth, Voicing::Close, false, Mode::Chromatic), vec![60, 67]);
2112 assert_eq!(notes_of(60, Chord::Octave, Voicing::Close, false, Mode::Chromatic), vec![60, 72]);
2113 assert_eq!(notes_of(60, Chord::Maj, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67]);
2114 assert_eq!(notes_of(60, Chord::Min, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 67]);
2115 assert_eq!(notes_of(60, Chord::Dim, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 66]);
2116 assert_eq!(notes_of(60, Chord::Sus2, Voicing::Close, false, Mode::Chromatic), vec![60, 62, 67]);
2117 assert_eq!(notes_of(60, Chord::Sus4, Voicing::Close, false, Mode::Chromatic), vec![60, 65, 67]);
2118 assert_eq!(notes_of(60, Chord::Maj6, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67, 69]);
2119 assert_eq!(notes_of(60, Chord::Min6, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 67, 69]);
2120 assert_eq!(notes_of(60, Chord::Dom7, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67, 70]);
2121 assert_eq!(notes_of(60, Chord::Min7, Voicing::Close, false, Mode::Chromatic), vec![60, 63, 67, 70]);
2122 assert_eq!(notes_of(60, Chord::Maj7, Voicing::Close, false, Mode::Chromatic), vec![60, 64, 67, 71]);
2123 assert_eq!(notes_of(60, Chord::Quartal, Voicing::Close, false, Mode::Chromatic), vec![60, 65, 70]);
2124 }
2125
2126 #[test]
2129 fn chord_identities_are_the_documented_order() {
2130 let order = [
2131 Chord::None, Chord::Fifth, Chord::Octave, Chord::Diatonic, Chord::Diatonic7,
2132 Chord::Maj, Chord::Min, Chord::Dim, Chord::Sus2, Chord::Sus4, Chord::Maj6,
2133 Chord::Min6, Chord::Dom7, Chord::Min7, Chord::Maj7, Chord::Quartal,
2134 ];
2135 for (index, chord) in order.iter().enumerate() {
2136 assert_eq!(chord.index() as usize, index);
2137 assert_eq!(Chord::from_index(index as u8), *chord);
2138 }
2139 assert_eq!(Chord::from_index(200), Chord::None, "an unknown id is one note");
2140 }
2141
2142 #[test]
2145 fn drop_two_lowers_the_second_voice_from_the_top() {
2146 assert_eq!(
2148 notes_of(60, Chord::Maj, Voicing::Drop2, false, Mode::Chromatic),
2149 vec![52, 60, 67]
2150 );
2151 assert_eq!(
2153 notes_of(60, Chord::Maj7, Voicing::Drop2, false, Mode::Chromatic),
2154 vec![55, 60, 64, 71]
2155 );
2156 }
2157
2158 #[test]
2159 fn inversions_lift_the_bottom_voices() {
2160 assert_eq!(
2161 notes_of(60, Chord::Maj, Voicing::First, false, Mode::Chromatic),
2162 vec![64, 67, 72]
2163 );
2164 assert_eq!(
2165 notes_of(60, Chord::Maj, Voicing::Second, false, Mode::Chromatic),
2166 vec![67, 72, 76]
2167 );
2168 }
2169
2170 #[test]
2171 fn root_below_adds_the_bass_double() {
2172 assert_eq!(
2173 notes_of(60, Chord::Maj, Voicing::Close, true, Mode::Chromatic),
2174 vec![48, 60, 64, 67]
2175 );
2176 }
2177
2178 #[test]
2183 fn every_chord_and_voicing_is_playable() {
2184 for &chord in &Chord::ALL {
2185 for &voicing in &Voicing::ALL {
2186 for below in [false, true] {
2187 for &mode in &Mode::ALL {
2188 for root in 24..=96u8 {
2189 let notes = notes_of(root, chord, voicing, below, mode);
2190 assert!(!notes.is_empty(), "{chord:?} produced nothing");
2191 assert!(notes.len() <= MAX_CHORD_NOTES);
2192 let mut seen = notes.clone();
2193 seen.dedup();
2194 assert_eq!(seen, notes, "{chord:?}/{voicing:?} doubled a note");
2195 for window in notes.windows(2) {
2196 assert!(window[0] < window[1], "not ascending");
2197 }
2198 }
2199 }
2200 }
2201 }
2202 }
2203 }
2204
2205 #[test]
2210 fn voicings_preserve_the_pitch_class_set() {
2211 for &chord in &Chord::ALL {
2212 for &mode in &Mode::ALL {
2213 for root in 36..=84u8 {
2214 let classes = |notes: Vec<u8>| {
2215 let mut c: Vec<u8> = notes.iter().map(|n| n % 12).collect();
2216 c.sort_unstable();
2217 c.dedup();
2218 c
2219 };
2220 let close = classes(notes_of(root, chord, Voicing::Close, false, mode));
2221 for &voicing in &Voicing::ALL {
2222 for below in [false, true] {
2223 assert_eq!(
2224 classes(notes_of(root, chord, voicing, below, mode)),
2225 close,
2226 "{chord:?} changed identity under {voicing:?} below={below}"
2227 );
2228 }
2229 }
2230 }
2231 }
2232 }
2233 }
2234
2235 #[test]
2239 fn diatonic_triads_have_the_textbook_qualities_in_every_mode() {
2240 let expected: [(Mode, [Chord; 7]); 7] = [
2241 (Mode::Ionian, [Chord::Maj, Chord::Min, Chord::Min, Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim]),
2242 (Mode::Dorian, [Chord::Min, Chord::Min, Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj]),
2243 (Mode::Phrygian, [Chord::Min, Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj, Chord::Min]),
2244 (Mode::Lydian, [Chord::Maj, Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj, Chord::Min, Chord::Min]),
2245 (Mode::Mixolydian, [Chord::Maj, Chord::Min, Chord::Dim, Chord::Maj, Chord::Min, Chord::Min, Chord::Maj]),
2246 (Mode::Aeolian, [Chord::Min, Chord::Dim, Chord::Maj, Chord::Min, Chord::Min, Chord::Maj, Chord::Maj]),
2247 (Mode::Locrian, [Chord::Dim, Chord::Maj, Chord::Min, Chord::Min, Chord::Maj, Chord::Maj, Chord::Min]),
2248 ];
2249
2250 for tonic in 0..12u8 {
2251 for (mode, qualities) in &expected {
2252 let scale = mode.scale().expect("a mode has a scale");
2253 for (degree, &quality) in qualities.iter().enumerate() {
2254 let root = 60 + i32::from(tonic) + scale[degree];
2255 let root = root as u8;
2256 let mut derived = [0u8; MAX_CHORD_NOTES];
2257 let n = chord_notes(
2258 root, Chord::Diatonic, Voicing::Close, false, *mode, tonic, &mut derived,
2259 );
2260 let mut explicit = [0u8; MAX_CHORD_NOTES];
2261 let m = chord_notes(
2262 root, quality, Voicing::Close, false, *mode, tonic, &mut explicit,
2263 );
2264 assert_eq!(
2265 derived[..n],
2266 explicit[..m],
2267 "{mode:?} degree {} in tonic {tonic} should be {quality:?}",
2268 degree + 1
2269 );
2270 }
2271 }
2272 }
2273 }
2274
2275 #[test]
2279 fn the_seventh_degree_is_half_diminished() {
2280 let mut out = [0u8; MAX_CHORD_NOTES];
2281 let n = chord_notes(71, Chord::Diatonic7, Voicing::Close, false, Mode::Ionian, 0, &mut out);
2282 assert_eq!(&out[..n], &[71, 74, 77, 81], "B D F A is not m7♭5");
2283 }
2284
2285 #[test]
2289 fn the_diatonic_chords_collapse_under_chromatic() {
2290 assert_eq!(
2291 notes_of(60, Chord::Diatonic, Voicing::Close, false, Mode::Chromatic),
2292 notes_of(60, Chord::Maj, Voicing::Close, false, Mode::Chromatic)
2293 );
2294 assert_eq!(
2295 notes_of(60, Chord::Diatonic7, Voicing::Close, false, Mode::Chromatic),
2296 notes_of(60, Chord::Maj7, Voicing::Close, false, Mode::Chromatic)
2297 );
2298 }
2299
2300 #[test]
2304 fn a_borrowed_root_falls_back_to_major() {
2305 assert_eq!(Mode::Ionian.degree_of(61, 0), None);
2306 assert_eq!(
2307 notes_of(61, Chord::Diatonic, Voicing::Close, false, Mode::Ionian),
2308 vec![61, 65, 68]
2309 );
2310 }
2311
2312 #[test]
2315 fn chromatic_walking_is_semitones() {
2316 assert_eq!(Mode::Chromatic.walk(60, 0, 1), 61);
2317 assert_eq!(Mode::Chromatic.walk(60, 0, -1), 59);
2318 assert_eq!(Mode::Chromatic.walk(0, 0, -1), 0, "the bottom of the range holds");
2319 assert_eq!(Mode::Chromatic.walk(127, 0, 1), 127);
2320 }
2321
2322 #[test]
2323 fn mode_walking_is_scale_degrees() {
2324 let mut note = 60;
2326 for expected in [62, 64, 65, 67, 69, 71, 72, 74] {
2327 note = Mode::Ionian.walk(note, 0, 1);
2328 assert_eq!(note, expected);
2329 }
2330 let mut note = 60;
2331 for expected in [59, 57, 55, 53, 52, 50, 48] {
2332 note = Mode::Ionian.walk(note, 0, -1);
2333 assert_eq!(note, expected);
2334 }
2335 }
2336
2337 #[test]
2341 fn walking_snaps_a_borrowed_note_onto_the_scale() {
2342 assert_eq!(Mode::Ionian.walk(61, 0, 1), 62, "C# up lands on D");
2343 assert_eq!(Mode::Ionian.walk(61, 0, -1), 60, "C# down lands on C");
2344 }
2345
2346 #[test]
2347 fn every_mode_walks_a_full_octave_in_seven_degrees() {
2348 for &mode in &Mode::ALL {
2349 if mode == Mode::Chromatic {
2350 continue;
2351 }
2352 for tonic in 0..12u8 {
2353 let start = 60 + tonic;
2354 let start = mode.walk(start, tonic, 0);
2355 let mut note = start;
2356 for _ in 0..7 {
2357 note = mode.walk(note, tonic, 1);
2358 }
2359 assert_eq!(note, start + 12, "{mode:?} in {tonic} did not close");
2360 }
2361 }
2362 }
2363
2364 #[test]
2367 fn switch_boundaries_are_the_next_grid_line() {
2368 let pattern = 3840;
2369 assert_eq!(SwitchQuant::Immediate.boundary(1234, pattern), 1234);
2370 assert_eq!(SwitchQuant::Beat.boundary(1234, pattern), 1920);
2371 assert_eq!(SwitchQuant::Bar.boundary(1234, pattern), 3840);
2372 assert_eq!(SwitchQuant::PatternEnd.boundary(1234, pattern), 3840);
2373 assert_eq!(SwitchQuant::PatternEnd.boundary(4000, 2880), 5760);
2374 }
2375
2376 #[test]
2380 fn a_boundary_already_reached_is_the_answer() {
2381 assert_eq!(SwitchQuant::Bar.boundary(3840, 3840), 3840);
2382 assert_eq!(SwitchQuant::Beat.boundary(960, 3840), 960);
2383 assert_eq!(SwitchQuant::PatternEnd.boundary(0, 3840), 0);
2384 }
2385
2386 const TPS: f64 = 120.0 * 960.0 / (60.0 * 44_100.0);
2390
2391 fn window(position: i64, frames: u32, previous: Option<PlaybackWindow>) -> PlaybackWindow {
2392 PlaybackWindow::for_block(position, frames, TPS, None, previous)
2393 }
2394
2395 #[test]
2396 fn the_first_window_starts_where_the_transport_is() {
2397 let w = window(1000, 512, None);
2398 assert_eq!(w.from(), 1000);
2399 assert!(!w.is_continuous(), "there is nothing for it to continue from");
2400 }
2401
2402 #[test]
2406 fn a_window_continues_from_the_last_one_across_a_rounding_gap() {
2407 let first = window(0, 470, None);
2408 let span = first.to();
2409 let second = window(span + 1, 470, Some(first));
2411 assert_eq!(second.from(), span, "a tick of song time was skipped");
2412 assert!(second.is_continuous());
2413 assert_eq!(second.to(), span + 1 + span);
2414 }
2415
2416 #[test]
2419 fn a_jump_breaks_continuity() {
2420 let first = window(0, 512, None);
2421 let jumped = window(100_000, 512, Some(first));
2422 assert_eq!(jumped.from(), 100_000);
2423 assert!(!jumped.is_continuous());
2424 }
2425
2426 #[test]
2430 fn a_loop_wrap_starts_the_window_at_the_loop_point() {
2431 let previous = PlaybackWindow::for_block(3800, 512, TPS, Some((0, 3840)), None);
2432 let wrapped = PlaybackWindow::for_block(3, 512, TPS, Some((0, 3840)), Some(previous));
2433 assert_eq!(wrapped.from(), 0);
2434 assert!(!wrapped.is_continuous());
2435 }
2436
2437 #[test]
2440 fn a_window_never_reaches_past_the_loop_end() {
2441 let w = PlaybackWindow::for_block(3830, 4096, TPS, Some((0, 3840)), None);
2442 assert_eq!(w.to(), 3840);
2443 assert!(!w.contains(3840));
2444 }
2445
2446 #[test]
2449 fn sample_offsets_come_from_ticks_and_nothing_else() {
2450 let w = window(1000, 512, None);
2451 assert_eq!(w.sample_offset(1000), 0);
2452 assert_eq!(w.sample_offset(999), 0, "before the window is the first sample");
2453 assert_eq!(w.sample_offset(1000 + 22), (22.0 / TPS) as u32);
2454 assert_eq!(w.sample_offset(i64::MAX), 511, "past the block is the last sample");
2455 }
2456
2457 #[test]
2458 fn a_zero_length_block_has_no_samples_to_land_on() {
2459 let w = window(0, 0, None);
2460 assert_eq!(w.sample_offset(1000), 0);
2461 }
2462
2463 fn player_with(slot0: PatternBlock, slot1: PatternBlock) -> PatternPlayer {
2466 let mut player = PatternPlayer::new();
2467 player.apply(1, slot1);
2468 player.apply(0, slot0);
2469 player
2470 }
2471
2472 fn run_player(
2476 player: &mut PatternPlayer,
2477 start: i64,
2478 frames: u32,
2479 until: i64,
2480 ) -> Vec<PatternEvent> {
2481 let mut out = Vec::new();
2482 let mut position = start;
2483 let mut previous = None;
2484 while position < until {
2485 let w = window(position, frames, previous);
2486 player.render(&w, true, &mut out);
2487 position = w.to();
2488 previous = Some(w);
2489 }
2490 out
2491 }
2492
2493 fn tick_player(
2495 player: &mut PatternPlayer,
2496 position: i64,
2497 frames: u32,
2498 previous: Option<PlaybackWindow>,
2499 ) -> (PlaybackWindow, Vec<PatternEvent>) {
2500 let w = window(position, frames, previous);
2501 let mut out = Vec::new();
2502 player.render(&w, true, &mut out);
2503 (w, out)
2504 }
2505
2506 #[test]
2507 fn a_stopped_transport_produces_nothing_and_then_flushes_once() {
2508 let mut player = player_with(drum_pattern(16), PatternBlock::empty());
2509 let (w, events) = tick_player(&mut player, 0, 512, None);
2510 assert!(!events.is_empty());
2511 assert!(player.held_notes() > 0);
2512
2513 let mut out = Vec::new();
2514 player.render(&w, false, &mut out);
2515 assert_eq!(out.len(), 1, "the sounding note was not turned off");
2516 assert_eq!(out[0].status, 0x80);
2517 assert_eq!(player.held_notes(), 0);
2518
2519 let mut again = Vec::new();
2520 player.render(&w, false, &mut again);
2521 assert!(again.is_empty(), "the flush repeated");
2522 }
2523
2524 #[test]
2527 fn a_pattern_switch_ends_the_old_notes_before_starting_the_new_ones() {
2528 let mut a = drum_pattern(16);
2529 a.lanes[0].steps[15].gate = Step::TIE;
2530 let mut b = drum_pattern(16);
2531 b.lanes[0] = Lane::drum(42);
2532 for step in &mut b.lanes[0].steps {
2533 step.on = true;
2534 }
2535
2536 let mut player = player_with(a, b);
2537 let mut queue = a;
2539 queue.pending_slot = Some(1);
2540 player.apply(0, queue);
2541 assert_eq!(player.countdown(3600), Some((1, 1)), "one step to go");
2542
2543 let out = run_player(&mut player, 3500, 512, 3900);
2546
2547 let at_boundary: Vec<(u8, u8)> = out
2548 .iter()
2549 .filter(|e| e.tick == 3840)
2550 .map(|e| (e.status, e.data1))
2551 .collect();
2552 assert_eq!(
2553 at_boundary,
2554 vec![(0x80, 36), (0x90, 42)],
2555 "the old note has to be ended before the new one starts"
2556 );
2557 assert_eq!(player.live_slot(), 1);
2558 assert_eq!(player.queued_slot(), None);
2559 }
2560
2561 #[test]
2565 fn an_immediate_switch_takes_effect_at_the_start_of_the_block() {
2566 let a = drum_pattern(16);
2567 let mut b = drum_pattern(16);
2568 b.lanes[0] = Lane::drum(42);
2569 for step in &mut b.lanes[0].steps {
2570 step.on = true;
2571 }
2572
2573 let mut player = player_with(a, b);
2574 let mut queued = a;
2575 queued.pending_slot = Some(1);
2576 queued.switch_quant = SwitchQuant::Immediate;
2577 player.apply(0, queued);
2578
2579 let w = window(480, 512, None);
2582 let mut out = Vec::new();
2583 player.render(&w, true, &mut out);
2584 assert_eq!(player.live_slot(), 1);
2585 let first = out.iter().find(|e| e.is_note_on()).expect("a note");
2586 assert_eq!(first.data1, 42, "the old pattern played after an immediate switch");
2587 assert_eq!(first.tick, 480);
2588 }
2589
2590 #[test]
2593 fn a_beat_quantized_switch_splits_the_block_at_the_beat() {
2594 let a = drum_pattern(16);
2595 let mut b = drum_pattern(16);
2596 b.lanes[0] = Lane::drum(42);
2597 for step in &mut b.lanes[0].steps {
2598 step.on = true;
2599 }
2600
2601 let mut player = player_with(a, b);
2602 let mut queued = a;
2603 queued.pending_slot = Some(1);
2604 queued.switch_quant = SwitchQuant::Beat;
2605 player.apply(0, queued);
2606
2607 let out = run_player(&mut player, 700, 512, 1100);
2608 let switched: Vec<(i64, u8)> = out
2609 .iter()
2610 .filter(|e| e.is_note_on())
2611 .map(|e| (e.tick, e.data1))
2612 .collect();
2613 assert_eq!(
2614 switched,
2615 vec![(720, 36), (960, 42)],
2616 "the switch did not land on the beat"
2617 );
2618 assert_eq!(player.live_slot(), 1);
2619 }
2620
2621 #[test]
2625 fn queueing_the_live_slot_does_nothing() {
2626 let mut block = drum_pattern(16);
2627 block.pending_slot = Some(0);
2628 let player = player_with(block, PatternBlock::empty());
2629 assert_eq!(player.queued_slot(), None);
2630 assert_eq!(player.countdown(0), None);
2631 }
2632
2633 #[test]
2637 fn a_chain_is_derived_from_the_position() {
2638 let a = drum_pattern(16);
2639 let mut b = drum_pattern(16);
2640 b.lanes[0] = Lane::drum(42);
2641 for step in &mut b.lanes[0].steps {
2642 step.on = true;
2643 }
2644
2645 let mut chained = a;
2646 chained.chain[0] = ChainEntry { slot: 0, repeats: 2 };
2647 chained.chain[1] = ChainEntry { slot: 1, repeats: 1 };
2648 chained.chain_len = 2;
2649
2650 let mut player = PatternPlayer::new();
2651 player.apply(1, b);
2652 player.apply(0, chained);
2653
2654 let cycle = 3840;
2655 for (position, expected) in [
2657 (0, 36),
2658 (cycle, 36),
2659 (cycle * 2, 42),
2660 (cycle * 3, 36),
2661 (cycle * 5, 42),
2662 ] {
2663 let mut out = Vec::new();
2664 let w = window(position, 512, None);
2665 player.render(&w, true, &mut out);
2666 let first = out.iter().find(|e| e.is_note_on()).expect("a note");
2667 assert_eq!(first.data1, expected, "wrong chain entry at tick {position}");
2668 }
2669 }
2670
2671 #[test]
2674 fn a_chain_advance_ends_the_notes_it_replaces() {
2675 let mut a = drum_pattern(16);
2676 a.lanes[0].steps[15].gate = Step::TIE;
2677 let mut b = drum_pattern(16);
2678 b.lanes[0] = Lane::drum(42);
2679 b.lanes[0].steps[0].on = true;
2680
2681 let mut chained = a;
2682 chained.chain[0] = ChainEntry { slot: 0, repeats: 1 };
2683 chained.chain[1] = ChainEntry { slot: 1, repeats: 1 };
2684 chained.chain_len = 2;
2685
2686 let mut player = PatternPlayer::new();
2687 player.apply(1, b);
2688 player.apply(0, chained);
2689
2690 let out = run_player(&mut player, 3500, 512, 3900);
2691 let at_boundary: Vec<(u8, u8)> = out
2692 .iter()
2693 .filter(|e| e.tick == 3840)
2694 .map(|e| (e.status, e.data1))
2695 .collect();
2696 assert_eq!(at_boundary, vec![(0x80, 36), (0x90, 42)]);
2697 }
2698
2699 #[test]
2705 fn a_bounced_cycle_is_tick_identical_to_live_playback() {
2706 for swing in [50u8, 58, 62, 75] {
2707 for rate in Rate::ALL {
2708 let mut block = drum_pattern(16);
2709 block.rate = rate;
2710 block.swing = swing;
2711 block.lanes[0].steps[3].gate = 150;
2712 block.lanes[0].steps[7].gate = Step::TIE;
2713 block.lanes[0].steps[9].accent = true;
2714
2715 let mut bounced = Vec::new();
2716 compile_cycle(&block, 0, &mut bounced);
2717
2718 let cycle = block.length_ticks();
2720 let mut live = Vec::new();
2721 let mut pending = PendingOffs::new();
2722 let mut from = 0;
2723 while from < cycle {
2724 let to = (from + 97).min(cycle);
2725 generate(&block, 0, from, to, &mut pending, &mut live);
2726 from = to;
2727 }
2728 pending.flush(cycle, &mut live);
2729 live.sort_by_key(|e| e.tick);
2730
2731 let key = |e: &PatternEvent| (e.tick, e.status, e.data1, e.data2);
2732 let bounced: Vec<_> = bounced.iter().map(key).collect();
2733 let live: Vec<_> = live.iter().map(key).collect();
2734 assert_eq!(bounced, live, "swing {swing} at {}", rate.label());
2735 }
2736 }
2737 }
2738
2739 #[test]
2745 fn rendering_a_pattern_does_not_allocate() {
2746 let mut a = drum_pattern(16);
2747 a.lanes[0].steps[15].gate = Step::TIE;
2748 let mut b = drum_pattern(16);
2749 b.lanes[0] = Lane::drum(42);
2750
2751 let mut player = Box::new(player_with(a, b));
2752 let mut sink = Vec::with_capacity(1024);
2753 let mut queued = a;
2754 queued.pending_slot = Some(1);
2755
2756 let mut w = window(0, 512, None);
2758 player.render(&w, true, &mut sink);
2759
2760 let allocations = crate::alloc_count::allocations_during(|| {
2761 let mut position = 0;
2762 for block in 0..64 {
2763 w = window(position, 512, Some(w));
2764 sink.clear();
2765 player.render(&w, true, &mut sink);
2766 if block == 8 {
2767 player.apply(0, queued);
2768 }
2769 position = w.to();
2770 }
2771 });
2772 assert_eq!(allocations, 0, "the pattern player reached the allocator");
2773 }
2774
2775 #[test]
2779 fn a_full_sink_stops_the_generator() {
2780 struct Capped(Vec<PatternEvent>, usize);
2781 impl EventSink for Capped {
2782 fn accept(&mut self, event: PatternEvent) -> bool {
2783 if self.0.len() >= self.1 {
2784 return false;
2785 }
2786 self.0.push(event);
2787 true
2788 }
2789 }
2790
2791 let block = drum_pattern(16);
2792 let mut sink = Capped(Vec::new(), 3);
2793 let mut pending = PendingOffs::new();
2794 generate(&block, 0, 0, block.length_ticks(), &mut pending, &mut sink);
2795 assert_eq!(sink.0.len(), 3, "the sink was written past its cap");
2796 }
2797}
2798