use crate::generics::{Tracks, Trigs};
use crate::parts::{
LfoParamsValues, MidiTrackArpParamsValues, MidiTrackCc1ParamsValues, MidiTrackCc2ParamsValues,
MidiTrackMidiParamsValues,
};
use crate::patterns::settings::{TrackPatternSettings, TrackPerTrackModeScale};
use crate::patterns::tracks::TrigRepeatsConditionsAndOffsets;
use crate::{Defaults, HasHeaderField, OtToolsIoError};
use itertools::Itertools;
use ot_tools_io_derive::{ArrayDefaults, AsMutDerive, AsRefDerive, BoxedArrayDefaults};
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
use std::array::from_fn;
const MIDI_TRACK_HEADER: [u8; 4] = [0x4d, 0x54, 0x52, 0x41];
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
ArrayDefaults,
BoxedArrayDefaults,
)]
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 MidiTrackTrigMasks {
pub trigger: Tracks<u8>,
pub trigless: Tracks<u8>,
pub plock: Tracks<u8>,
pub swing: Tracks<u8>,
pub unknown: Tracks<u8>,
}
impl Default for MidiTrackTrigMasks {
fn default() -> Self {
Self {
trigger: Tracks::new(from_fn(|_| 0)),
trigless: Tracks::new(from_fn(|_| 0)),
plock: Tracks::new(from_fn(|_| 0)),
swing: Tracks::new(from_fn(|_| 170)),
unknown: Tracks::new(from_fn(|_| 0)),
}
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
BoxedArrayDefaults,
)]
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: Trigs<MidiTrackParameterLocks>,
pub trig_offsets_repeats_conditions: Trigs<TrigRepeatsConditionsAndOffsets>,
}
impl MidiTrackTrigMasks {
const HALF_PAGE_TRIG_BITMASK_VALUES: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128];
fn get_halfpage_trigs_from_bitmask_value(bitmask: &u8) -> [bool; 8] {
let arr: [bool; 8] = Self::HALF_PAGE_TRIG_BITMASK_VALUES
.iter()
.map(|x| (bitmask & x) > 0)
.collect_array()
.unwrap();
arr
}
pub fn get_track_trigs_from_bitmasks(bitmasks: &[u8; 8]) -> Trigs<bool> {
let x = bitmasks
.iter()
.flat_map(Self::get_halfpage_trigs_from_bitmask_value)
.collect_array()
.unwrap();
Trigs::new(x)
}
pub fn trigger(&self) -> Trigs<bool> {
Self::get_track_trigs_from_bitmasks(&self.trigger)
}
pub fn trigless(&self) -> Trigs<bool> {
Self::get_track_trigs_from_bitmasks(&self.trigless)
}
pub fn plock(&self) -> Trigs<bool> {
Self::get_track_trigs_from_bitmasks(&self.plock)
}
pub fn swing(&self) -> Trigs<bool> {
Self::get_track_trigs_from_bitmasks(&self.swing)
}
}
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: Trigs::default(),
trig_offsets_repeats_conditions: Trigs::<TrigRepeatsConditionsAndOffsets>::default(),
}
}
}
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 super::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 super::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(())
}
}