use crate::{Defaults, HasHeaderField, OtToolsIoError};
use ot_tools_io_derive::{
ArrayDefaults, AsMutDerive, AsRefDerive, BoxedBigArrayDefaults, ContainerArrayMethods,
IsDefaultCheck,
};
use std::array::from_fn;
use std::slice::{Iter, IterMut};
use crate::parts::{
AudioTrackAmpParamsValues, AudioTrackFxParamsValues, LfoParamsValues, MidiTrackArpParamsValues,
MidiTrackCc1ParamsValues, MidiTrackCc2ParamsValues, MidiTrackMidiParamsValues,
};
use serde::{Deserialize, Serialize};
use serde_big_array::{Array, BigArray};
const PATTERN_HEADER: [u8; 8] = [0x50, 0x54, 0x52, 0x4e, 0x00, 0x00, 0x00, 0x00];
const MIDI_TRACK_HEADER: [u8; 4] = [0x4d, 0x54, 0x52, 0x41];
const AUDIO_TRACK_HEADER: [u8; 4] = [0x54, 0x52, 0x41, 0x43];
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct AudioTrackParameterLockPlayback {
pub param1: u8,
pub param2: u8,
pub param3: u8,
pub param4: u8,
pub param5: u8,
pub param6: u8,
}
impl Default for AudioTrackParameterLocks {
fn default() -> Self {
Self {
machine: AudioTrackParameterLockPlayback {
param1: 255,
param2: 255,
param3: 255,
param4: 255,
param5: 255,
param6: 255,
},
lfo: LfoParamsValues {
spd1: 255,
spd2: 255,
spd3: 255,
dep1: 255,
dep2: 255,
dep3: 255,
},
amp: AudioTrackAmpParamsValues {
atk: 255,
hold: 255,
rel: 255,
vol: 255,
bal: 255,
f: 255,
},
fx1: AudioTrackFxParamsValues {
param_1: 255,
param_2: 255,
param_3: 255,
param_4: 255,
param_5: 255,
param_6: 255,
},
fx2: AudioTrackFxParamsValues {
param_1: 255,
param_2: 255,
param_3: 255,
param_4: 255,
param_5: 255,
param_6: 255,
},
static_slot_id: 255,
flex_slot_id: 255,
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ArrayDefaults,
BoxedBigArrayDefaults,
IsDefaultCheck,
)]
pub struct AudioTrackParameterLocks {
pub machine: AudioTrackParameterLockPlayback,
pub lfo: LfoParamsValues,
pub amp: AudioTrackAmpParamsValues,
pub fx1: AudioTrackFxParamsValues,
pub fx2: AudioTrackFxParamsValues,
pub static_slot_id: u8,
pub flex_slot_id: u8,
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ArrayDefaults,
BoxedBigArrayDefaults,
IsDefaultCheck,
ContainerArrayMethods,
)]
pub struct AudioTrackParameterLocksArray(pub Box<Array<AudioTrackParameterLocks, 64>>);
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ArrayDefaults,
BoxedBigArrayDefaults,
)]
pub struct MidiTrackParameterLocks {
pub midi: MidiTrackMidiParamsValues,
pub lfo: LfoParamsValues,
pub arp: MidiTrackArpParamsValues,
pub ctrl1: MidiTrackCc1ParamsValues,
pub ctrl2: MidiTrackCc2ParamsValues,
#[serde(with = "BigArray")]
unknown: [u8; 2],
}
impl Default for MidiTrackParameterLocks {
fn default() -> Self {
Self {
midi: MidiTrackMidiParamsValues {
note: 255,
vel: 255,
len: 255,
not2: 255,
not3: 255,
not4: 255,
},
lfo: LfoParamsValues {
spd1: 255,
spd2: 255,
spd3: 255,
dep1: 255,
dep2: 255,
dep3: 255,
},
arp: MidiTrackArpParamsValues {
tran: 255,
leg: 255,
mode: 255,
spd: 255,
rnge: 255,
nlen: 255,
},
ctrl1: MidiTrackCc1ParamsValues {
pb: 255,
at: 255,
cc1: 255,
cc2: 255,
cc3: 255,
cc4: 255,
},
ctrl2: MidiTrackCc2ParamsValues {
cc5: 255,
cc6: 255,
cc7: 255,
cc8: 255,
cc9: 255,
cc10: 255,
},
unknown: [255, 255],
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct TrackPatternSettings {
pub start_silent: u8,
pub plays_free: u8,
pub trig_mode: u8,
pub trig_quant: u8,
pub oneshot_trk: u8,
}
impl Default for TrackPatternSettings {
fn default() -> Self {
Self {
start_silent: 255,
plays_free: 0,
trig_mode: 0,
trig_quant: 0,
oneshot_trk: 0,
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct AudioTrackTrigMasks {
#[serde(with = "BigArray")]
pub trigger: [u8; 8],
#[serde(with = "BigArray")]
pub trigless: [u8; 8],
#[serde(with = "BigArray")]
pub plock: [u8; 8],
#[serde(with = "BigArray")]
pub oneshot: [u8; 8],
#[serde(with = "BigArray")]
pub recorder: [u8; 32],
#[serde(with = "BigArray")]
pub swing: [u8; 8],
#[serde(with = "BigArray")]
pub slide: [u8; 8],
}
impl Default for AudioTrackTrigMasks {
fn default() -> Self {
Self {
trigger: from_fn(|_| 0),
trigless: from_fn(|_| 0),
plock: from_fn(|_| 0),
oneshot: from_fn(|_| 0),
recorder: from_fn(|_| 0),
swing: from_fn(|_| 170),
slide: from_fn(|_| 0),
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct TrackPerTrackModeScale {
pub per_track_len: u8,
pub per_track_scale: u8,
}
impl Default for TrackPerTrackModeScale {
fn default() -> Self {
Self {
per_track_len: 16,
per_track_scale: 2,
}
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
BoxedBigArrayDefaults,
)]
pub struct AudioTrackTrigs {
#[serde(with = "BigArray")]
pub header: [u8; 4],
#[serde(with = "BigArray")]
pub unknown_1: [u8; 4],
pub track_id: u8,
pub trig_masks: AudioTrackTrigMasks,
pub scale_per_track_mode: TrackPerTrackModeScale,
pub swing_amount: u8,
pub pattern_settings: TrackPatternSettings,
pub unknown_2: u8,
pub plocks: AudioTrackParameterLocksArray,
#[serde(with = "BigArray")]
pub unknown_3: [u8; 64],
#[serde(with = "BigArray")]
pub trig_offsets_repeats_conditions: [[u8; 2]; 64],
}
impl Default for AudioTrackTrigs {
fn default() -> Self {
Self {
header: AUDIO_TRACK_HEADER,
unknown_1: from_fn(|_| 0),
track_id: 0,
trig_masks: AudioTrackTrigMasks::default(),
scale_per_track_mode: TrackPerTrackModeScale::default(),
swing_amount: 0,
pattern_settings: TrackPatternSettings::default(),
unknown_2: 0,
plocks: AudioTrackParameterLocksArray::default(),
unknown_3: from_fn(|_| 0),
trig_offsets_repeats_conditions: from_fn(|_| [0, 0]),
}
}
}
impl<const N: usize> Defaults<[Self; N]> for AudioTrackTrigs {
fn defaults() -> [Self; N]
where
Self: Default,
{
from_fn(|i| Self {
track_id: i as u8,
..Default::default()
})
}
}
#[cfg(test)]
mod audio_track_trigs_defaults {
use crate::patterns::AudioTrackTrigs;
use crate::Defaults;
fn defs() -> [AudioTrackTrigs; 8] {
AudioTrackTrigs::defaults()
}
#[test]
fn ok_track_ids() -> Result<(), ()> {
for i in 0..8 {
println!("Track: {} Track ID: {i}", i + 1);
assert_eq!(defs()[i].track_id, i as u8);
}
Ok(())
}
}
impl HasHeaderField for AudioTrackTrigs {
fn check_header(&self) -> Result<bool, OtToolsIoError> {
Ok(self.header == AUDIO_TRACK_HEADER)
}
}
#[cfg(test)]
mod audio_track_trigs_header {
use crate::patterns::AudioTrackTrigs;
use crate::{
test_utils::get_blank_proj_dirpath, BankFile, HasHeaderField, OctatrackFileIO,
OtToolsIoError,
};
#[test]
fn file_read_valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let x = BankFile::from_data_file(&path)?.patterns[0]
.clone()
.audio_track_trigs;
assert!(x[0].check_header()?);
Ok(())
}
#[test]
fn file_read_invalid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let x = BankFile::from_data_file(&path)?.patterns[0]
.clone()
.audio_track_trigs;
let mut trigs = x[0].clone();
trigs.header[0] = 254;
trigs.header[1] = 254;
trigs.header[2] = 254;
trigs.header[3] = 254;
assert!(!trigs.check_header()?);
Ok(())
}
#[test]
fn default_valid() -> Result<(), OtToolsIoError> {
let trigs = AudioTrackTrigs::default();
assert!(trigs.check_header()?);
Ok(())
}
#[test]
fn default_invalid() -> Result<(), OtToolsIoError> {
let mut trigs = AudioTrackTrigs::default();
trigs.header[0] = 0x01;
trigs.header[1] = 0x01;
trigs.header[2] = 0x50;
assert!(!trigs.check_header()?);
Ok(())
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ContainerArrayMethods,
)]
pub struct AudioTrackTrigsArray(pub [AudioTrackTrigs; 8]);
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct MidiTrackTrigMasks {
#[serde(with = "BigArray")]
pub trigger: [u8; 8],
#[serde(with = "BigArray")]
pub trigless: [u8; 8],
#[serde(with = "BigArray")]
pub plock: [u8; 8],
#[serde(with = "BigArray")]
pub swing: [u8; 8],
#[serde(with = "BigArray")]
pub unknown: [u8; 8],
}
impl Default for MidiTrackTrigMasks {
fn default() -> Self {
Self {
trigger: from_fn(|_| 0),
trigless: from_fn(|_| 0),
plock: from_fn(|_| 0),
swing: from_fn(|_| 170),
unknown: from_fn(|_| 0),
}
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
BoxedBigArrayDefaults,
)]
pub struct MidiTrackTrigs {
#[serde(with = "BigArray")]
pub header: [u8; 4],
#[serde(with = "BigArray")]
pub unknown_1: [u8; 4],
pub track_id: u8,
pub trig_masks: MidiTrackTrigMasks,
pub scale_per_track_mode: TrackPerTrackModeScale,
pub swing_amount: u8,
pub pattern_settings: TrackPatternSettings,
pub plocks: Box<Array<MidiTrackParameterLocks, 64>>,
#[serde(with = "BigArray")]
pub trig_offsets_repeats_conditions: [[u8; 2]; 64],
}
impl Default for MidiTrackTrigs {
fn default() -> Self {
Self {
header: MIDI_TRACK_HEADER,
unknown_1: from_fn(|_| 0),
track_id: 0,
trig_masks: MidiTrackTrigMasks::default(),
scale_per_track_mode: TrackPerTrackModeScale::default(),
swing_amount: 0,
pattern_settings: TrackPatternSettings::default(),
plocks: MidiTrackParameterLocks::defaults(),
trig_offsets_repeats_conditions: from_fn(|_| [0, 0]),
}
}
}
impl<const N: usize> Defaults<[Self; N]> for MidiTrackTrigs {
fn defaults() -> [Self; N]
where
Self: Default,
{
from_fn(|i| Self {
track_id: i as u8,
..Default::default()
})
}
}
#[cfg(test)]
mod midi_track_trigs_defaults {
use crate::patterns::MidiTrackTrigs;
use crate::Defaults;
fn defs() -> [MidiTrackTrigs; 8] {
MidiTrackTrigs::defaults()
}
#[test]
fn ok_track_ids() -> Result<(), ()> {
for i in 0..8 {
println!("Track: {} Track ID: {i}", i + 1);
assert_eq!(defs()[i].track_id, i as u8);
}
Ok(())
}
}
impl HasHeaderField for MidiTrackTrigs {
fn check_header(&self) -> Result<bool, OtToolsIoError> {
Ok(self.header == MIDI_TRACK_HEADER)
}
}
#[cfg(test)]
mod midi_track_trigs_header {
use crate::patterns::MidiTrackTrigs;
use crate::{
test_utils::get_blank_proj_dirpath, BankFile, HasHeaderField, OctatrackFileIO,
OtToolsIoError,
};
#[test]
fn file_read_valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let x = BankFile::from_data_file(&path)?.patterns[0]
.clone()
.midi_track_trigs;
assert!(x[0].check_header()?);
Ok(())
}
#[test]
fn file_read_invalid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let x = BankFile::from_data_file(&path)?.patterns[0]
.clone()
.midi_track_trigs;
let mut trigs = x[0].clone();
trigs.header[0] = 254;
trigs.header[1] = 254;
trigs.header[2] = 254;
trigs.header[3] = 254;
assert!(!trigs.check_header()?);
Ok(())
}
#[test]
fn default_valid() -> Result<(), OtToolsIoError> {
let trigs = MidiTrackTrigs::default();
assert!(trigs.check_header()?);
Ok(())
}
#[test]
fn default_invalid() -> Result<(), OtToolsIoError> {
let mut trigs = MidiTrackTrigs::default();
trigs.header[0] = 0x01;
trigs.header[1] = 0x01;
trigs.header[2] = 0x50;
assert!(!trigs.check_header()?);
Ok(())
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ContainerArrayMethods,
)]
pub struct MidiTrackTrigsArray(pub [MidiTrackTrigs; 8]);
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct PatternScaleSettings {
pub master_len_per_track_multiplier: u8,
pub master_len_per_track: u8,
pub master_scale_per_track: u8,
pub master_len: u8,
pub master_scale: u8,
pub scale_mode: u8,
}
impl Default for PatternScaleSettings {
fn default() -> Self {
Self {
master_len_per_track_multiplier: 0,
master_len_per_track: 16,
master_scale_per_track: 2,
master_len: 16,
master_scale: 2,
scale_mode: 0,
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub struct PatternChainBehavior {
pub use_pattern_setting: u8,
pub use_project_setting: u8,
}
#[allow(clippy::derivable_impls)]
impl Default for PatternChainBehavior {
fn default() -> Self {
Self {
use_pattern_setting: 0,
use_project_setting: 0,
}
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ArrayDefaults,
BoxedBigArrayDefaults,
IsDefaultCheck,
)]
pub struct Pattern {
#[serde(with = "BigArray")]
pub header: [u8; 8],
pub audio_track_trigs: AudioTrackTrigsArray,
pub midi_track_trigs: MidiTrackTrigsArray,
pub scale: PatternScaleSettings,
pub chain_behaviour: PatternChainBehavior,
pub unknown: u8,
pub part_assignment: u8,
pub tempo_1: u8,
pub tempo_2: u8,
}
impl Default for Pattern {
fn default() -> Self {
Self {
header: PATTERN_HEADER,
audio_track_trigs: AudioTrackTrigsArray::default(),
midi_track_trigs: MidiTrackTrigsArray::default(),
scale: PatternScaleSettings::default(),
chain_behaviour: PatternChainBehavior::default(),
unknown: 0,
part_assignment: 0,
tempo_1: 11,
tempo_2: 64,
}
}
}
impl HasHeaderField for Pattern {
fn check_header(&self) -> Result<bool, OtToolsIoError> {
Ok(self.header == PATTERN_HEADER)
}
}
#[cfg(test)]
mod pattern_header {
use crate::{
patterns::Pattern, test_utils::get_blank_proj_dirpath, BankFile, HasHeaderField,
OctatrackFileIO, OtToolsIoError,
};
#[test]
fn file_read_valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let pattern = BankFile::from_data_file(&path)?.patterns[0].clone();
assert!(pattern.check_header()?);
Ok(())
}
#[test]
fn file_read_invalid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let mut pattern = BankFile::from_data_file(&path)?.patterns[0].clone();
pattern.header[0] = 254;
pattern.header[1] = 254;
pattern.header[2] = 254;
pattern.header[3] = 254;
assert!(!pattern.check_header()?);
Ok(())
}
#[test]
fn default_valid() -> Result<(), OtToolsIoError> {
let pattern = Pattern::default();
assert!(pattern.check_header()?);
Ok(())
}
#[test]
fn default_invalid() -> Result<(), OtToolsIoError> {
let mut pattern = Pattern::default();
pattern.header[0] = 0x01;
pattern.header[1] = 0x01;
pattern.header[7] = 0x50;
assert!(!pattern.check_header()?);
Ok(())
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ContainerArrayMethods,
)]
pub struct PatternArray(pub Box<Array<Pattern, 16>>);