ot_tools_io/patterns/tracks/
midi.rs1use crate::generics::{Tracks, Trigs};
7use crate::parts::{
8 LfoParamsValues, MidiTrackArpParamsValues, MidiTrackCc1ParamsValues, MidiTrackCc2ParamsValues,
9 MidiTrackMidiParamsValues,
10};
11use crate::patterns::settings::{TrackPatternSettings, TrackPerTrackModeScale};
12use crate::patterns::tracks::TrigRepeatsConditionsAndOffsets;
13use crate::{Defaults, HasHeaderField, OtToolsIoError};
14use itertools::Itertools;
15use ot_tools_io_derive::{ArrayDefaults, AsMutDerive, AsRefDerive, BoxedArrayDefaults};
16use serde::{Deserialize, Serialize};
17use serde_big_array::BigArray;
18use std::array::from_fn;
19
20const MIDI_TRACK_HEADER: [u8; 4] = [0x4d, 0x54, 0x52, 0x41];
22
23#[derive(
25 Copy,
26 Clone,
27 Debug,
28 Eq,
29 Hash,
30 Ord,
31 PartialEq,
32 PartialOrd,
33 Serialize,
34 Deserialize,
35 AsMutDerive,
36 AsRefDerive,
37 ArrayDefaults,
38 BoxedArrayDefaults,
39)]
40pub struct MidiTrackParameterLocks {
41 pub midi: MidiTrackMidiParamsValues,
42 pub lfo: LfoParamsValues,
43 pub arp: MidiTrackArpParamsValues,
44 pub ctrl1: MidiTrackCc1ParamsValues,
45 pub ctrl2: MidiTrackCc2ParamsValues,
46
47 #[serde(with = "BigArray")]
48 unknown: [u8; 2],
49}
50
51impl Default for MidiTrackParameterLocks {
52 fn default() -> Self {
53 Self {
65 midi: MidiTrackMidiParamsValues {
66 note: 255,
67 vel: 255,
68 len: 255,
69 not2: 255,
70 not3: 255,
71 not4: 255,
72 },
73 lfo: LfoParamsValues {
74 spd1: 255,
75 spd2: 255,
76 spd3: 255,
77 dep1: 255,
78 dep2: 255,
79 dep3: 255,
80 },
81 arp: MidiTrackArpParamsValues {
82 tran: 255,
83 leg: 255,
84 mode: 255,
85 spd: 255,
86 rnge: 255,
87 nlen: 255,
88 },
89 ctrl1: MidiTrackCc1ParamsValues {
90 pb: 255,
91 at: 255,
92 cc1: 255,
93 cc2: 255,
94 cc3: 255,
95 cc4: 255,
96 },
97 ctrl2: MidiTrackCc2ParamsValues {
98 cc5: 255,
99 cc6: 255,
100 cc7: 255,
101 cc8: 255,
102 cc9: 255,
103 cc10: 255,
104 },
105 unknown: [255, 255],
106 }
107 }
108}
109
110#[derive(
124 Copy,
125 Clone,
126 Debug,
127 Eq,
128 Hash,
129 Ord,
130 PartialEq,
131 PartialOrd,
132 Serialize,
133 Deserialize,
134 AsMutDerive,
135 AsRefDerive,
136)]
137pub struct MidiTrackTrigMasks {
138 pub trigger: Tracks<u8>,
140
141 pub trigless: Tracks<u8>,
143
144 pub plock: Tracks<u8>,
147
148 pub swing: Tracks<u8>,
150
151 pub unknown: Tracks<u8>,
154}
155
156impl Default for MidiTrackTrigMasks {
157 fn default() -> Self {
158 Self {
159 trigger: Tracks::new(from_fn(|_| 0)),
160 trigless: Tracks::new(from_fn(|_| 0)),
161 plock: Tracks::new(from_fn(|_| 0)),
162 swing: Tracks::new(from_fn(|_| 170)),
163 unknown: Tracks::new(from_fn(|_| 0)),
164 }
165 }
166}
167
168#[derive(
172 Clone,
173 Debug,
174 Eq,
175 Hash,
176 Ord,
177 PartialEq,
178 PartialOrd,
179 Serialize,
180 Deserialize,
181 AsMutDerive,
182 AsRefDerive,
183 BoxedArrayDefaults,
184)]
185pub struct MidiTrackTrigs {
186 #[serde(with = "BigArray")]
194 pub header: [u8; 4],
195
196 #[serde(with = "BigArray")]
198 pub unknown_1: [u8; 4],
199
200 pub track_id: u8,
202
203 pub trig_masks: MidiTrackTrigMasks,
205
206 pub scale_per_track_mode: TrackPerTrackModeScale,
208
209 pub swing_amount: u8,
212
213 pub pattern_settings: TrackPatternSettings,
215
216 pub plocks: Trigs<MidiTrackParameterLocks>,
225
226 pub trig_offsets_repeats_conditions: Trigs<TrigRepeatsConditionsAndOffsets>,
228}
229
230impl MidiTrackTrigMasks {
231 const HALF_PAGE_TRIG_BITMASK_VALUES: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128];
232
233 fn get_halfpage_trigs_from_bitmask_value(bitmask: &u8) -> [bool; 8] {
237 let arr: [bool; 8] = Self::HALF_PAGE_TRIG_BITMASK_VALUES
238 .iter()
239 .map(|x| (bitmask & x) > 0)
240 .collect_array()
241 .unwrap();
242 arr
243 }
244
245 pub fn get_track_trigs_from_bitmasks(bitmasks: &[u8; 8]) -> Trigs<bool> {
249 let x = bitmasks
250 .iter()
251 .flat_map(Self::get_halfpage_trigs_from_bitmask_value)
252 .collect_array()
253 .unwrap();
254
255 Trigs::new(x)
256 }
257
258 pub fn trigger(&self) -> Trigs<bool> {
261 Self::get_track_trigs_from_bitmasks(&self.trigger)
262 }
263
264 pub fn trigless(&self) -> Trigs<bool> {
267 Self::get_track_trigs_from_bitmasks(&self.trigless)
268 }
269
270 pub fn plock(&self) -> Trigs<bool> {
273 Self::get_track_trigs_from_bitmasks(&self.plock)
274 }
275
276 pub fn swing(&self) -> Trigs<bool> {
279 Self::get_track_trigs_from_bitmasks(&self.swing)
280 }
281}
282
283impl Default for MidiTrackTrigs {
284 fn default() -> Self {
285 Self {
286 header: MIDI_TRACK_HEADER,
287 unknown_1: from_fn(|_| 0),
288 track_id: 0,
289 trig_masks: MidiTrackTrigMasks::default(),
290 scale_per_track_mode: TrackPerTrackModeScale::default(),
291 swing_amount: 0,
292 pattern_settings: TrackPatternSettings::default(),
293 plocks: Trigs::default(),
294 trig_offsets_repeats_conditions: Trigs::<TrigRepeatsConditionsAndOffsets>::default(),
295 }
296 }
297}
298
299impl<const N: usize> Defaults<[Self; N]> for MidiTrackTrigs {
301 fn defaults() -> [Self; N]
302 where
303 Self: Default,
304 {
305 from_fn(|i| Self {
306 track_id: i as u8,
307 ..Default::default()
308 })
309 }
310}
311
312#[cfg(test)]
313mod midi_track_trigs_defaults {
314
315 use super::MidiTrackTrigs;
316 use crate::Defaults;
317
318 fn defs() -> [MidiTrackTrigs; 8] {
319 MidiTrackTrigs::defaults()
320 }
321
322 #[test]
323 fn ok_track_ids() -> Result<(), ()> {
324 for i in 0..8 {
325 println!("Track: {} Track ID: {i}", i + 1);
326 assert_eq!(defs()[i].track_id, i as u8);
327 }
328 Ok(())
329 }
330}
331
332impl HasHeaderField for MidiTrackTrigs {
333 fn check_header(&self) -> Result<bool, OtToolsIoError> {
334 Ok(self.header == MIDI_TRACK_HEADER)
335 }
336}
337
338#[cfg(test)]
339mod midi_track_trigs_header {
340 use super::MidiTrackTrigs;
341 use crate::{
342 test_utils::get_blank_proj_dirpath, BankFile, HasHeaderField, OctatrackFileIO,
343 OtToolsIoError,
344 };
345 #[test]
346 fn file_read_valid() -> Result<(), OtToolsIoError> {
347 let path = get_blank_proj_dirpath().join("bank01.work");
348 let x = BankFile::from_data_file(&path)?.patterns[0]
349 .clone()
350 .midi_track_trigs;
351 assert!(x[0].check_header()?);
352 Ok(())
353 }
354
355 #[test]
356 fn file_read_invalid() -> Result<(), OtToolsIoError> {
357 let path = get_blank_proj_dirpath().join("bank01.work");
358 let x = BankFile::from_data_file(&path)?.patterns[0]
359 .clone()
360 .midi_track_trigs;
361 let mut trigs = x[0].clone();
362 trigs.header[0] = 254;
363 trigs.header[1] = 254;
364 trigs.header[2] = 254;
365 trigs.header[3] = 254;
366 assert!(!trigs.check_header()?);
367 Ok(())
368 }
369
370 #[test]
371 fn default_valid() -> Result<(), OtToolsIoError> {
372 let trigs = MidiTrackTrigs::default();
373 assert!(trigs.check_header()?);
374 Ok(())
375 }
376
377 #[test]
378 fn default_invalid() -> Result<(), OtToolsIoError> {
379 let mut trigs = MidiTrackTrigs::default();
380 trigs.header[0] = 0x01;
381 trigs.header[1] = 0x01;
382 trigs.header[2] = 0x50;
383 assert!(!trigs.check_header()?);
384 Ok(())
385 }
386}