ot_tools_io/banks/patterns.rs
1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Models for pattern data within a bank.
7use crate::{
8 banks::parts::{
9 AudioTrackAmpParamsValues, AudioTrackFxParamsValues, LfoParamsValues,
10 MidiTrackArpParamsValues, MidiTrackCc1ParamsValues, MidiTrackCc2ParamsValues,
11 MidiTrackLfoParamsValues, MidiTrackMidiParamsValues,
12 },
13 CheckHeader, DefaultsArrayBoxed, OptionEnumValueConvert, OtToolsIoErrors,
14};
15use ot_tools_io_derive::DefaultsAsBoxedBigArray;
16use std::array::from_fn;
17
18use crate::RBoxErr;
19use serde::{Deserialize, Serialize};
20use serde_big_array::{Array, BigArray};
21
22const HALF_PAGE_TRIG_BITMASK_VALUES: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128];
23const PATTERN_HEADER: [u8; 8] = [0x50, 0x54, 0x52, 0x4e, 0x00, 0x00, 0x00, 0x00];
24
25/// Header array for a MIDI track section in binary data files: `MTRA`
26const MIDI_TRACK_HEADER: [u8; 4] = [0x4d, 0x54, 0x52, 0x41];
27
28/// Header array for a MIDI track section in binary data files: `TRAC`
29const AUDIO_TRACK_HEADER: [u8; 4] = [0x54, 0x52, 0x41, 0x43];
30
31/// Given a half-page trig bit mask, get an array of 8x boolean values
32/// indicating whether each trig in the half-page is active or not
33pub fn get_halfpage_trigs_from_bitmask_value(bitmask: &u8) -> RBoxErr<[bool; 8]> {
34 let arr: [bool; 8] = HALF_PAGE_TRIG_BITMASK_VALUES
35 .iter()
36 .map(|x| (bitmask & x) > 0)
37 .collect::<Vec<bool>>()
38 .try_into()
39 .unwrap();
40 Ok(arr)
41}
42
43/// Given a half-page trig bit mask, get an array of 8x boolean values
44/// indicating where each trig in the half-page is active or not
45pub fn get_track_trigs_from_bitmasks(bitmasks: &[u8; 8]) -> RBoxErr<[bool; 64]> {
46 let trigs: [bool; 64] = bitmasks
47 .iter()
48 .flat_map(|x: &u8| get_halfpage_trigs_from_bitmask_value(x).unwrap())
49 .collect::<Vec<bool>>()
50 .try_into()
51 .unwrap();
52
53 Ok(trigs)
54}
55
56/// A Trig's parameter locks on the Playback/Machine page for an Audio Track.
57#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
58pub struct AudioTrackParameterLockPlayback {
59 pub param1: u8,
60 pub param2: u8,
61 pub param3: u8,
62 pub param4: u8,
63 pub param5: u8,
64 pub param6: u8,
65}
66
67impl Default for AudioTrackParameterLocks {
68 fn default() -> Self {
69 // 255 -> disabled
70
71 // NOTE: the `part.rs` `default` methods for each of these type has
72 // fields all set to the correct defaults for the TRACK view, not p-lock
73 // trigS. So don't try and use the type's `default` method here as you
74 // will end up with a bunch of p-locks on trigs for all the default
75 // values. (Although maybe that's a desired feature for some workflows).
76
77 // Yes, this comment is duplicated below. It is to make sur you've seen
78 // it.
79 Self {
80 machine: AudioTrackParameterLockPlayback {
81 param1: 255,
82 param2: 255,
83 param3: 255,
84 param4: 255,
85 param5: 255,
86 param6: 255,
87 },
88 lfo: LfoParamsValues {
89 spd1: 255,
90 spd2: 255,
91 spd3: 255,
92 dep1: 255,
93 dep2: 255,
94 dep3: 255,
95 },
96 amp: AudioTrackAmpParamsValues {
97 atk: 255,
98 hold: 255,
99 rel: 255,
100 vol: 255,
101 bal: 255,
102 unused: 255,
103 },
104 fx1: AudioTrackFxParamsValues {
105 param_1: 255,
106 param_2: 255,
107 param_3: 255,
108 param_4: 255,
109 param_5: 255,
110 param_6: 255,
111 },
112 fx2: AudioTrackFxParamsValues {
113 param_1: 255,
114 param_2: 255,
115 param_3: 255,
116 param_4: 255,
117 param_5: 255,
118 param_6: 255,
119 },
120 static_slot_id: 255,
121 flex_slot_id: 255,
122 }
123 }
124}
125
126/// A single trig's parameter locks on an Audio Track.
127#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy, DefaultsAsBoxedBigArray)]
128pub struct AudioTrackParameterLocks {
129 pub machine: AudioTrackParameterLockPlayback,
130 pub lfo: LfoParamsValues,
131 pub amp: AudioTrackAmpParamsValues,
132 pub fx1: AudioTrackFxParamsValues,
133 pub fx2: AudioTrackFxParamsValues,
134 /// P-Lock to change an audio track's static machine sample slot assignment per trig
135 pub static_slot_id: u8,
136 /// P-Lock to change an audio track's flex machine sample slot assignment per trig
137 pub flex_slot_id: u8,
138}
139
140/// MIDI Track parameter locks.
141#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy, DefaultsAsBoxedBigArray)]
142pub struct MidiTrackParameterLocks {
143 pub midi: MidiTrackMidiParamsValues,
144 pub lfo: MidiTrackLfoParamsValues,
145 pub arp: MidiTrackArpParamsValues,
146 pub ctrl1: MidiTrackCc1ParamsValues,
147 pub ctrl2: MidiTrackCc2ParamsValues,
148
149 #[serde(with = "BigArray")]
150 unknown: [u8; 2],
151}
152
153impl Default for MidiTrackParameterLocks {
154 fn default() -> Self {
155 // 255 -> disabled
156
157 // NOTE: the `part.rs` `default` methods for each of these type has
158 // fields all set to the correct defaults for the TRACK view, not p-lock
159 // trigS. So don't try and use the type's `default` method here as you
160 // will end up with a bunch of p-locks on trigs for all the default
161 // values. (Although maybe that's a desired feature for some workflows).
162
163 // Yes, this comment is duplicated above. It is to make sur you've seen
164 // it.
165
166 Self {
167 midi: MidiTrackMidiParamsValues {
168 note: 255,
169 vel: 255,
170 len: 255,
171 not2: 255,
172 not3: 255,
173 not4: 255,
174 },
175 lfo: MidiTrackLfoParamsValues {
176 spd1: 255,
177 spd2: 255,
178 spd3: 255,
179 dep1: 255,
180 dep2: 255,
181 dep3: 255,
182 },
183 arp: MidiTrackArpParamsValues {
184 tran: 255,
185 leg: 255,
186 mode: 255,
187 spd: 255,
188 rnge: 255,
189 nlen: 255,
190 },
191 ctrl1: MidiTrackCc1ParamsValues {
192 pb: 255,
193 at: 255,
194 cc1: 255,
195 cc2: 255,
196 cc3: 255,
197 cc4: 255,
198 },
199 ctrl2: MidiTrackCc2ParamsValues {
200 cc5: 255,
201 cc6: 255,
202 cc7: 255,
203 cc8: 255,
204 cc9: 255,
205 cc10: 255,
206 },
207 unknown: [255, 255],
208 }
209 }
210}
211
212/// Audio & MIDI Track Pattern playback settings.
213#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
214pub struct TrackPatternSettings {
215 /// Silence any existing audio playback on the Audio Track when switching Patterns.
216 pub start_silent: u8,
217
218 /// Trigger Audio Track playback without any quantization or syncing to other Audio Tracks.
219 pub plays_free: u8,
220
221 /// Quantization when this Audio Track is Triggered for Playback.
222 ///
223 /// Options
224 /// ```text
225 /// N/A and ONE: 0 (Default)
226 /// ONE2: 1
227 /// HOLD: 2
228 /// ```
229 pub trig_mode: u8,
230
231 /// Track Trigger Quantization.
232 ///
233 /// Options
234 /// ```text
235 /// N/A and TR.LEN: 0 (Default)
236 /// 1/16: 1
237 /// 2/16: 2
238 /// 3/16: 3
239 /// 4/16: 4
240 /// 6/16: 5
241 /// 8/16: 6
242 /// 12/16: 7
243 /// 16/16: 8
244 /// 24/16: 9
245 /// 32/16: 10
246 /// 48/16: 11
247 /// 64/16: 12
248 /// 96/16: 13
249 /// 128/16: 14
250 /// 192/16: 15
251 /// 256/16: 16
252 /// DIRECT: 255
253 /// ```
254 pub trig_quant: u8,
255
256 /// Whether to play the track as a `ONESHOT` track.
257 pub oneshot_trk: u8,
258}
259
260impl Default for TrackPatternSettings {
261 fn default() -> Self {
262 Self {
263 start_silent: 255,
264 plays_free: 0,
265 trig_mode: 0,
266 trig_quant: 0,
267 oneshot_trk: 0,
268 }
269 }
270}
271
272/// Trig bitmasks array for Audio Tracks.
273/// Can be converted into an array of booleans using the `get_track_trigs_from_bitmasks` function.
274///
275/// Trig bitmask arrays have bitmasks stored in this order, which is slightly confusing (pay attention to the difference with 7 + 8!):
276/// 1. 1st half of the 4th page
277/// 2. 2nd half of the 4th page
278/// 3. 1st half of the 3rd page
279/// 4. 2nd half of the 3rd page
280/// 5. 1st half of the 2nd page
281/// 6. 2nd half of the 2nd page
282/// 7. 2nd half of the 1st page
283/// 8. 1st half of the 1st page
284///
285/// ### Bitmask values for trig positions
286/// With single trigs in a half-page
287/// ```text
288/// positions
289/// 1 2 3 4 5 6 7 8 | mask value
290/// ----------------|-----------
291/// - - - - - - - - | 0
292/// x - - - - - - - | 1
293/// - x - - - - - - | 2
294/// - - x - - - - - | 4
295/// - - - x - - - - | 8
296/// - - - - x - - - | 16
297/// - - - - - x - - | 32
298/// - - - - - - x - | 64
299/// - - - - - - - x | 128
300/// ```
301///
302/// When there are multiple trigs in a half-page, the individual position values are summed together:
303///
304/// ```text
305/// 1 2 3 4 5 6 7 8 | mask value
306/// ----------------|-----------
307/// x x - - - - - - | 1 + 2 = 3
308/// x x x x - - - - | 1 + 2 + 4 + 8 = 15
309/// ```
310/// ### Fuller diagram of mask values
311///
312/// ```text
313/// positions
314/// 1 2 3 4 5 6 7 8 | mask value
315/// ----------------|-----------
316/// x - - - - - - - | 1
317/// - x - - - - - - | 2
318/// x x - - - - - - | 3
319/// - - x - - - - - | 4
320/// x - x - - - - - | 5
321/// - x x - - - - - | 6
322/// x x x - - - - - | 7
323/// - - - x - - - - | 8
324/// x - - x - - - - | 9
325/// - x - x - - - - | 10
326/// x x - x - - - - | 11
327/// - - x x - - - - | 12
328/// x - x x - - - - | 13
329/// - x x x - - - - | 14
330/// x x x x - - - - | 15
331/// ................|....
332/// x x x x x x - - | 63
333/// ................|....
334/// - - - - - - - x | 128
335/// ................|....
336/// - x - x - x - x | 170
337/// ................|....
338/// - - - - x x x x | 240
339/// ................|....
340/// x x x x x x x x | 255
341/// ```
342///
343#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
344pub struct AudioTrackTrigMasks {
345 /// Trigger Trig masks -- indicate which Trigger Trigs are active.
346 /// Base track Trig masks are stored backwards, meaning
347 /// the first 8 Trig positions are the last bytes in this section.
348 #[serde(with = "BigArray")]
349 pub trigger: [u8; 8],
350
351 /// Envelope Trig masks -- indicate which Envelope Trigs are active.
352 /// See the description of the `trig_trig_masks` field for an
353 /// explanation of how the masking works.
354 #[serde(with = "BigArray")]
355 pub trigless: [u8; 8],
356
357 /// Parameter-Lock Trig masks -- indicate which Parameter-Lock Trigs are active.
358 /// See the description of the `trig_trig_masks` field for an
359 /// explanation of how the masking works.
360 #[serde(with = "BigArray")]
361 pub plock: [u8; 8],
362
363 /// Hold Trig masks -- indicate which Hold Trigs are active.
364 /// See the description of the `trig_trig_masks` field for an
365 /// explanation of how the masking works.
366 #[serde(with = "BigArray")]
367 pub oneshot: [u8; 8],
368
369 /// Recorder Trig masks -- indicate which Recorder Trigs are active.
370 /// These seem to function differently to the main Track Trig masks.
371 /// Filling up Recorder Trigs on a Pattern results in a 32 length array
372 /// instead of 8 length.
373 /// Possible that the Trig type is stored in this array as well.
374 #[serde(with = "BigArray")]
375 pub recorder: [u8; 32],
376
377 /// Swing trigs Trig masks.
378 #[serde(with = "BigArray")]
379 pub swing: [u8; 8],
380
381 /// Parameter Slide trigs Trig masks.
382 #[serde(with = "BigArray")]
383 pub slide: [u8; 8],
384}
385
386impl Default for AudioTrackTrigMasks {
387 fn default() -> Self {
388 Self {
389 trigger: from_fn(|_| 0),
390 trigless: from_fn(|_| 0),
391 plock: from_fn(|_| 0),
392 oneshot: from_fn(|_| 0),
393 recorder: from_fn(|_| 0),
394 swing: from_fn(|_| 170),
395 slide: from_fn(|_| 0),
396 }
397 }
398}
399
400/// Audio Track custom scaling when the Pattern is in PER TRACK scale mode.
401#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
402pub struct TrackPerTrackModeScale {
403 /// The Audio Track's Length when Pattern is in Per Track mode.
404 /// Default: 16
405 pub per_track_len: u8,
406
407 /// The Audio Track's Scale when Pattern is in Per Track mode.
408 ///
409 /// Options
410 /// ```text
411 /// 0 -> 2x
412 /// 1 -> 3/2x
413 /// 2 -> 1x (Default)
414 /// 3 -> 3/4x
415 /// 4 -> 1/2x
416 /// 5 -> 1/4x
417 /// 6 -> 1/8x
418 /// ```
419 pub per_track_scale: u8,
420}
421
422impl Default for TrackPerTrackModeScale {
423 fn default() -> Self {
424 Self {
425 per_track_len: 16,
426 per_track_scale: 2,
427 }
428 }
429}
430
431/// Sample Slot options for Projects.
432#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Eq, Hash)]
433pub enum TrigCondition {
434 None,
435 /// > FILL is true (will activate the trig) when fill mode is active (see below).
436 Fill,
437 /// > ... true when FILL is not.
438 NotFill,
439 /// > PRE is true if the most recently evaluated trig condition on the same track was true.
440 Pre,
441 /// > ... true when PRE is not.
442 NotPre,
443 /// > true if the most recently evaluated trig condition on the neighbor track was true.
444 /// > The neighbor track is the track before the one being edited.
445 /// > For example, the neighbor track of track 4 is track 3. If no conditions exist on the
446 /// > neighbor track, the condition is false.
447 Nei,
448 /// > ... true when NEI is not.
449 NotNei,
450 /// > only true the first time the pattern play (when looped).
451 First,
452 /// > ... true when 1st is not.
453 NotFirst,
454 /// > probability condition. 1% chance of being true.
455 Percent1,
456 /// > probability condition. 2% chance of being true.
457 Percent2,
458 /// > probability condition. 4% chance of being true.
459 Percent4,
460 /// > probability condition. 6% chance of being true.
461 Percent6,
462 /// > probability condition. 9% chance of being true.
463 Percent9,
464 /// > probability condition. 13% chance of being true.
465 Percent13,
466 /// > probability condition. 19% chance of being true.
467 Percent19,
468 /// > probability condition. 25% chance of being true.
469 Percent25,
470 /// > probability condition. 33% chance of being true.
471 Percent33,
472 /// > probability condition. 41% chance of being true.
473 Percent41,
474 /// > probability condition. 50% chance of being true.
475 Percent50,
476 /// > probability condition. 59% chance of being true.
477 Percent59,
478 /// > probability condition. 67% chance of being true.
479 Percent67,
480 /// > probability condition. 75% chance of being true.
481 Percent75,
482 /// > probability condition. 81% chance of being true.
483 Percent81,
484 /// > probability condition. 87% chance of being true.
485 Percent87,
486 /// > probability condition. 91% chance of being true.
487 Percent91,
488 /// > probability condition. 94% chance of being true.
489 Percent94,
490 /// > probability condition. 96% chance of being true.
491 Percent96,
492 /// > probability condition. 98% chance of being true.
493 Percent98,
494 /// > probability condition. 99% chance of being true.
495 Percent99,
496 /// pattern loop 1 triggers, pattern loop 2 resets
497 PatternT1R2,
498 /// pattern loop 2 triggers, pattern loop 2 resets
499 PatternT2R2,
500 /// pattern loop 1 triggers, pattern loop 3 resets
501 PatternT1R3,
502 /// pattern loop 2 triggers, pattern loop 3 resets
503 PatternT2R3,
504 /// pattern loop 3 triggers, pattern loop 3 resets
505 PatternT3R3,
506 /// pattern loop 1 triggers, pattern loop 4 resets
507 PatternT1R4,
508 /// pattern loop 2 triggers, pattern loop 4 resets
509 PatternT2R4,
510 /// pattern loop 3 triggers, pattern loop 4 resets
511 PatternT3R4,
512 /// pattern loop 4 triggers, pattern loop 4 resets
513 PatternT4R4,
514 /// pattern loop 1 triggers, pattern loop 5 resets
515 PatternT1R5,
516 /// pattern loop 2 triggers, pattern loop 5 resets
517 PatternT2R5,
518 /// pattern loop 3 triggers, pattern loop 5 resets
519 PatternT3R5,
520 /// pattern loop 4 triggers, pattern loop 5 resets
521 PatternT4R5,
522 /// pattern loop 5 triggers, pattern loop 5 resets
523 PatternT5R5,
524 /// pattern loop 1 triggers, pattern loop 6 resets
525 PatternT1R6,
526 /// pattern loop 2 triggers, pattern loop 6 resets
527 PatternT2R6,
528 /// pattern loop 3 triggers, pattern loop 6 resets
529 PatternT3R6,
530 /// pattern loop 4 triggers, pattern loop 6 resets
531 PatternT4R6,
532 /// pattern loop 5 triggers, pattern loop 6 resets
533 PatternT5R6,
534 /// pattern loop 6 triggers, pattern loop 6 resets
535 PatternT6R6,
536 /// pattern loop 1 triggers, pattern loop 7 resets
537 PatternT1R7,
538 /// pattern loop 2 triggers, pattern loop 7 resets
539 PatternT2R7,
540 /// pattern loop 3 triggers, pattern loop 7 resets
541 PatternT3R7,
542 /// pattern loop 4 triggers, pattern loop 7 resets
543 PatternT4R7,
544 /// pattern loop 5 triggers, pattern loop 7 resets
545 PatternT5R7,
546 /// pattern loop 6 triggers, pattern loop 7 resets
547 PatternT6R7,
548 /// pattern loop 7 triggers, pattern loop 7 resets
549 PatternT7R7,
550 /// pattern loop 1 triggers, pattern loop 8 resets
551 PatternT1R8,
552 /// pattern loop 2 triggers, pattern loop 8 resets
553 PatternT2R8,
554 /// pattern loop 3 triggers, pattern loop 8 resets
555 PatternT3R8,
556 /// pattern loop 4 triggers, pattern loop 8 resets
557 PatternT4R8,
558 /// pattern loop 5 triggers, pattern loop 8 resets
559 PatternT5R8,
560 /// pattern loop 6 triggers, pattern loop 8 resets
561 PatternT6R8,
562 /// pattern loop 7 triggers, pattern loop 8 resets
563 PatternT7R8,
564 /// pattern loop 8 triggers, pattern loop 8 resets
565 PatternT8R8,
566}
567
568impl OptionEnumValueConvert for TrigCondition {
569 type T = TrigCondition;
570 type V = u8;
571
572 fn from_value(v: &Self::V) -> RBoxErr<Self::T> {
573 // read the essay for `AudioTrackTrigs.trig_timings_repeats_conditions`
574 // to understand why rem_euclid is used here
575 match v.rem_euclid(128) {
576 0 => Ok(TrigCondition::None),
577 1 => Ok(TrigCondition::Fill),
578 2 => Ok(TrigCondition::NotFill),
579 3 => Ok(TrigCondition::Pre),
580 4 => Ok(TrigCondition::NotPre),
581 5 => Ok(TrigCondition::Nei),
582 6 => Ok(TrigCondition::NotNei),
583 7 => Ok(TrigCondition::First),
584 8 => Ok(TrigCondition::NotFirst),
585 //
586 9 => Ok(TrigCondition::Percent1),
587 10 => Ok(TrigCondition::Percent2),
588 11 => Ok(TrigCondition::Percent4),
589 12 => Ok(TrigCondition::Percent6),
590 13 => Ok(TrigCondition::Percent9),
591 14 => Ok(TrigCondition::Percent13),
592 15 => Ok(TrigCondition::Percent19),
593 16 => Ok(TrigCondition::Percent25),
594 17 => Ok(TrigCondition::Percent33),
595 18 => Ok(TrigCondition::Percent41),
596 19 => Ok(TrigCondition::Percent50),
597 20 => Ok(TrigCondition::Percent59),
598 21 => Ok(TrigCondition::Percent67),
599 22 => Ok(TrigCondition::Percent75),
600 23 => Ok(TrigCondition::Percent81),
601 24 => Ok(TrigCondition::Percent87),
602 25 => Ok(TrigCondition::Percent91),
603 26 => Ok(TrigCondition::Percent94),
604 27 => Ok(TrigCondition::Percent96),
605 28 => Ok(TrigCondition::Percent98),
606 29 => Ok(TrigCondition::Percent99),
607 //
608 30 => Ok(TrigCondition::PatternT1R2),
609 31 => Ok(TrigCondition::PatternT2R2),
610 //
611 32 => Ok(TrigCondition::PatternT1R3),
612 33 => Ok(TrigCondition::PatternT2R3),
613 34 => Ok(TrigCondition::PatternT3R3),
614 //
615 35 => Ok(TrigCondition::PatternT1R4),
616 36 => Ok(TrigCondition::PatternT2R4),
617 37 => Ok(TrigCondition::PatternT3R4),
618 38 => Ok(TrigCondition::PatternT4R4),
619 //
620 39 => Ok(TrigCondition::PatternT1R5),
621 40 => Ok(TrigCondition::PatternT2R5),
622 41 => Ok(TrigCondition::PatternT3R5),
623 42 => Ok(TrigCondition::PatternT4R5),
624 43 => Ok(TrigCondition::PatternT5R5),
625 //
626 44 => Ok(TrigCondition::PatternT1R6),
627 45 => Ok(TrigCondition::PatternT2R6),
628 46 => Ok(TrigCondition::PatternT3R6),
629 47 => Ok(TrigCondition::PatternT4R6),
630 48 => Ok(TrigCondition::PatternT5R6),
631 49 => Ok(TrigCondition::PatternT6R6),
632 //
633 50 => Ok(TrigCondition::PatternT1R7),
634 51 => Ok(TrigCondition::PatternT2R7),
635 52 => Ok(TrigCondition::PatternT3R7),
636 53 => Ok(TrigCondition::PatternT4R7),
637 54 => Ok(TrigCondition::PatternT5R7),
638 55 => Ok(TrigCondition::PatternT6R7),
639 56 => Ok(TrigCondition::PatternT7R7),
640 //
641 57 => Ok(TrigCondition::PatternT1R8),
642 58 => Ok(TrigCondition::PatternT2R8),
643 59 => Ok(TrigCondition::PatternT3R8),
644 60 => Ok(TrigCondition::PatternT4R8),
645 61 => Ok(TrigCondition::PatternT5R8),
646 62 => Ok(TrigCondition::PatternT6R8),
647 63 => Ok(TrigCondition::PatternT7R8),
648 64 => Ok(TrigCondition::PatternT8R8),
649 //
650 _ => Err(OtToolsIoErrors::NoMatchingOptionEnumValue.into()),
651 }
652 }
653
654 fn value(&self) -> RBoxErr<Self::V> {
655 match self {
656 Self::None => Ok(0),
657 Self::Fill => Ok(1),
658 Self::NotFill => Ok(2),
659 Self::Pre => Ok(3),
660 Self::NotPre => Ok(4),
661 Self::Nei => Ok(5),
662 Self::NotNei => Ok(6),
663 Self::First => Ok(7),
664 Self::NotFirst => Ok(8),
665 Self::Percent1 => Ok(9),
666 Self::Percent2 => Ok(10),
667 Self::Percent4 => Ok(11),
668 Self::Percent6 => Ok(12),
669 Self::Percent9 => Ok(13),
670 Self::Percent13 => Ok(14),
671 Self::Percent19 => Ok(15),
672 Self::Percent25 => Ok(16),
673 Self::Percent33 => Ok(17),
674 Self::Percent41 => Ok(18),
675 Self::Percent50 => Ok(19),
676 Self::Percent59 => Ok(20),
677 Self::Percent67 => Ok(21),
678 Self::Percent75 => Ok(22),
679 Self::Percent81 => Ok(23),
680 Self::Percent87 => Ok(24),
681 Self::Percent91 => Ok(25),
682 Self::Percent94 => Ok(26),
683 Self::Percent96 => Ok(27),
684 Self::Percent98 => Ok(28),
685 Self::Percent99 => Ok(29),
686 Self::PatternT1R2 => Ok(30),
687 Self::PatternT2R2 => Ok(31),
688 Self::PatternT1R3 => Ok(32),
689 Self::PatternT2R3 => Ok(33),
690 Self::PatternT3R3 => Ok(34),
691 Self::PatternT1R4 => Ok(35),
692 Self::PatternT2R4 => Ok(36),
693 Self::PatternT3R4 => Ok(37),
694 Self::PatternT4R4 => Ok(38),
695 Self::PatternT1R5 => Ok(39),
696 Self::PatternT2R5 => Ok(40),
697 Self::PatternT3R5 => Ok(41),
698 Self::PatternT4R5 => Ok(42),
699 Self::PatternT5R5 => Ok(43),
700 Self::PatternT1R6 => Ok(44),
701 Self::PatternT2R6 => Ok(45),
702 Self::PatternT3R6 => Ok(46),
703 Self::PatternT4R6 => Ok(47),
704 Self::PatternT5R6 => Ok(48),
705 Self::PatternT6R6 => Ok(49),
706 Self::PatternT1R7 => Ok(50),
707 Self::PatternT2R7 => Ok(51),
708 Self::PatternT3R7 => Ok(52),
709 Self::PatternT4R7 => Ok(53),
710 Self::PatternT5R7 => Ok(54),
711 Self::PatternT6R7 => Ok(55),
712 Self::PatternT7R7 => Ok(56),
713 Self::PatternT1R8 => Ok(57),
714 Self::PatternT2R8 => Ok(58),
715 Self::PatternT3R8 => Ok(59),
716 Self::PatternT4R8 => Ok(60),
717 Self::PatternT5R8 => Ok(61),
718 Self::PatternT6R8 => Ok(62),
719 Self::PatternT7R8 => Ok(63),
720 Self::PatternT8R8 => Ok(64),
721 }
722 }
723}
724
725/// Track trigs assigned on an Audio Track within a Pattern
726#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
727pub struct AudioTrackTrigs {
728 /// Header data section
729 ///
730 /// example data:
731 /// ```text
732 /// TRAC
733 /// 54 52 41 43
734 /// ```
735 #[serde(with = "BigArray")]
736 pub header: [u8; 4],
737
738 /// Unknown data.
739 #[serde(with = "BigArray")]
740 pub unknown_1: [u8; 4],
741
742 /// The zero indexed track number
743 pub track_id: u8,
744
745 /// Trig masks contain the Trig step locations for different trig types
746 pub trig_masks: AudioTrackTrigMasks,
747
748 /// The scale of this Audio Track in Per Track Pattern mode.
749 pub scale_per_track_mode: TrackPerTrackModeScale,
750
751 /// Amount of swing when a Swing Trig is active for the Track.
752 /// Maximum is `30` (`80` on device), minimum is `0` (`50` on device).
753 pub swing_amount: u8,
754
755 /// Pattern settings for this Audio Track
756 pub pattern_settings: TrackPatternSettings,
757
758 /// Unknown data.
759 pub unknown_2: u8,
760
761 /// Parameter-Lock data for all Trigs.
762 // note -- stack overflow if tring to use #[serde(with = "BigArray")]
763 pub plocks: Box<Array<AudioTrackParameterLocks, 64>>,
764
765 /// What the hell is this field?!?!
766 /// It **has to** be something to do with trigs, but i have no idea what it could be.
767 #[serde(with = "BigArray")]
768 pub unknown_3: [u8; 64],
769
770 /// Trig Offsets, Trig Counts and Trig Conditions
771 /// ====
772 /// This is ..... slightly frustrating.
773 ///
774 /// This 64 length array consisting of a pair of bytes for each array element hold three
775 /// data references... Trig Cunts and Trig Conditions use the two bytes independently,
776 /// so they're easier to explain first
777 ///
778 /// Trig Counts and Trig Conditions
779 /// ====
780 ///
781 /// Trig Counts and Trig Conditions data is interleaved for each trig.
782 /// For Trig position 1, array index 0 is the count value and array index 1 is the Trig
783 /// Condition.
784 ///
785 /// For trig counts (1st byte), the value (zero-indexed) is multiplied by 32.
786 /// - 8 trig counts (7 repeats) --> 7 * 3 = 224
787 /// - 4 trig counts (3 repeats) -- 3 * 32 = 96
788 /// - 1 trig counts (0 repeats) -- 0 * 32 = 0
789 ///
790 /// For conditionals, see the `TrigCondition` enum and associated traits for more details.
791 /// The maximum value for a Trig Condition byte is 64.
792 ///
793 /// ```rust
794 /// // no trig micro-timings at all
795 /// [
796 /// // trig 1
797 /// [
798 /// 0, // trig counts (number)
799 /// 0, // trig condition (enum rep)
800 /// ],
801 /// // trig 2
802 /// [
803 /// 224, // trig counts (max value)
804 /// 64, // trig condition (max value)
805 /// ],
806 /// // trig 3
807 /// [
808 /// 32, // trig counts (minimum non-zero value)
809 /// 1, // trig condition (minimum non-zero value)
810 /// ],
811 /// // ... and so on
812 /// ];
813 /// ```
814 ///
815 /// Trig Offsets
816 /// ====
817 ///
818 /// Trig Offset values use both of these interleaved bytes on top of the
819 /// trig repeat and trig condition values... Which makes life more complex
820 /// and somewhat frustrating.
821 ///
822 /// Inspected values
823 /// - -23/384 -> 1st byte 20, 2nd byte 128
824 /// - -1/32 -> 1st byte 26, 2nd byte 0
825 /// - -1/64 -> 1st byte 29, 2nd byte 0
826 /// - -1/128 -> 1st byte 30, 2nd byte 128
827 /// - 1/128 -> 1st byte 1, 2nd byte 128
828 /// - 1/64 -> 1st byte 3, 2nd byte 0
829 /// - 1/32 -> 1st byte 6, 2nd byte 0
830 /// - 23/384 -> 1st byte 11, 2nd byte 128
831 ///
832 /// #### 1st byte
833 /// The 1st byte only has 31 possible values: 255 - 224 (trig count max) = 31.
834 /// So it makes sense sort of that this is a mask? I guess?
835 ///
836 /// #### 2nd byte
837 /// From what I can tell, the second offset byte is either 0 or 128.
838 /// So a 2nd byte for an offset adjusted trig with a `8:8` trig condition is either
839 /// - 128 + 64 = 192
840 /// - 0 + 64 = 64
841 ///
842 /// So you will need to a `x.rem_euclid(128)` somewhere if you want to parse this.
843 ///
844 /// Combining the trig offset with trig count and trig conditions, we end up with
845 /// ```rust
846 /// [
847 /// // trig one, -23/384 offset with 1x trig count and None condition
848 /// [
849 /// 20, // 20 + (32 * 0)
850 /// 128, // 128 + 0
851 /// ],
852 /// // trig two, -23/384 offset with 2x trig count and Fill condition
853 /// [
854 /// 52, // 20 + (32 * 1)
855 /// 129, // 128 + 1
856 /// ],
857 /// // trig three, -23/384 offset with 3x trig count and Fill condition
858 /// [
859 /// 84, // 20 + (32 * 2)
860 /// 129, // 128 + 1
861 /// ],
862 /// // trig four, -23/384 offset with 3x trig count and NotFill condition
863 /// [
864 /// 84, // 20 + (32 * 2)
865 /// 130, // 128 + 2
866 /// ],
867 /// // trig five, +1/32 offset with 2x trig count and Fill condition
868 /// [
869 /// 38, // 6 + (32 * 1)
870 /// 1, // 0 + 1
871 /// ],
872 /// // trig six, +1/32 offset with 3x trig count and Fill condition
873 /// [
874 /// 70, // 6 + (32 * 2)
875 /// 1, // 0 + 1
876 /// ],
877 /// // trig seven, +1/32 offset with 3x trig count and NotFill condition
878 /// [
879 /// 70, // 6 + (32 * 2)
880 /// 2, // 0 + 2
881 /// ],
882 /// // .... and so on
883 /// ];
884 /// ```
885 ///
886 /// #### Extending pages and offsets
887 ///
888 /// If you have a trig offset on Trig 1 with only one pattern page activated,
889 /// the trig offsets for Trig 1 are replicated over the relevant trig
890 /// positions for each first trig in the inactive pages in this array.
891 ///
892 /// So, for a 1/32 offset on trig 1 with only one page active, you get the
893 /// following values showing up in this array:
894 /// - pair of bytes at array index 15 -> 1/32
895 /// - pair of bytes at array index 31 -> 1/32
896 /// - pair of bytes at array index 47 -> 1/32
897 ///
898 /// This does not happen for offset values at any other trig position
899 /// (from what I can tell in my limited testing -- trig values 2-4 and 9-11
900 /// inclusive are not replicated in the same way).
901 ///
902 /// This 'replicating trig offset values over unused pages' behaviour does
903 /// not happen for trig counts. I haven't tested whether this applies to trig
904 /// conditions yet.
905 ///
906 /// It seems that this behaviour could be to make sure the octatack plays
907 /// correctly offset trigs when you extend a page live, i.e. when extending
908 /// a one-page pattern to a two-page pattern, if there is a negative offset
909 /// value there the octatrack will need to play the offset trig before the
910 /// first page has completed.
911 ///
912 /// Or it could be a bug :shrug:
913 #[serde(with = "BigArray")]
914 pub trig_offsets_repeats_conditions: [[u8; 2]; 64],
915}
916
917impl AudioTrackTrigs {
918 /// WARNING: This `default` method is not from the `Default` trait, as we
919 /// cannot use a default struct instance to create an array/vector of
920 /// midi track trig data --> the individual default depends on their
921 /// position in the final array!
922 ///
923 /// In the future, maybe it might be worth creating `default_with` and
924 /// `defaults_with` methods to deal with this. But it's not clear they are
925 /// needed just yet. 80/20.
926 fn default(id: u8) -> Self {
927 assert!(id < 8);
928 Self {
929 header: AUDIO_TRACK_HEADER,
930 unknown_1: from_fn(|_| 0),
931 track_id: id,
932 trig_masks: AudioTrackTrigMasks::default(),
933 scale_per_track_mode: TrackPerTrackModeScale::default(),
934 swing_amount: 0,
935 pattern_settings: TrackPatternSettings::default(),
936 unknown_2: 0,
937 plocks: AudioTrackParameterLocks::defaults(),
938 unknown_3: from_fn(|_| 0),
939 trig_offsets_repeats_conditions: from_fn(|_| [0, 0]),
940 }
941 }
942
943 /// WARNING: This `defaults` method is not from the `Defaults` trait, as we
944 /// cannot use a default struct instance to create an array/vector of
945 /// machine slots data --> the individual default depends on their position
946 /// in the final array!
947 ///
948 /// In the future, maybe it might be worth creating `default_with` and
949 /// `defaults_with` methods to deal with this. But it's not clear they are
950 /// needed just yet. 80/20.
951 pub fn defaults<const N: usize>() -> [Self; N] {
952 from_fn(|x| Self::default(x as u8))
953 }
954}
955
956impl CheckHeader for AudioTrackTrigs {
957 fn check_header(&self) -> bool {
958 self.header == AUDIO_TRACK_HEADER
959 }
960}
961
962/// MIDI Track Trig masks.
963/// Can be converted into an array of booleans using the `get_track_trigs_from_bitmasks` function.
964/// See `AudioTrackTrigMasks` for more information.
965///
966/// Trig mask arrays have data stored in this order, which is slightly confusing (pay attention to the difference with 7 + 8!):
967/// 1. 1st half of the 4th page
968/// 2. 2nd half of the 4th page
969/// 3. 1st half of the 3rd page
970/// 4. 2nd half of the 3rd page
971/// 5. 1st half of the 2nd page
972/// 6. 2nd half of the 2nd page
973/// 7. 2nd half of the 1st page
974/// 8. 1st half of the 1st page
975#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
976pub struct MidiTrackTrigMasks {
977 /// Note Trig masks.
978 #[serde(with = "BigArray")]
979 pub trigger: [u8; 8],
980
981 /// Trigless Trig masks.
982 #[serde(with = "BigArray")]
983 pub trigless: [u8; 8],
984
985 /// Parameter Lock Trig masks.
986 /// Note this only stores data for exclusive parameter lock *trigs* (light green trigs).
987 #[serde(with = "BigArray")]
988 pub plock: [u8; 8],
989
990 /// Swing trigs mask.
991 #[serde(with = "BigArray")]
992 pub swing: [u8; 8],
993
994 /// this is a block of 8, so looks like a trig mask for tracks,
995 /// but I can't think of what it could be.
996 #[serde(with = "BigArray")]
997 pub unknown: [u8; 8],
998}
999
1000impl Default for MidiTrackTrigMasks {
1001 fn default() -> Self {
1002 Self {
1003 trigger: from_fn(|_| 0),
1004 trigless: from_fn(|_| 0),
1005 plock: from_fn(|_| 0),
1006 swing: from_fn(|_| 170),
1007 unknown: from_fn(|_| 0),
1008 }
1009 }
1010}
1011
1012/// Track trigs assigned on an Audio Track within a Pattern
1013#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1014pub struct MidiTrackTrigs {
1015 /// Header data section
1016 ///
1017 /// example data:
1018 /// ```text
1019 /// MTRA
1020 /// 4d 54 52 41
1021 /// ```
1022 #[serde(with = "BigArray")]
1023 pub header: [u8; 4],
1024
1025 /// Unknown data.
1026 #[serde(with = "BigArray")]
1027 pub unknown_1: [u8; 4],
1028
1029 /// The zero indexed track number
1030 pub track_id: u8,
1031
1032 /// MIDI Track Trig masks contain the Trig step locations for different trig types
1033 pub trig_masks: MidiTrackTrigMasks,
1034
1035 /// The scale of this MIDI Track in Per Track Pattern mode.
1036 pub scale_per_track_mode: TrackPerTrackModeScale,
1037
1038 /// Amount of swing when a Swing Trig is active for the Track.
1039 /// Maximum is `30` (`80` on device), minimum is `0` (`50` on device).
1040 pub swing_amount: u8,
1041
1042 /// Pattern settings for this MIDI Track
1043 pub pattern_settings: TrackPatternSettings,
1044
1045 /// trig properties -- p-locks etc.
1046 /// the big `0xff` value block within tracks basically.
1047 /// 32 bytes per trig -- 6x parameters for 5x pages plus 2x extra fields at the end.
1048 ///
1049 /// For audio tracks, the 2x extra fields at the end are for sample locks,
1050 /// but there's no such concept for MIDI tracks.
1051 /// It seems like Elektron devs reused their data structures for P-Locks on both Audio + MIDI tracks.
1052 // note -- stack overflow if tring to use #[serde(with = "BigArray")]
1053 pub plocks: Box<Array<MidiTrackParameterLocks, 64>>,
1054
1055 /// See the documentation for `AudioTrackTrigs` on how this field works.
1056 #[serde(with = "BigArray")]
1057 pub trig_offsets_repeats_conditions: [[u8; 2]; 64],
1058}
1059
1060impl MidiTrackTrigs {
1061 /// WARNING: This `default` method is not from the `Default` trait, as we
1062 /// cannot use a default struct instance to create an array/vector of
1063 /// midi track trig data --> the individual default depends on their
1064 /// position in the final array!
1065 ///
1066 /// In the future, maybe it might be worth creating `default_with` and
1067 /// `defaults_with` methods to deal with this. But it's not clear they are
1068 /// needed just yet. 80/20.
1069 fn default(id: u8) -> Self {
1070 // TODO: create an ot-tools error
1071 assert!(id < 8);
1072 Self {
1073 header: MIDI_TRACK_HEADER,
1074 unknown_1: from_fn(|_| 0),
1075 track_id: id,
1076 trig_masks: MidiTrackTrigMasks::default(),
1077 scale_per_track_mode: TrackPerTrackModeScale::default(),
1078 swing_amount: 0,
1079 pattern_settings: TrackPatternSettings::default(),
1080 plocks: MidiTrackParameterLocks::defaults(),
1081 trig_offsets_repeats_conditions: from_fn(|_| [0, 0]),
1082 }
1083 }
1084
1085 /// WARNING: This `defaults` method is not from the `Defaults` trait, as we
1086 /// cannot use a default struct instance to create an array/vector of
1087 /// machine slots data --> the individual default depends on their position
1088 /// in the final array!
1089 ///
1090 /// In the future, maybe it might be worth creating `default_with` and
1091 /// `defaults_with` methods to deal with this. But it's not clear they are
1092 /// needed just yet. 80/20.
1093 pub fn defaults<const N: usize>() -> [Self; N] {
1094 from_fn(|x| Self::default(x as u8))
1095 }
1096}
1097
1098impl CheckHeader for MidiTrackTrigs {
1099 fn check_header(&self) -> bool {
1100 self.header == MIDI_TRACK_HEADER
1101 }
1102}
1103
1104/// Pattern level scaling settings.
1105/// Some of these settings still apply when the pattern is in Per-Track scaling mode.
1106#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1107pub struct PatternScaleSettings {
1108 /// Multiply this value by `master_len_per_track` to get
1109 /// the real Master Length in Per Track Pattern mode.
1110 ///
1111 /// This field must be set to `255` when Master Length in
1112 /// Per Track Pattern mode is set to `INF`.
1113 ///
1114 /// ```text
1115 /// 0: From 2 steps to 255 steps.
1116 /// 1: From 256 steps to 511 steps.
1117 /// 2: From 512 steps to 767 steps.
1118 /// 3: From 768 steps to 1023 steps.
1119 /// 4: 1024 steps only.
1120 /// 255: `INF`.
1121 /// ```
1122 pub master_len_per_track_multiplier: u8,
1123
1124 /// Master Length in Per Track Pattern mode.
1125 /// Must multiply this by multiplier like this `(x + 1) * (mult + 1)` to get the real number.
1126 ///
1127 /// This field must be set to `255` when Master Length in
1128 /// Per Track Pattern mode is set to `INF`.
1129 ///
1130 /// Minimum value is 2 when the multiplier equals 0.
1131 pub master_len_per_track: u8,
1132
1133 /// The Audio Track's Scale when Pattern is in Per Track mode.
1134 ///
1135 /// Options
1136 /// ```text
1137 /// 0 -> 2x
1138 /// 1 -> 3/2x
1139 /// 2 -> 1x (Default)
1140 /// 3 -> 3/4x
1141 /// 4 -> 1/2x
1142 /// 5 -> 1/4x
1143 /// 6 -> 1/8x
1144 /// ```
1145 pub master_scale_per_track: u8,
1146
1147 /// Master Pattern Length.
1148 /// Determines Pattern Length for all Tracks when NOT in Per Track mode.
1149 pub master_len: u8,
1150
1151 /// Master Pattern playback multiplier.
1152 ///
1153 /// Options
1154 /// ```text
1155 /// 0 -> 2x
1156 /// 1 -> 3/2x
1157 /// 2 -> 1x (Default)
1158 /// 3 -> 3/4x
1159 /// 4 -> 1/2x
1160 /// 5 -> 1/4x
1161 /// 6 -> 1/8x
1162 /// ```
1163 pub master_scale: u8,
1164
1165 /// Scale mode for the Pattern.
1166 ///
1167 /// Options
1168 /// ```text
1169 /// NORMAL: 0 (Default)
1170 /// PER TRACK: 1
1171 /// ```
1172 pub scale_mode: u8,
1173}
1174
1175impl Default for PatternScaleSettings {
1176 fn default() -> Self {
1177 Self {
1178 master_len_per_track_multiplier: 0,
1179 master_len_per_track: 16,
1180 master_scale_per_track: 2,
1181 master_len: 16,
1182 master_scale: 2,
1183 scale_mode: 0,
1184 }
1185 }
1186}
1187
1188/// Chaining behaviour for the pattern.
1189#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1190pub struct PatternChainBehavior {
1191 /// When `use_project_setting` field is set to `1`/`true`
1192 /// this field should be set to `N/A` with a value of `255`.
1193 pub use_pattern_setting: u8,
1194
1195 /// Pattern Chain Behaviour -- Use the Project level setting for chain
1196 /// behaviour and disable any Pattern level chaining behaviour.
1197 /// Numeric Boolean.
1198 /// When this is `1` the `use_pattern_setting` should be set to `255`.
1199 pub use_project_setting: u8,
1200}
1201
1202// allow the verbose implementation to keep things
1203// - (a) standardised across all types
1204// - (b) easier for non-rustaceans to follow when reading through data structures
1205#[allow(clippy::derivable_impls)]
1206impl Default for PatternChainBehavior {
1207 fn default() -> Self {
1208 Self {
1209 use_pattern_setting: 0,
1210 use_project_setting: 0,
1211 }
1212 }
1213}
1214
1215/// A pattern of trigs stored in the bank.
1216#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, DefaultsAsBoxedBigArray)]
1217pub struct Pattern {
1218 /// Header indicating start of pattern section
1219 ///
1220 /// example data:
1221 /// ```text
1222 /// PTRN....
1223 /// 50 54 52 4e 00 00 00 00
1224 /// ```
1225 #[serde(with = "BigArray")]
1226 pub header: [u8; 8],
1227
1228 /// Audio Track data
1229 #[serde(with = "BigArray")]
1230 pub audio_track_trigs: [AudioTrackTrigs; 8],
1231
1232 /// MIDI Track data
1233 #[serde(with = "BigArray")]
1234 pub midi_track_trigs: [MidiTrackTrigs; 8],
1235
1236 /// Pattern scaling controls and settings
1237 pub scale: PatternScaleSettings,
1238
1239 /// Pattern chaining behaviour and settings
1240 pub chain_behaviour: PatternChainBehavior,
1241
1242 /// Unknown data.
1243 pub unknown: u8,
1244
1245 /// The Part of a Bank assigned to a Pattern.
1246 /// Part 1 = 0; Part 2 = 1; Part 3 = 2; Part 4 = 3.
1247 /// Credit to [@sezare56 on elektronauts for catching this one](https://www.elektronauts.com/t/octalib-a-simple-octatrack-librarian/225192/27)
1248 pub part_assignment: u8,
1249
1250 /// Pattern setting for Tempo.
1251 ///
1252 /// The Tempo value is split across both `tempo_1` and `tempo_2`.
1253 /// Yet to figure out how they relate to each other.
1254 ///
1255 /// Value of 120 BPM is 11 for this field.
1256 /// Value of 30 BPM is 2 for this field.
1257 pub tempo_1: u8,
1258
1259 /// Pattern setting for Tempo.
1260 ///
1261 /// The Tempo value is split across both `tempo_1` and `tempo_2`.
1262 /// Tet to figure out how they relate to each other.
1263 ///
1264 /// Value of 120 BPM is `64` for this field.
1265 /// Value of 30 BPM is `208` for this field.
1266 pub tempo_2: u8,
1267}
1268
1269impl Default for Pattern {
1270 fn default() -> Self {
1271 Self {
1272 header: PATTERN_HEADER,
1273 audio_track_trigs: AudioTrackTrigs::defaults(),
1274 midi_track_trigs: MidiTrackTrigs::defaults(),
1275 scale: PatternScaleSettings::default(),
1276 chain_behaviour: PatternChainBehavior::default(),
1277 unknown: 0,
1278 part_assignment: 0,
1279 // **I believe** these two mask values make the tempo 120.0 BPM
1280 // don't quote me on that though
1281 tempo_1: 11,
1282 tempo_2: 64,
1283 }
1284 }
1285}
1286
1287impl CheckHeader for Pattern {
1288 fn check_header(&self) -> bool {
1289 self.header == PATTERN_HEADER
1290 }
1291}
1292
1293#[cfg(test)]
1294#[allow(unused_imports)]
1295mod test {
1296
1297 mod track_trig_defaults {
1298
1299 mod audio {
1300 use crate::banks::patterns::AudioTrackTrigs;
1301 use crate::RBoxErr;
1302
1303 fn wrap_err(track_id: u8) -> RBoxErr<AudioTrackTrigs> {
1304 Ok(AudioTrackTrigs::default(track_id))
1305 }
1306
1307 #[test]
1308 fn ok_track_id_zero() {
1309 assert!(wrap_err(0).is_ok());
1310 }
1311
1312 #[test]
1313 fn ok_track_id_seven() {
1314 assert!(wrap_err(7).is_ok());
1315 }
1316
1317 // todo: proper error handling (don't use an assert!() in the default method)
1318 #[test]
1319 #[should_panic]
1320 fn err_default_track_id_eight() {
1321 assert!(wrap_err(8).is_err());
1322 }
1323 }
1324 mod midi {
1325 use crate::banks::patterns::MidiTrackTrigs;
1326 use crate::RBoxErr;
1327
1328 fn wrap_err(track_id: u8) -> RBoxErr<MidiTrackTrigs> {
1329 Ok(MidiTrackTrigs::default(track_id))
1330 }
1331
1332 #[test]
1333 fn ok_track_id_zero() {
1334 assert!(wrap_err(0).is_ok());
1335 }
1336
1337 #[test]
1338 fn ok_track_id_seven() {
1339 assert!(wrap_err(7).is_ok());
1340 }
1341
1342 // todo: proper error handling (don't use an assert!() in the default method)
1343 #[test]
1344 #[should_panic]
1345 fn err_default_track_id_eight() {
1346 assert!(wrap_err(8).is_err());
1347 }
1348 }
1349 }
1350 mod trig_bitmasks {
1351
1352 #[test]
1353 fn test_all_track_trigs_from_bitmasks() {
1354 let x: [u8; 8] = std::array::from_fn(|_| 255);
1355 let y: [bool; 64] = std::array::from_fn(|_| true);
1356
1357 assert_eq!(
1358 crate::banks::patterns::get_track_trigs_from_bitmasks(&x).unwrap(),
1359 y
1360 )
1361 }
1362
1363 #[test]
1364 fn test_no_track_trigs_from_bitmasks() {
1365 let x: [u8; 8] = std::array::from_fn(|_| 0);
1366 let y: [bool; 64] = std::array::from_fn(|_| false);
1367
1368 assert_eq!(
1369 crate::banks::patterns::get_track_trigs_from_bitmasks(&x).unwrap(),
1370 y
1371 )
1372 }
1373
1374 #[test]
1375 fn test_halfpage_trig_bitmask_unmask_0() {
1376 assert_eq!(
1377 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&0).unwrap(),
1378 [false, false, false, false, false, false, false, false],
1379 );
1380 }
1381
1382 #[test]
1383 fn test_halfpage_trig_bitmask_unmask_1() {
1384 assert_eq!(
1385 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&1).unwrap(),
1386 [true, false, false, false, false, false, false, false],
1387 );
1388 }
1389 #[test]
1390 fn test_halfpage_trig_bitmask_unmask_2() {
1391 assert_eq!(
1392 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&2).unwrap(),
1393 [false, true, false, false, false, false, false, false],
1394 );
1395 }
1396
1397 #[test]
1398 fn test_halfpage_trig_bitmask_unmask_4() {
1399 assert_eq!(
1400 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&4).unwrap(),
1401 [false, false, true, false, false, false, false, false],
1402 );
1403 }
1404
1405 #[test]
1406 fn test_halfpage_trig_bitmask_unmask_8() {
1407 assert_eq!(
1408 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&8).unwrap(),
1409 [false, false, false, true, false, false, false, false],
1410 );
1411 }
1412
1413 #[test]
1414 fn test_halfpage_trig_bitmask_unmask_16() {
1415 assert_eq!(
1416 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&16).unwrap(),
1417 [false, false, false, false, true, false, false, false],
1418 );
1419 }
1420
1421 #[test]
1422 fn test_halfpage_trig_bitmask_unmask_32() {
1423 assert_eq!(
1424 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&32).unwrap(),
1425 [false, false, false, false, false, true, false, false],
1426 );
1427 }
1428
1429 #[test]
1430 fn test_halfpage_trig_bitmask_unmask_64() {
1431 assert_eq!(
1432 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&64).unwrap(),
1433 [false, false, false, false, false, false, true, false],
1434 );
1435 }
1436
1437 #[test]
1438 fn test_halfpage_trig_bitmask_unmask_128() {
1439 assert_eq!(
1440 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&128).unwrap(),
1441 [false, false, false, false, false, false, false, true],
1442 );
1443 }
1444
1445 #[test]
1446 fn test_halfpage_trig_bitmask_unmask_3() {
1447 assert_eq!(
1448 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&3).unwrap(),
1449 [true, true, false, false, false, false, false, false],
1450 );
1451 }
1452
1453 #[test]
1454 fn test_halfpage_trig_bitmask_unmask_7() {
1455 assert_eq!(
1456 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&7).unwrap(),
1457 [true, true, true, false, false, false, false, false],
1458 );
1459 }
1460
1461 #[test]
1462 fn test_halfpage_trig_bitmask_unmask_15() {
1463 assert_eq!(
1464 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&15).unwrap(),
1465 [true, true, true, true, false, false, false, false],
1466 );
1467 }
1468
1469 #[test]
1470 fn test_halfpage_trig_bitmask_unmask_31() {
1471 assert_eq!(
1472 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&31).unwrap(),
1473 [true, true, true, true, true, false, false, false],
1474 );
1475 }
1476
1477 #[test]
1478 fn test_halfpage_trig_bitmask_unmask_63() {
1479 assert_eq!(
1480 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&63).unwrap(),
1481 [true, true, true, true, true, true, false, false],
1482 );
1483 }
1484
1485 #[test]
1486 fn test_halfpage_trig_bitmask_unmask_127() {
1487 assert_eq!(
1488 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&127).unwrap(),
1489 [true, true, true, true, true, true, true, false],
1490 );
1491 }
1492
1493 #[test]
1494 fn test_halfpage_trig_bitmask_unmask_255() {
1495 assert_eq!(
1496 crate::banks::patterns::get_halfpage_trigs_from_bitmask_value(&255).unwrap(),
1497 [true, true, true, true, true, true, true, true],
1498 );
1499 }
1500 }
1501
1502 mod integrity {
1503 mod pattern {
1504 // valid header: [0x50, 0x54, 0x52, 0x4e, 0x00, 0x00, 0x00, 0x00];
1505 use crate::banks::patterns::Pattern;
1506 use crate::CheckHeader;
1507
1508 #[test]
1509 fn true_valid_header() {
1510 let pattern = Pattern::default();
1511 assert!(pattern.check_header());
1512 }
1513
1514 #[test]
1515 fn false_invalid_header() {
1516 let mut pattern = Pattern::default();
1517 pattern.header[0] = 0x01;
1518 pattern.header[1] = 0x01;
1519 pattern.header[7] = 0x50;
1520 assert!(!pattern.check_header());
1521 }
1522 }
1523 mod audio_track_trigs {
1524 use crate::banks::patterns::AudioTrackTrigs;
1525 use crate::CheckHeader;
1526
1527 #[test]
1528 fn true_valid_header() {
1529 let trigs = AudioTrackTrigs::default(0);
1530 assert!(trigs.check_header());
1531 }
1532
1533 #[test]
1534 fn false_invalid_header() {
1535 let mut trigs = AudioTrackTrigs::default(0);
1536 trigs.header[0] = 0x01;
1537 trigs.header[1] = 0x01;
1538 trigs.header[2] = 0x50;
1539 assert!(!trigs.check_header());
1540 }
1541 }
1542 mod midi_track_trigs {
1543 use crate::banks::patterns::MidiTrackTrigs;
1544 use crate::CheckHeader;
1545
1546 #[test]
1547 fn true_valid_header() {
1548 let trigs = MidiTrackTrigs::default(0);
1549 assert!(trigs.check_header());
1550 }
1551
1552 #[test]
1553 fn false_invalid_header() {
1554 let mut trigs = MidiTrackTrigs::default(0);
1555 trigs.header[0] = 0x01;
1556 trigs.header[1] = 0x01;
1557 trigs.header[2] = 0x50;
1558 assert!(!trigs.check_header());
1559 }
1560 }
1561 }
1562}