mod lfos;
mod scenes;
mod tracks;
use crate::generics::{Scenes, Tracks};
pub use crate::parts::{
lfos::CustomLfoDesign,
lfos::CustomLfoInterpolationMask,
lfos::LfoParamsSetup1,
lfos::LfoParamsSetup2,
lfos::LfoParamsValues,
scenes::ActiveScenes,
scenes::SceneParams,
scenes::SceneXlvAssignments,
tracks::audio::AudioTrackAmpParamsSetup,
tracks::audio::AudioTrackAmpParamsValues,
tracks::audio::AudioTrackFxParamsSetup,
tracks::audio::AudioTrackFxParamsValues,
tracks::audio::AudioTrackMachineParamsSetupNeighbor,
tracks::audio::AudioTrackMachineParamsSetupPickup,
tracks::audio::AudioTrackMachineParamsSetupStd,
tracks::audio::AudioTrackMachineParamsSetupThru,
tracks::audio::AudioTrackMachineParamsValuesNeighbor,
tracks::audio::AudioTrackMachineParamsValuesPickup,
tracks::audio::AudioTrackMachineParamsValuesStd,
tracks::audio::AudioTrackMachineParamsValuesThru,
tracks::audio::AudioTrackMachineSlot,
tracks::audio::AudioTrackMachinesParamsSetup,
tracks::audio::AudioTrackMachinesParamsValues,
tracks::audio::AudioTrackParamsSetup,
tracks::audio::AudioTrackParamsValues,
tracks::audio::AudioTrackSceneLockPlayback,
tracks::audio::AudioTrackVolume,
tracks::midi::MidiArpSequence,
tracks::midi::MidiTrackArpParamsSetup,
tracks::midi::MidiTrackArpParamsValues,
tracks::midi::MidiTrackCc1ParamsSetup,
tracks::midi::MidiTrackCc1ParamsValues,
tracks::midi::MidiTrackCc2ParamsSetup,
tracks::midi::MidiTrackCc2ParamsValues,
tracks::midi::MidiTrackMidiParamsValues,
tracks::midi::MidiTrackNoteParamsSetup,
tracks::midi::MidiTrackParamsSetup,
tracks::midi::MidiTrackParamsValues,
tracks::recorder::RecorderSetup,
tracks::recorder::RecorderSetupProcessing,
tracks::recorder::RecorderSetupSources,
};
use crate::{Defaults, HasHeaderField, OtToolsIoError};
use ot_tools_io_derive::{AsMutDerive, AsRefDerive, BoxedArrayDefaults};
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
use std::array::from_fn;
#[cfg(test)]
mod test_helper {
use crate::{
read_bin_file, test_utils::get_blank_proj_dirpath, BankFile, OctatrackFileIO,
OtToolsIoError,
};
pub(super) fn get_default_test_bank() -> Result<BankFile, OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let bytes = read_bin_file(&path)?;
BankFile::from_bytes(&bytes)
}
}
const PART_HEADER: [u8; 4] = [0x50, 0x41, 0x52, 0x54];
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
)]
pub enum OnOrOff {
On = 1,
Off = 0,
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
BoxedArrayDefaults,
)]
pub struct Part {
#[serde(with = "BigArray")]
pub header: [u8; 4],
#[serde(with = "BigArray")]
pub data_block_1: [u8; 4],
pub part_id: u8,
pub audio_track_fx1: Tracks<u8>,
pub audio_track_fx2: Tracks<u8>,
pub active_scenes: ActiveScenes,
pub audio_track_volumes: Tracks<AudioTrackVolume>,
pub audio_track_machine_types: Tracks<u8>,
pub audio_track_machine_params: Tracks<AudioTrackMachinesParamsValues>,
pub audio_track_params_values: Tracks<AudioTrackParamsValues>,
pub audio_track_machine_setup: Tracks<AudioTrackMachinesParamsSetup>,
pub audio_track_machine_slots: Tracks<AudioTrackMachineSlot>,
pub audio_track_params_setup: Tracks<AudioTrackParamsSetup>,
pub midi_track_params_values: Tracks<MidiTrackParamsValues>,
pub midi_track_params_setup: Tracks<MidiTrackParamsSetup>,
pub recorder_setup: Tracks<RecorderSetup>,
pub scenes: Scenes<Tracks<SceneParams>>,
pub scene_xlvs: Scenes<SceneXlvAssignments>,
pub audio_tracks_custom_lfo_designs: Tracks<CustomLfoDesign>,
pub audio_tracks_custom_lfos_interpolation_masks: Tracks<CustomLfoInterpolationMask>,
pub midi_tracks_custom_lfos: Tracks<CustomLfoDesign>,
pub midi_tracks_custom_lfos_interpolation_masks: Tracks<CustomLfoInterpolationMask>,
#[serde(with = "BigArray")]
pub midi_tracks_arp_mute_masks: [u8; 16],
pub midi_tracks_arp_seqs: Tracks<MidiArpSequence>,
}
impl Default for Part {
fn default() -> Self {
Self {
header: PART_HEADER,
data_block_1: from_fn(|_| 0),
part_id: 0,
audio_track_fx1: Tracks::new(from_fn(|_| 4)),
audio_track_fx2: Tracks::new(from_fn(|_| 8)),
active_scenes: ActiveScenes::default(),
audio_track_volumes: Tracks::default(),
audio_track_machine_types: Tracks::new(from_fn(|_| 0)),
audio_track_machine_params: Tracks::default(),
audio_track_params_values: Tracks::default(),
audio_track_machine_setup: Tracks::default(),
audio_track_machine_slots: Tracks::default(),
audio_track_params_setup: Tracks::default(),
midi_track_params_values: Tracks::default(),
midi_track_params_setup: Tracks::default(),
recorder_setup: Tracks::default(),
scenes: Scenes::<Tracks<SceneParams>>::default(),
scene_xlvs: Scenes::<SceneXlvAssignments>::default(),
audio_tracks_custom_lfo_designs: Tracks::default(),
audio_tracks_custom_lfos_interpolation_masks: Tracks::default(),
midi_tracks_custom_lfos: Tracks::default(),
midi_tracks_custom_lfos_interpolation_masks: Tracks::default(),
midi_tracks_arp_mute_masks: from_fn(|_| 255),
midi_tracks_arp_seqs: Tracks::default(),
}
}
}
impl crate::IsDefault for Part {
fn is_default(&self) -> bool
where
Self: Default + PartialEq,
{
let x = Self {
part_id: self.part_id,
..Default::default()
};
self == &x
}
}
#[cfg(test)]
mod is_default {
use crate::parts::Part;
use crate::IsDefault;
use crate::OtToolsIoError;
#[test]
fn valid_part_1() -> Result<(), OtToolsIoError> {
let p = Part::default();
assert!(p.is_default());
Ok(())
}
#[test]
fn valid_part_2() -> Result<(), OtToolsIoError> {
assert!(Part {
part_id: 1,
..Default::default()
}
.is_default());
Ok(())
}
#[test]
fn valid_part_3() -> Result<(), OtToolsIoError> {
assert!(Part {
part_id: 2,
..Default::default()
}
.is_default());
Ok(())
}
#[test]
fn valid_part_4() -> Result<(), OtToolsIoError> {
assert!(Part {
part_id: 3,
..Default::default()
}
.is_default());
Ok(())
}
}
impl<const N: usize> Defaults<[Self; N]> for Part {
fn defaults() -> [Self; N]
where
Self: Default,
{
from_fn(|i| Self {
part_id: i as u8,
..Default::default()
})
}
}
impl HasHeaderField for Part {
fn check_header(&self) -> Result<bool, OtToolsIoError> {
Ok(self.header == PART_HEADER)
}
}
#[cfg(test)]
mod part_header_field {
use crate::{
test_utils::get_blank_proj_dirpath, BankFile, HasHeaderField, OctatrackFileIO,
OtToolsIoError,
};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let parts = BankFile::from_data_file(&path)?.parts_saved.clone();
let part = parts[0];
assert!(part.check_header()?);
Ok(())
}
#[test]
fn invalid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let parts = BankFile::from_data_file(&path)?.parts_saved.clone();
let mut part = parts[0];
part.header[0] = 255;
part.header[1] = 255;
part.header[2] = 255;
part.header[3] = 255;
assert!(!part.check_header()?);
Ok(())
}
}
#[cfg(test)]
mod parts_header_field {
use crate::{
test_utils::get_blank_proj_dirpath, BankFile, HasHeaderField, OctatrackFileIO,
OtToolsIoError,
};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let parts = BankFile::from_data_file(&path)?.parts_saved;
assert!(parts.check_header()?);
Ok(())
}
#[test]
fn invalid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("bank01.work");
let mut parts = BankFile::from_data_file(&path)?.parts_saved;
parts[0].header[0] = 255;
parts[0].header[1] = 255;
parts[0].header[2] = 255;
parts[0].header[3] = 255;
assert!(!parts.check_header()?);
Ok(())
}
}