ot_tools_io/patterns/tracks.rs
1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6use crate::Defaults;
7use ot_tools_io_derive::{ArrayDefaults, AsMutDerive, AsRefDerive, BoxedArrayDefaults};
8use serde::{Deserialize, Serialize};
9use std::array::from_fn;
10
11pub(crate) mod audio;
12pub(crate) mod midi;
13
14/// # Trig Repeats, Conditions and Offsets
15///
16/// This is ..... a slightly frustrating implementation.
17///
18/// There is a 64 length array consisting of a pair of bytes.
19/// Each pair of elements actually represents three different types of data...
20/// Trig Counts and Trig Conditions use the two bytes independently,
21/// so they're easier to explain first.
22///
23/// After that -- see the section on Trig Offsets below, which makes everything more complicated.
24///
25/// ## Trig Counts and Trig Conditions
26///
27/// Trig Counts and Trig Conditions data is interleaved for each trig.
28/// For Trig position 1, array index 0 is the count value and array index 1 is the Trig
29/// Condition.
30///
31/// For trig counts (1st byte), the value (zero-indexed) is multiplied by 32.
32/// - 8 trig counts (7 repeats) --> 7 * 3 = 224
33/// - 4 trig counts (3 repeats) -- 3 * 32 = 96
34/// - 1 trig counts (0 repeats) -- 0 * 32 = 0
35///
36/// For conditionals, see the `TrigCondition` enum and associated traits for more details.
37/// The maximum value for a Trig Condition byte is 64.
38///
39/// ```rust
40/// // no trig micro-timings at all
41/// [
42/// // trig 1
43/// [
44/// 0, // trig counts (number)
45/// 0, // trig condition (enum rep)
46/// ],
47/// // trig 2
48/// [
49/// 224, // trig counts (max value)
50/// 64, // trig condition (max value)
51/// ],
52/// // trig 3
53/// [
54/// 32, // trig counts (minimum non-zero value)
55/// 1, // trig condition (minimum non-zero value)
56/// ],
57/// // ... and so on
58/// ];
59/// ```
60///
61/// ## Trig Offsets
62///
63/// Trig Offset values use both of these interleaved bytes on top of the
64/// trig repeat and trig condition values... Which makes life more complex
65/// and somewhat frustrating.
66///
67/// Inspected values
68/// - -23/384 -> 1st byte 20, 2nd byte 128
69/// - -1/32 -> 1st byte 26, 2nd byte 0
70/// - -1/64 -> 1st byte 29, 2nd byte 0
71/// - -1/128 -> 1st byte 30, 2nd byte 128
72/// - 1/128 -> 1st byte 1, 2nd byte 128
73/// - 1/64 -> 1st byte 3, 2nd byte 0
74/// - 1/32 -> 1st byte 6, 2nd byte 0
75/// - 23/384 -> 1st byte 11, 2nd byte 128
76///
77/// ### 1st byte
78/// The 1st byte only has 31 possible values: 255 - 224 (trig count max) = 31.
79/// So it makes sense sort of that this is a mask? I guess?
80///
81/// ### 2nd byte
82/// From what I can tell, the second offset byte is either 0 or 128.
83/// So a 2nd byte for an offset adjusted trig with a `8:8` trig condition is either
84/// - 128 + 64 = 192
85/// - 0 + 64 = 64
86///
87/// So you will need to a `x.rem_euclid(128)` somewhere if you want to parse this.
88///
89/// Combining the trig offset with trig count and trig conditions, we end up with
90/// ```rust
91/// [
92/// // trig one, -23/384 offset with 1x trig count and None condition
93/// [
94/// 20, // 20 + (32 * 0)
95/// 128, // 128 + 0
96/// ],
97/// // trig two, -23/384 offset with 2x trig count and Fill condition
98/// [
99/// 52, // 20 + (32 * 1)
100/// 129, // 128 + 1
101/// ],
102/// // trig three, -23/384 offset with 3x trig count and Fill condition
103/// [
104/// 84, // 20 + (32 * 2)
105/// 129, // 128 + 1
106/// ],
107/// // trig four, -23/384 offset with 3x trig count and NotFill condition
108/// [
109/// 84, // 20 + (32 * 2)
110/// 130, // 128 + 2
111/// ],
112/// // trig five, +1/32 offset with 2x trig count and Fill condition
113/// [
114/// 38, // 6 + (32 * 1)
115/// 1, // 0 + 1
116/// ],
117/// // trig six, +1/32 offset with 3x trig count and Fill condition
118/// [
119/// 70, // 6 + (32 * 2)
120/// 1, // 0 + 1
121/// ],
122/// // trig seven, +1/32 offset with 3x trig count and NotFill condition
123/// [
124/// 70, // 6 + (32 * 2)
125/// 2, // 0 + 2
126/// ],
127/// // .... and so on
128/// ];
129/// ```
130///
131/// ### Extending pages and offsets
132///
133/// If you have a trig offset on Trig 1 with only one pattern page activated,
134/// the trig offsets for Trig 1 are replicated over the relevant trig
135/// positions for each first trig in the inactive pages in this array.
136///
137/// So, for a 1/32 offset on trig 1 with only one page active, you get the
138/// following values showing up in this array:
139/// - pair of bytes at array index 15 -> 1/32
140/// - pair of bytes at array index 31 -> 1/32
141/// - pair of bytes at array index 47 -> 1/32
142///
143/// This does not happen for offset values at any other trig position
144/// (from what I can tell in my limited testing -- trig values 2-4 and 9-11
145/// inclusive are not replicated in the same way).
146///
147/// This 'replicating trig offset values over unused pages' behaviour does
148/// not happen for trig counts. I haven't tested whether this applies to trig
149/// conditions yet.
150///
151/// It seems that this behaviour could be to make sure the octatrack plays
152/// correctly offset trigs when you extend a page live, i.e. when extending
153/// a one-page pattern to a two-page pattern, if there is a negative offset
154/// value there the octatrack will need to play the offset trig before the
155/// first page has completed.
156///
157/// Or it could be a bug :shrug:
158#[derive(
159 Clone,
160 Debug,
161 Default,
162 Eq,
163 Hash,
164 Ord,
165 PartialEq,
166 PartialOrd,
167 Serialize,
168 Deserialize,
169 AsMutDerive,
170 AsRefDerive,
171 ArrayDefaults,
172 BoxedArrayDefaults,
173)]
174pub struct TrigRepeatsConditionsAndOffsets {
175 /// Number of trig repeats -- WARNING: also contains interleaved data for trig offsets!
176 pub count: u8,
177 /// Trig trigger probability condition -- WARNING: also contains interleaved data for trig offsets!
178 pub condition: u8,
179}