ot_tools_io/
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::{Defaults, HasHeaderField, OtToolsIoError};
8use ot_tools_io_derive::{ArrayDefaults, BoxedBigArrayDefaults};
9use std::array::from_fn;
10
11use crate::parts::{
12    AudioTrackAmpParamsValues, AudioTrackFxParamsValues, LfoParamsValues, MidiTrackArpParamsValues,
13    MidiTrackCc1ParamsValues, MidiTrackCc2ParamsValues, MidiTrackMidiParamsValues,
14};
15use serde::{Deserialize, Serialize};
16use serde_big_array::{Array, BigArray};
17
18const PATTERN_HEADER: [u8; 8] = [0x50, 0x54, 0x52, 0x4e, 0x00, 0x00, 0x00, 0x00];
19
20/// Header array for a MIDI track section in binary data files: `MTRA`
21const MIDI_TRACK_HEADER: [u8; 4] = [0x4d, 0x54, 0x52, 0x41];
22
23/// Header array for a MIDI track section in binary data files: `TRAC`
24const AUDIO_TRACK_HEADER: [u8; 4] = [0x54, 0x52, 0x41, 0x43];
25
26/// A Trig's parameter locks on the Playback/Machine page for an Audio Track.
27#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
28pub struct AudioTrackParameterLockPlayback {
29    pub param1: u8,
30    pub param2: u8,
31    pub param3: u8,
32    pub param4: u8,
33    pub param5: u8,
34    pub param6: u8,
35}
36
37impl Default for AudioTrackParameterLocks {
38    fn default() -> Self {
39        // 255 -> disabled
40
41        // NOTE: the `part.rs` `default` methods for each of these type has
42        // fields all set to the correct defaults for the TRACK view, not p-lock
43        // trigS. So don't try and use the type's `default` method here as you
44        // will end up with a bunch of p-locks on trigs for all the default
45        // values. (Although maybe that's a desired feature for some workflows).
46
47        // Yes, this comment is duplicated below. It is to make sur you've seen
48        // it.
49        Self {
50            machine: AudioTrackParameterLockPlayback {
51                param1: 255,
52                param2: 255,
53                param3: 255,
54                param4: 255,
55                param5: 255,
56                param6: 255,
57            },
58            lfo: LfoParamsValues {
59                spd1: 255,
60                spd2: 255,
61                spd3: 255,
62                dep1: 255,
63                dep2: 255,
64                dep3: 255,
65            },
66            amp: AudioTrackAmpParamsValues {
67                atk: 255,
68                hold: 255,
69                rel: 255,
70                vol: 255,
71                bal: 255,
72                unused: 255,
73            },
74            fx1: AudioTrackFxParamsValues {
75                param_1: 255,
76                param_2: 255,
77                param_3: 255,
78                param_4: 255,
79                param_5: 255,
80                param_6: 255,
81            },
82            fx2: AudioTrackFxParamsValues {
83                param_1: 255,
84                param_2: 255,
85                param_3: 255,
86                param_4: 255,
87                param_5: 255,
88                param_6: 255,
89            },
90            static_slot_id: 255,
91            flex_slot_id: 255,
92        }
93    }
94}
95
96/// A single trig's parameter locks on an Audio Track.
97#[derive(
98    Debug, Serialize, Deserialize, Clone, PartialEq, Copy, ArrayDefaults, BoxedBigArrayDefaults,
99)]
100pub struct AudioTrackParameterLocks {
101    pub machine: AudioTrackParameterLockPlayback,
102    pub lfo: LfoParamsValues,
103    pub amp: AudioTrackAmpParamsValues,
104    pub fx1: AudioTrackFxParamsValues,
105    pub fx2: AudioTrackFxParamsValues,
106    /// P-Lock to change an audio track's static machine sample slot assignment per trig
107    pub static_slot_id: u8,
108    /// P-Lock to change an audio track's flex machine sample slot assignment per trig
109    pub flex_slot_id: u8,
110}
111
112/// MIDI Track parameter locks.
113#[derive(
114    Debug, Serialize, Deserialize, Clone, PartialEq, Copy, ArrayDefaults, BoxedBigArrayDefaults,
115)]
116pub struct MidiTrackParameterLocks {
117    pub midi: MidiTrackMidiParamsValues,
118    pub lfo: LfoParamsValues,
119    pub arp: MidiTrackArpParamsValues,
120    pub ctrl1: MidiTrackCc1ParamsValues,
121    pub ctrl2: MidiTrackCc2ParamsValues,
122
123    #[serde(with = "BigArray")]
124    unknown: [u8; 2],
125}
126
127impl Default for MidiTrackParameterLocks {
128    fn default() -> Self {
129        // 255 -> disabled
130
131        // NOTE: the `part.rs` `default` methods for each of these type has
132        // fields all set to the correct defaults for the TRACK view, not p-lock
133        // trigS. So don't try and use the type's `default` method here as you
134        // will end up with a bunch of p-locks on trigs for all the default
135        // values. (Although maybe that's a desired feature for some workflows).
136
137        // Yes, this comment is duplicated above. It is to make sur you've seen
138        // it.
139
140        Self {
141            midi: MidiTrackMidiParamsValues {
142                note: 255,
143                vel: 255,
144                len: 255,
145                not2: 255,
146                not3: 255,
147                not4: 255,
148            },
149            lfo: LfoParamsValues {
150                spd1: 255,
151                spd2: 255,
152                spd3: 255,
153                dep1: 255,
154                dep2: 255,
155                dep3: 255,
156            },
157            arp: MidiTrackArpParamsValues {
158                tran: 255,
159                leg: 255,
160                mode: 255,
161                spd: 255,
162                rnge: 255,
163                nlen: 255,
164            },
165            ctrl1: MidiTrackCc1ParamsValues {
166                pb: 255,
167                at: 255,
168                cc1: 255,
169                cc2: 255,
170                cc3: 255,
171                cc4: 255,
172            },
173            ctrl2: MidiTrackCc2ParamsValues {
174                cc5: 255,
175                cc6: 255,
176                cc7: 255,
177                cc8: 255,
178                cc9: 255,
179                cc10: 255,
180            },
181            unknown: [255, 255],
182        }
183    }
184}
185
186/// Audio & MIDI Track Pattern playback settings.
187#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
188pub struct TrackPatternSettings {
189    /// Silence any existing audio playback on the Audio Track when switching Patterns.
190    pub start_silent: u8,
191
192    /// Trigger Audio Track playback without any quantization or syncing to other Audio Tracks.
193    pub plays_free: u8,
194
195    /// Quantization when this Audio Track is Triggered for Playback.
196    ///
197    /// Options
198    /// ```text
199    /// N/A and ONE: 0 (Default)
200    /// ONE2: 1
201    /// HOLD: 2
202    /// ```
203    pub trig_mode: u8,
204
205    /// Track Trigger Quantization.
206    ///
207    /// Options
208    /// ```text
209    /// N/A and TR.LEN: 0 (Default)
210    /// 1/16: 1
211    /// 2/16: 2
212    /// 3/16: 3
213    /// 4/16: 4
214    /// 6/16: 5
215    /// 8/16: 6
216    /// 12/16: 7
217    /// 16/16: 8
218    /// 24/16: 9
219    /// 32/16: 10
220    /// 48/16: 11
221    /// 64/16: 12
222    /// 96/16: 13
223    /// 128/16: 14
224    /// 192/16: 15
225    /// 256/16: 16
226    /// DIRECT: 255
227    /// ```
228    pub trig_quant: u8,
229
230    /// Whether to play the track as a `ONESHOT` track.
231    pub oneshot_trk: u8,
232}
233
234impl Default for TrackPatternSettings {
235    fn default() -> Self {
236        Self {
237            start_silent: 255,
238            plays_free: 0,
239            trig_mode: 0,
240            trig_quant: 0,
241            oneshot_trk: 0,
242        }
243    }
244}
245
246/// Trig bitmasks array for Audio Tracks.
247/// Can be converted into an array of booleans using the `get_track_trigs_from_bitmasks` function.
248///
249/// Trig bitmask arrays have bitmasks stored in this order, which is slightly confusing (pay attention to the difference with 7 + 8!):
250/// 1. 1st half of the 4th page
251/// 2. 2nd half of the 4th page
252/// 3. 1st half of the 3rd page
253/// 4. 2nd half of the 3rd page
254/// 5. 1st half of the 2nd page
255/// 6. 2nd half of the 2nd page
256/// 7. 2nd half of the 1st page
257/// 8. 1st half of the 1st page
258///
259/// ### Bitmask values for trig positions
260/// With single trigs in a half-page
261/// ```text
262/// positions
263/// 1 2 3 4 5 6 7 8 | mask value
264/// ----------------|-----------
265/// - - - - - - - - | 0
266/// x - - - - - - - | 1
267/// - x - - - - - - | 2
268/// - - x - - - - - | 4
269/// - - - x - - - - | 8
270/// - - - - x - - - | 16
271/// - - - - - x - - | 32
272/// - - - - - - x - | 64
273/// - - - - - - - x | 128
274/// ```
275///
276/// When there are multiple trigs in a half-page, the individual position values are summed together:
277///
278/// ```text
279/// 1 2 3 4 5 6 7 8 | mask value
280/// ----------------|-----------
281/// x x - - - - - - | 1 + 2 = 3
282/// x x x x - - - - | 1 + 2 + 4 + 8 = 15
283/// ```
284/// ### Fuller diagram of mask values
285///
286/// ```text
287/// positions
288/// 1 2 3 4 5 6 7 8 | mask value
289/// ----------------|-----------
290/// x - - - - - - - | 1
291/// - x - - - - - - | 2
292/// x x - - - - - - | 3
293/// - - x - - - - - | 4
294/// x - x - - - - - | 5
295/// - x x - - - - - | 6
296/// x x x - - - - - | 7
297/// - - - x - - - - | 8
298/// x - - x - - - - | 9
299/// - x - x - - - - | 10
300/// x x - x - - - - | 11
301/// - - x x - - - - | 12
302/// x - x x - - - - | 13
303/// - x x x - - - - | 14
304/// x x x x - - - - | 15
305/// ................|....
306/// x x x x x x - - | 63
307/// ................|....
308/// - - - - - - - x | 128
309/// ................|....
310/// - x - x - x - x | 170
311/// ................|....
312/// - - - - x x x x | 240
313/// ................|....
314/// x x x x x x x x | 255
315/// ```
316///
317#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
318pub struct AudioTrackTrigMasks {
319    /// Trigger Trig masks -- indicate which Trigger Trigs are active.
320    /// Base track Trig masks are stored backwards, meaning
321    /// the first 8 Trig positions are the last bytes in this section.
322    #[serde(with = "BigArray")]
323    pub trigger: [u8; 8],
324
325    /// Envelope Trig masks -- indicate which Envelope Trigs are active.
326    /// See the description of the `trig_trig_masks` field for an
327    /// explanation of how the masking works.
328    #[serde(with = "BigArray")]
329    pub trigless: [u8; 8],
330
331    /// Parameter-Lock Trig masks -- indicate which Parameter-Lock Trigs are active.
332    /// See the description of the `trig_trig_masks` field for an
333    /// explanation of how the masking works.    
334    #[serde(with = "BigArray")]
335    pub plock: [u8; 8],
336
337    /// Hold Trig masks -- indicate which Hold Trigs are active.
338    /// See the description of the `trig_trig_masks` field for an
339    /// explanation of how the masking works.
340    #[serde(with = "BigArray")]
341    pub oneshot: [u8; 8],
342
343    /// Recorder Trig masks -- indicate which Recorder Trigs are active.
344    /// These seem to function differently to the main Track Trig masks.
345    /// Filling up Recorder Trigs on a Pattern results in a 32 length array
346    /// instead of 8 length.
347    /// Possible that the Trig type is stored in this array as well.
348    #[serde(with = "BigArray")]
349    pub recorder: [u8; 32],
350
351    /// Swing trigs Trig masks.
352    #[serde(with = "BigArray")]
353    pub swing: [u8; 8],
354
355    /// Parameter Slide trigs Trig masks.
356    #[serde(with = "BigArray")]
357    pub slide: [u8; 8],
358}
359
360impl Default for AudioTrackTrigMasks {
361    fn default() -> Self {
362        Self {
363            trigger: from_fn(|_| 0),
364            trigless: from_fn(|_| 0),
365            plock: from_fn(|_| 0),
366            oneshot: from_fn(|_| 0),
367            recorder: from_fn(|_| 0),
368            swing: from_fn(|_| 170),
369            slide: from_fn(|_| 0),
370        }
371    }
372}
373
374/// Audio Track custom scaling when the Pattern is in PER TRACK scale mode.
375#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
376pub struct TrackPerTrackModeScale {
377    /// The Audio Track's Length when Pattern is in Per Track mode.
378    /// Default: 16
379    pub per_track_len: u8,
380
381    /// The Audio Track's Scale when Pattern is in Per Track mode.
382    ///
383    /// Options
384    /// ```text
385    /// 0 -> 2x
386    /// 1 -> 3/2x
387    /// 2 -> 1x (Default)
388    /// 3 -> 3/4x
389    /// 4 -> 1/2x
390    /// 5 -> 1/4x
391    /// 6 -> 1/8x
392    /// ```
393    pub per_track_scale: u8,
394}
395
396impl Default for TrackPerTrackModeScale {
397    fn default() -> Self {
398        Self {
399            per_track_len: 16,
400            per_track_scale: 2,
401        }
402    }
403}
404
405/// Track trigs assigned on an Audio Track within a Pattern
406#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, BoxedBigArrayDefaults)]
407pub struct AudioTrackTrigs {
408    /// Header data section
409    ///
410    /// example data:
411    /// ```text
412    /// TRAC
413    /// 54 52 41 43
414    /// ```
415    #[serde(with = "BigArray")]
416    pub header: [u8; 4],
417
418    /// Unknown data.
419    #[serde(with = "BigArray")]
420    pub unknown_1: [u8; 4],
421
422    /// The zero indexed track number
423    pub track_id: u8,
424
425    /// Trig masks contain the Trig step locations for different trig types
426    pub trig_masks: AudioTrackTrigMasks,
427
428    /// The scale of this Audio Track in Per Track Pattern mode.
429    pub scale_per_track_mode: TrackPerTrackModeScale,
430
431    /// Amount of swing when a Swing Trig is active for the Track.
432    /// Maximum is `30` (`80` on device), minimum is `0` (`50` on device).
433    pub swing_amount: u8,
434
435    /// Pattern settings for this Audio Track
436    pub pattern_settings: TrackPatternSettings,
437
438    /// Unknown data.
439    pub unknown_2: u8,
440
441    /// Parameter-Lock data for all Trigs.
442    // note -- stack overflow if tring to use #[serde(with = "BigArray")]
443    pub plocks: Box<Array<AudioTrackParameterLocks, 64>>,
444
445    /// What the hell is this field?!?!
446    /// It **has to** be something to do with trigs, but i have no idea what it could be.
447    #[serde(with = "BigArray")]
448    pub unknown_3: [u8; 64],
449
450    /// Trig Offsets, Trig Counts and Trig Conditions
451    /// ====
452    /// This is ..... slightly frustrating.
453    ///
454    /// This 64 length array consisting of a pair of bytes for each array element hold three
455    /// data references... Trig Cunts and Trig Conditions use the two bytes independently,
456    /// so they're easier to explain first
457    ///
458    /// Trig Counts and Trig Conditions
459    /// ====
460    ///
461    /// Trig Counts and Trig Conditions data is interleaved for each trig.
462    /// For Trig position 1, array index 0 is the count value and array index 1 is the Trig
463    /// Condition.
464    ///
465    /// For trig counts (1st byte), the value (zero-indexed) is multiplied by 32.
466    /// - 8 trig counts (7 repeats) --> 7 * 3 = 224
467    /// - 4 trig counts (3 repeats) -- 3 * 32 = 96
468    /// - 1 trig counts (0 repeats) -- 0 * 32 = 0
469    ///
470    /// For conditionals, see the `TrigCondition` enum and associated traits for more details.
471    /// The maximum value for a Trig Condition byte is 64.
472    ///
473    /// ```rust
474    /// // no trig micro-timings at all
475    /// [
476    ///     // trig 1
477    ///     [
478    ///         0,   // trig counts (number)
479    ///         0,   // trig condition (enum rep)
480    ///     ],
481    ///     // trig 2
482    ///     [
483    ///         224, // trig counts (max value)
484    ///         64,  // trig condition (max value)
485    ///     ],
486    ///     // trig 3
487    ///     [
488    ///         32,  // trig counts (minimum non-zero value)
489    ///         1,   // trig condition (minimum non-zero value)
490    ///     ],
491    ///     // ... and so on
492    /// ];
493    /// ```
494    ///
495    /// Trig Offsets
496    /// ====
497    ///
498    /// Trig Offset values use both of these interleaved bytes on top of the
499    /// trig repeat and trig condition values... Which makes life more complex
500    /// and somewhat frustrating.
501    ///
502    /// Inspected values
503    /// - -23/384 -> 1st byte 20, 2nd byte 128
504    /// - -1/32 -> 1st byte 26, 2nd byte 0
505    /// - -1/64 -> 1st byte 29, 2nd byte 0
506    /// - -1/128 -> 1st byte 30, 2nd byte 128
507    /// - 1/128 -> 1st byte 1, 2nd byte 128
508    /// - 1/64 -> 1st byte 3, 2nd byte 0
509    /// - 1/32 -> 1st byte 6, 2nd byte 0
510    /// - 23/384 -> 1st byte 11, 2nd byte 128
511    ///
512    /// #### 1st byte
513    /// The 1st byte only has 31 possible values: 255 - 224 (trig count max) = 31.
514    /// So it makes sense sort of that this is a mask? I guess?
515    ///
516    /// #### 2nd byte
517    /// From what I can tell, the second offset byte is either 0 or 128.
518    /// So a 2nd byte for an offset adjusted trig with a `8:8` trig condition is either
519    /// - 128 + 64 = 192
520    /// - 0 + 64 = 64
521    ///
522    /// So you will need to a `x.rem_euclid(128)` somewhere if you want to parse this.
523    ///
524    /// Combining the trig offset with trig count and trig conditions, we end up with
525    /// ```rust
526    /// [
527    ///     // trig one, -23/384 offset with 1x trig count and None condition
528    ///     [
529    ///         20,  // 20 + (32 * 0)
530    ///         128, // 128 + 0
531    ///     ],
532    ///     // trig two, -23/384 offset with 2x trig count and Fill condition
533    ///     [
534    ///         52,  // 20 + (32 * 1)
535    ///         129, // 128 + 1
536    ///     ],
537    ///     // trig three, -23/384 offset with 3x trig count and Fill condition
538    ///     [
539    ///         84,  // 20 + (32 * 2)
540    ///         129, // 128 + 1
541    ///     ],
542    ///     // trig four, -23/384 offset with 3x trig count and NotFill condition
543    ///     [
544    ///         84,  // 20 + (32 * 2)
545    ///         130, // 128 + 2
546    ///     ],
547    ///     // trig five, +1/32 offset with 2x trig count and Fill condition
548    ///     [
549    ///         38,  // 6 + (32 * 1)
550    ///         1,   // 0 + 1
551    ///     ],
552    ///     // trig six, +1/32 offset with 3x trig count and Fill condition
553    ///     [
554    ///         70,  // 6 + (32 * 2)
555    ///         1,   // 0 + 1
556    ///     ],
557    ///     // trig seven, +1/32 offset with 3x trig count and NotFill condition
558    ///     [
559    ///         70,  // 6 + (32 * 2)
560    ///         2,   // 0 + 2
561    ///     ],
562    ///     // .... and so on
563    /// ];
564    /// ```
565    ///
566    /// #### Extending pages and offsets
567    ///
568    /// If you have a trig offset on Trig 1 with only one pattern page activated,
569    /// the trig offsets for Trig 1 are replicated over the relevant trig
570    /// positions for each first trig in the inactive pages in this array.
571    ///
572    /// So, for a 1/32 offset on trig 1 with only one page active, you get the
573    /// following values showing up in this array:
574    /// - pair of bytes at array index 15 -> 1/32
575    /// - pair of bytes at array index 31 -> 1/32
576    /// - pair of bytes at array index 47 -> 1/32
577    ///
578    /// This does not happen for offset values at any other trig position
579    /// (from what I can tell in my limited testing -- trig values 2-4 and 9-11
580    /// inclusive are not replicated in the same way).
581    ///
582    /// This 'replicating trig offset values over unused pages' behaviour does
583    /// not happen for trig counts. I haven't tested whether this applies to trig
584    /// conditions yet.
585    ///
586    /// It seems that this behaviour could be to make sure the octatrack plays
587    /// correctly offset trigs when you extend a page live, i.e. when extending
588    /// a one-page pattern to a two-page pattern, if there is a negative offset
589    /// value there the octatrack will need to play the offset trig before the
590    /// first page has completed.
591    ///
592    /// Or it could be a bug :shrug:
593    #[serde(with = "BigArray")]
594    pub trig_offsets_repeats_conditions: [[u8; 2]; 64],
595}
596
597impl Default for AudioTrackTrigs {
598    fn default() -> Self {
599        Self {
600            header: AUDIO_TRACK_HEADER,
601            unknown_1: from_fn(|_| 0),
602            track_id: 0,
603            trig_masks: AudioTrackTrigMasks::default(),
604            scale_per_track_mode: TrackPerTrackModeScale::default(),
605            swing_amount: 0,
606            pattern_settings: TrackPatternSettings::default(),
607            unknown_2: 0,
608            plocks: AudioTrackParameterLocks::defaults(),
609            unknown_3: from_fn(|_| 0),
610            trig_offsets_repeats_conditions: from_fn(|_| [0, 0]),
611        }
612    }
613}
614
615// need to implement manually to handle track_id field
616impl<const N: usize> Defaults<[Self; N]> for AudioTrackTrigs {
617    fn defaults() -> [Self; N]
618    where
619        Self: Default,
620    {
621        from_fn(|i| Self {
622            track_id: i as u8,
623            ..Default::default()
624        })
625    }
626}
627
628impl HasHeaderField for AudioTrackTrigs {
629    fn check_header(&self) -> Result<bool, OtToolsIoError> {
630        Ok(self.header == AUDIO_TRACK_HEADER)
631    }
632}
633
634/// MIDI Track Trig masks.
635/// Can be converted into an array of booleans using the `get_track_trigs_from_bitmasks` function.
636/// See `AudioTrackTrigMasks` for more information.
637///
638/// Trig mask arrays have data stored in this order, which is slightly confusing (pay attention to the difference with 7 + 8!):
639/// 1. 1st half of the 4th page
640/// 2. 2nd half of the 4th page
641/// 3. 1st half of the 3rd page
642/// 4. 2nd half of the 3rd page
643/// 5. 1st half of the 2nd page
644/// 6. 2nd half of the 2nd page
645/// 7. 2nd half of the 1st page
646/// 8. 1st half of the 1st page
647#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
648pub struct MidiTrackTrigMasks {
649    /// Note Trig masks.
650    #[serde(with = "BigArray")]
651    pub trigger: [u8; 8],
652
653    /// Trigless Trig masks.
654    #[serde(with = "BigArray")]
655    pub trigless: [u8; 8],
656
657    /// Parameter Lock Trig masks.
658    /// Note this only stores data for exclusive parameter lock *trigs* (light green trigs).
659    #[serde(with = "BigArray")]
660    pub plock: [u8; 8],
661
662    /// Swing trigs mask.
663    #[serde(with = "BigArray")]
664    pub swing: [u8; 8],
665
666    /// this is a block of 8, so looks like a trig mask for tracks,
667    /// but I can't think of what it could be.
668    #[serde(with = "BigArray")]
669    pub unknown: [u8; 8],
670}
671
672impl Default for MidiTrackTrigMasks {
673    fn default() -> Self {
674        Self {
675            trigger: from_fn(|_| 0),
676            trigless: from_fn(|_| 0),
677            plock: from_fn(|_| 0),
678            swing: from_fn(|_| 170),
679            unknown: from_fn(|_| 0),
680        }
681    }
682}
683
684/// Track trigs assigned on an Audio Track within a Pattern
685#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, BoxedBigArrayDefaults)]
686pub struct MidiTrackTrigs {
687    /// Header data section
688    ///
689    /// example data:
690    /// ```text
691    /// MTRA
692    /// 4d 54 52 41
693    /// ```
694    #[serde(with = "BigArray")]
695    pub header: [u8; 4],
696
697    /// Unknown data.
698    #[serde(with = "BigArray")]
699    pub unknown_1: [u8; 4],
700
701    /// The zero indexed track number
702    pub track_id: u8,
703
704    /// MIDI Track Trig masks contain the Trig step locations for different trig types
705    pub trig_masks: MidiTrackTrigMasks,
706
707    /// The scale of this MIDI Track in Per Track Pattern mode.
708    pub scale_per_track_mode: TrackPerTrackModeScale,
709
710    /// Amount of swing when a Swing Trig is active for the Track.
711    /// Maximum is `30` (`80` on device), minimum is `0` (`50` on device).
712    pub swing_amount: u8,
713
714    /// Pattern settings for this MIDI Track
715    pub pattern_settings: TrackPatternSettings,
716
717    /// trig properties -- p-locks etc.
718    /// the big `0xff` value block within tracks basically.
719    /// 32 bytes per trig -- 6x parameters for 5x pages plus 2x extra fields at the end.
720    ///
721    /// For audio tracks, the 2x extra fields at the end are for sample locks,
722    /// but there's no such concept for MIDI tracks.
723    /// It seems like Elektron devs reused their data structures for P-Locks on both Audio + MIDI tracks.
724    // note -- stack overflow if trying to use #[serde(with = "BigArray")]
725    pub plocks: Box<Array<MidiTrackParameterLocks, 64>>,
726
727    /// See the documentation for `AudioTrackTrigs` on how this field works.
728    #[serde(with = "BigArray")]
729    pub trig_offsets_repeats_conditions: [[u8; 2]; 64],
730}
731
732impl Default for MidiTrackTrigs {
733    fn default() -> Self {
734        Self {
735            header: MIDI_TRACK_HEADER,
736            unknown_1: from_fn(|_| 0),
737            track_id: 0,
738            trig_masks: MidiTrackTrigMasks::default(),
739            scale_per_track_mode: TrackPerTrackModeScale::default(),
740            swing_amount: 0,
741            pattern_settings: TrackPatternSettings::default(),
742            plocks: MidiTrackParameterLocks::defaults(),
743            trig_offsets_repeats_conditions: from_fn(|_| [0, 0]),
744        }
745    }
746}
747
748// needs to be manually implemented
749impl<const N: usize> Defaults<[Self; N]> for MidiTrackTrigs {
750    fn defaults() -> [Self; N]
751    where
752        Self: Default,
753    {
754        from_fn(|i| Self {
755            track_id: i as u8,
756            ..Default::default()
757        })
758    }
759}
760
761impl HasHeaderField for MidiTrackTrigs {
762    fn check_header(&self) -> Result<bool, OtToolsIoError> {
763        Ok(self.header == MIDI_TRACK_HEADER)
764    }
765}
766
767/// Pattern level scaling settings.
768/// Some of these settings still apply when the pattern is in Per-Track scaling mode.
769#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
770pub struct PatternScaleSettings {
771    /// Multiply this value by `master_len_per_track` to get
772    /// the real Master Length in Per Track Pattern mode.
773    ///
774    /// This field must be set to `255` when Master Length in
775    /// Per Track Pattern mode is set to `INF`.
776    ///
777    /// ```text
778    /// 0: From 2 steps to 255 steps.
779    /// 1: From 256 steps to 511 steps.
780    /// 2: From 512 steps to 767 steps.
781    /// 3: From 768 steps to 1023 steps.
782    /// 4: 1024 steps only.
783    /// 255: `INF`.
784    /// ```
785    pub master_len_per_track_multiplier: u8,
786
787    /// Master Length in Per Track Pattern mode.
788    /// Must multiply this by multiplier like this `(x + 1) * (mult + 1)` to get the real number.
789    ///
790    /// This field must be set to `255` when Master Length in
791    /// Per Track Pattern mode is set to `INF`.
792    ///
793    /// Minimum value is 2 when the multiplier equals 0.
794    pub master_len_per_track: u8,
795
796    /// The Audio Track's Scale when Pattern is in Per Track mode.
797    ///
798    /// Options
799    /// ```text
800    /// 0 -> 2x
801    /// 1 -> 3/2x
802    /// 2 -> 1x (Default)
803    /// 3 -> 3/4x
804    /// 4 -> 1/2x
805    /// 5 -> 1/4x
806    /// 6 -> 1/8x
807    /// ```
808    pub master_scale_per_track: u8,
809
810    /// Master Pattern Length.
811    /// Determines Pattern Length for all Tracks when NOT in Per Track mode.
812    pub master_len: u8,
813
814    /// Master Pattern playback multiplier.
815    ///
816    /// Options
817    /// ```text
818    /// 0 -> 2x
819    /// 1 -> 3/2x
820    /// 2 -> 1x (Default)
821    /// 3 -> 3/4x
822    /// 4 -> 1/2x
823    /// 5 -> 1/4x
824    /// 6 -> 1/8x
825    /// ```
826    pub master_scale: u8,
827
828    /// Scale mode for the Pattern.
829    ///
830    /// Options
831    /// ```text
832    /// NORMAL: 0 (Default)
833    /// PER TRACK: 1
834    /// ```
835    pub scale_mode: u8,
836}
837
838impl Default for PatternScaleSettings {
839    fn default() -> Self {
840        Self {
841            master_len_per_track_multiplier: 0,
842            master_len_per_track: 16,
843            master_scale_per_track: 2,
844            master_len: 16,
845            master_scale: 2,
846            scale_mode: 0,
847        }
848    }
849}
850
851/// Chaining behaviour for the pattern.
852#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
853pub struct PatternChainBehavior {
854    /// When `use_project_setting` field is set to `1`/`true`
855    /// this field should be set to `N/A` with a value of `255`.
856    pub use_pattern_setting: u8,
857
858    /// Pattern Chain Behaviour -- Use the Project level setting for chain
859    /// behaviour and disable any Pattern level chaining behaviour.
860    /// Numeric Boolean.
861    /// When this is `1` the `use_pattern_setting` should be set to `255`.
862    pub use_project_setting: u8,
863}
864
865// allow the verbose implementation to keep things
866// - (a) standardized across all types
867// - (b) easier for non-rustaceans to follow when reading through data structures
868#[allow(clippy::derivable_impls)]
869impl Default for PatternChainBehavior {
870    fn default() -> Self {
871        Self {
872            use_pattern_setting: 0,
873            use_project_setting: 0,
874        }
875    }
876}
877
878/// A pattern of trigs stored in the bank.
879#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, ArrayDefaults, BoxedBigArrayDefaults)]
880pub struct Pattern {
881    /// Header indicating start of pattern section
882    ///
883    /// example data:
884    /// ```text
885    /// PTRN....
886    /// 50 54 52 4e 00 00 00 00
887    /// ```
888    #[serde(with = "BigArray")]
889    pub header: [u8; 8],
890
891    /// Audio Track data
892    #[serde(with = "BigArray")]
893    pub audio_track_trigs: [AudioTrackTrigs; 8],
894
895    /// MIDI Track data
896    #[serde(with = "BigArray")]
897    pub midi_track_trigs: [MidiTrackTrigs; 8],
898
899    /// Pattern scaling controls and settings
900    pub scale: PatternScaleSettings,
901
902    /// Pattern chaining behaviour and settings
903    pub chain_behaviour: PatternChainBehavior,
904
905    /// Unknown data.
906    pub unknown: u8,
907
908    /// The Part of a Bank assigned to a Pattern.
909    /// Part 1 = 0; Part 2 = 1; Part 3 = 2; Part 4 = 3.
910    /// Credit to [@sezare56 on elektronauts for catching this one](https://www.elektronauts.com/t/octalib-a-simple-octatrack-librarian/225192/27)
911    pub part_assignment: u8,
912
913    /// Pattern setting for Tempo.
914    ///
915    /// The Tempo value is split across both `tempo_1` and `tempo_2`.
916    /// Yet to figure out how they relate to each other.
917    ///
918    /// Value of 120 BPM is 11 for this field.
919    /// Value of 30 BPM is 2 for this field.
920    pub tempo_1: u8,
921
922    /// Pattern setting for Tempo.
923    ///
924    /// The Tempo value is split across both `tempo_1` and `tempo_2`.
925    /// Tet to figure out how they relate to each other.
926    ///
927    /// Value of 120 BPM is `64` for this field.
928    /// Value of 30 BPM is `208` for this field.
929    pub tempo_2: u8,
930}
931
932impl Default for Pattern {
933    fn default() -> Self {
934        Self {
935            header: PATTERN_HEADER,
936            audio_track_trigs: AudioTrackTrigs::defaults(),
937            midi_track_trigs: MidiTrackTrigs::defaults(),
938            scale: PatternScaleSettings::default(),
939            chain_behaviour: PatternChainBehavior::default(),
940            unknown: 0,
941            part_assignment: 0,
942            // **I believe** these two mask values make the tempo 120.0 BPM
943            // don't quote me on that though
944            tempo_1: 11,
945            tempo_2: 64,
946        }
947    }
948}
949
950impl HasHeaderField for Pattern {
951    fn check_header(&self) -> Result<bool, OtToolsIoError> {
952        Ok(self.header == PATTERN_HEADER)
953    }
954}
955
956#[cfg(test)]
957#[allow(unused_imports)]
958mod test {
959
960    mod track_trig_defaults {
961
962        mod audio {
963            use crate::patterns::AudioTrackTrigs;
964            use crate::Defaults;
965
966            fn defs() -> [AudioTrackTrigs; 8] {
967                AudioTrackTrigs::defaults()
968            }
969
970            #[test]
971            fn ok_track_ids() -> Result<(), ()> {
972                for i in 0..8 {
973                    println!("Track: {} Track ID: {i}", i + 1);
974                    assert_eq!(defs()[i].track_id, i as u8);
975                }
976                Ok(())
977            }
978        }
979        mod midi {
980            use crate::patterns::MidiTrackTrigs;
981            use crate::{Defaults, OtToolsIoError};
982
983            fn defs() -> [MidiTrackTrigs; 8] {
984                MidiTrackTrigs::defaults()
985            }
986
987            #[test]
988            fn ok_track_ids() -> Result<(), ()> {
989                for i in 0..8 {
990                    println!("Track: {} Track ID: {i}", i + 1);
991                    assert_eq!(defs()[i].track_id, i as u8);
992                }
993                Ok(())
994            }
995        }
996    }
997
998    mod integrity {
999        mod pattern {
1000            // valid header: [0x50, 0x54, 0x52, 0x4e, 0x00, 0x00, 0x00, 0x00];
1001            use crate::patterns::Pattern;
1002            use crate::{HasHeaderField, OtToolsIoError};
1003
1004            #[test]
1005            fn true_valid_header() -> Result<(), OtToolsIoError> {
1006                let pattern = Pattern::default();
1007                assert!(pattern.check_header()?);
1008                Ok(())
1009            }
1010
1011            #[test]
1012            fn false_invalid_header() -> Result<(), OtToolsIoError> {
1013                let mut pattern = Pattern::default();
1014                pattern.header[0] = 0x01;
1015                pattern.header[1] = 0x01;
1016                pattern.header[7] = 0x50;
1017                assert!(!pattern.check_header()?);
1018                Ok(())
1019            }
1020        }
1021        mod audio_track_trigs {
1022            use crate::patterns::AudioTrackTrigs;
1023            use crate::{HasHeaderField, OtToolsIoError};
1024
1025            #[test]
1026            fn true_valid_header() -> Result<(), OtToolsIoError> {
1027                let trigs = AudioTrackTrigs::default();
1028                assert!(trigs.check_header()?);
1029                Ok(())
1030            }
1031
1032            #[test]
1033            fn false_invalid_header() -> Result<(), OtToolsIoError> {
1034                let mut trigs = AudioTrackTrigs::default();
1035                trigs.header[0] = 0x01;
1036                trigs.header[1] = 0x01;
1037                trigs.header[2] = 0x50;
1038                assert!(!trigs.check_header()?);
1039                Ok(())
1040            }
1041        }
1042        mod midi_track_trigs {
1043            use crate::patterns::MidiTrackTrigs;
1044            use crate::{HasHeaderField, OtToolsIoError};
1045
1046            #[test]
1047            fn true_valid_header() -> Result<(), OtToolsIoError> {
1048                let trigs = MidiTrackTrigs::default();
1049                assert!(trigs.check_header()?);
1050                Ok(())
1051            }
1052
1053            #[test]
1054            fn false_invalid_header() -> Result<(), OtToolsIoError> {
1055                let mut trigs = MidiTrackTrigs::default();
1056                trigs.header[0] = 0x01;
1057                trigs.header[1] = 0x01;
1058                trigs.header[2] = 0x50;
1059                assert!(!trigs.check_header()?);
1060                Ok(())
1061            }
1062        }
1063    }
1064}