mod slot_markers;
use crate::generics::{PlaybackSlots, RecordingBufferSlots};
use crate::traits::SwapBytes;
use crate::{
HasChecksumField, HasFileVersionField, HasHeaderField, OctatrackFileIO, OtToolsIoError,
};
use ot_tools_io_derive::{AsMutDerive, AsRefDerive, IntegrityChecks, IsDefaultCheck};
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
pub use slot_markers::{SlotMarkers, SlotMarkersError};
pub const MARKERS_HEADER: [u8; 21] = [
0x46, 0x4f, 0x52, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x44, 0x50, 0x53, 0x31, 0x53, 0x41, 0x4d, 0x50,
0x00, 0x00, 0x00, 0x00, 0x00,
];
pub const MARKERS_FILE_VERSION: u8 = 4;
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsMutDerive,
AsRefDerive,
IntegrityChecks,
IsDefaultCheck,
)]
pub struct MarkersFile {
#[serde(with = "BigArray")]
pub header: [u8; 21],
pub datatype_version: u8,
pub flex_slots: PlaybackSlots<SlotMarkers>,
pub recording_buffers: RecordingBufferSlots<SlotMarkers>,
pub static_slots: PlaybackSlots<SlotMarkers>,
pub checksum: u16,
}
impl MarkersFile {
pub fn new(
flex_slots: PlaybackSlots<SlotMarkers>,
recording_buffers: RecordingBufferSlots<SlotMarkers>,
static_slots: PlaybackSlots<SlotMarkers>,
) -> Result<Self, OtToolsIoError> {
let mut init = Self {
header: MARKERS_HEADER,
datatype_version: MARKERS_FILE_VERSION,
flex_slots,
recording_buffers,
static_slots,
checksum: 0,
};
init.checksum = init.calculate_checksum()?;
init.validate()?;
Ok(init)
}
pub fn validate(&self) -> Result<(), OtToolsIoError> {
for slot in self.flex_slots.iter() {
slot.validate()?;
}
for slot in self.static_slots.iter() {
slot.validate()?;
}
Ok(())
}
pub fn deinit(&mut self) {
for slot in self.static_slots.iter_mut() {
slot.deinit();
}
for slot in self.recording_buffers.iter_mut() {
slot.deinit();
}
for slot in self.flex_slots.iter_mut() {
slot.deinit();
}
}
}
#[cfg(test)]
mod deinit {
use crate::{IsDefault, MarkersFile, OtToolsIoError};
#[test]
fn ok() -> Result<(), OtToolsIoError> {
let mut x = MarkersFile::default();
x.deinit();
assert!(x.is_default());
Ok(())
}
}
#[cfg(test)]
mod validate {
use crate::markers::SlotMarkersError;
use crate::{test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO, OtToolsIoError};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("markers.work");
assert!(MarkersFile::from_data_file(&path)?.validate().is_ok());
Ok(())
}
#[test]
fn invalid_trim_offset() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("markers.work");
let mut x = MarkersFile::from_data_file(&path)?;
x.flex_slots[0].trim_offset = 100;
assert_eq!(
x.validate().unwrap_err().to_string(),
OtToolsIoError::Markers(SlotMarkersError::Trim { start: 100, end: 0 }).to_string()
);
Ok(())
}
#[test]
fn invalid_slice_count() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("markers.work");
let mut x = MarkersFile::from_data_file(&path)?;
x.flex_slots[0].slice_count = 100;
x.flex_slots[0].trim_end = 1000;
x.flex_slots[0].trim_offset = 0;
x.flex_slots[0].loop_point = 0;
assert_eq!(
x.validate().unwrap_err().to_string(),
OtToolsIoError::Markers(SlotMarkersError::SliceCount { value: 100 }).to_string()
);
Ok(())
}
#[test]
fn invalid_loop_point() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("markers.work");
let mut x = MarkersFile::from_data_file(&path)?;
x.flex_slots[0].loop_point = 100;
assert_eq!(
x.validate().unwrap_err().to_string(),
OtToolsIoError::Markers(SlotMarkersError::Loop { value: 100 }).to_string()
);
Ok(())
}
}
impl Default for MarkersFile {
fn default() -> Self {
Self::new(
PlaybackSlots::<SlotMarkers>::default(),
RecordingBufferSlots::<SlotMarkers>::default(),
PlaybackSlots::<SlotMarkers>::default(),
)
.unwrap()
}
}
impl SwapBytes for MarkersFile {
fn swap_bytes(self) -> Self {
let mut flex_slots = self.flex_slots.clone();
#[allow(clippy::clone_on_copy)] for (i, slot) in self.flex_slots.iter().enumerate() {
flex_slots[i] = slot.clone().swap_bytes();
}
let mut static_slots = self.static_slots.clone();
#[allow(clippy::clone_on_copy)] for (i, slot) in self.static_slots.iter().enumerate() {
static_slots[i] = slot.clone().swap_bytes();
}
let mut recording_buffers = self.recording_buffers;
#[allow(clippy::clone_on_copy)] for (i, slot) in self.recording_buffers.iter().enumerate() {
recording_buffers[i] = slot.clone().swap_bytes();
}
Self {
header: self.header,
datatype_version: self.datatype_version,
flex_slots,
recording_buffers,
static_slots,
checksum: self.checksum.swap_bytes(),
}
}
}
impl OctatrackFileIO for MarkersFile {
fn to_bytes(&self) -> Result<Vec<u8>, OtToolsIoError>
where
Self: Serialize,
{
let mut chkd = self.clone();
chkd.checksum = self.calculate_checksum()?;
let encoded = if cfg!(target_endian = "little") {
bincode::serialize(&chkd.swap_bytes())?
} else {
bincode::serialize(&chkd)?
};
Ok(encoded)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, OtToolsIoError>
where
Self: Sized,
Self: for<'a> Deserialize<'a>,
{
let mut x: Self = bincode::deserialize(bytes)?;
#[cfg(target_endian = "little")]
{
x = x.swap_bytes();
}
Ok(x)
}
}
#[cfg(test)]
mod from_bytes {
use crate::{
read_bin_file, test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO,
OtToolsIoError,
};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("markers.work");
let bytes = read_bin_file(&path)?;
let s = MarkersFile::from_bytes(&bytes)?;
assert_eq!(s, MarkersFile::default());
Ok(())
}
}
#[cfg(test)]
mod to_bytes {
use crate::{
read_bin_file, test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO,
OtToolsIoError,
};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
let path = get_blank_proj_dirpath().join("markers.work");
let bytes = read_bin_file(&path)?;
let b = MarkersFile::default().to_bytes()?;
assert_eq!(b, bytes);
Ok(())
}
}
impl HasChecksumField for MarkersFile {
fn calculate_checksum(&self) -> Result<u16, OtToolsIoError> {
let bytes = bincode::serialize(self)?;
let mut chk: u16 = 0;
for byte in &bytes[16..bytes.len() - 2] {
chk = chk.wrapping_add(*byte as u16);
}
Ok(chk)
}
fn update_checksum(&mut self) -> Result<(), OtToolsIoError> {
self.checksum = self.calculate_checksum()?;
Ok(())
}
fn check_checksum(&self) -> Result<bool, OtToolsIoError> {
Ok(self.checksum == self.calculate_checksum()?)
}
}
#[cfg(test)]
mod checksum_field {
use crate::{HasChecksumField, MarkersFile, OtToolsIoError};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
let mut x = MarkersFile::default();
x.checksum = x.calculate_checksum()?;
assert!(x.check_checksum()?);
Ok(())
}
#[test]
fn invalid() -> Result<(), OtToolsIoError> {
let x = MarkersFile {
checksum: u16::MAX,
..Default::default()
};
assert!(!x.check_checksum()?);
Ok(())
}
mod files {
use crate::test_utils::{get_blank_proj_dirpath, get_markers_dirpath};
use crate::{HasChecksumField, MarkersFile, OctatrackFileIO, OtToolsIoError};
use std::path::Path;
fn helper_test_chksum(fp: &Path) -> Result<(u16, u16), OtToolsIoError> {
let valid = MarkersFile::from_data_file(fp)?;
let mut test = valid.clone();
test.checksum = 0;
let chk = test.calculate_checksum()?;
Ok((chk, valid.checksum))
}
#[allow(clippy::field_reassign_with_default)]
#[test]
fn default_method() -> Result<(), OtToolsIoError> {
let (_, valid) = helper_test_chksum(&get_blank_proj_dirpath().join("markers.work"))?;
let mut x = MarkersFile::default();
x.checksum = 0;
let test = x.calculate_checksum()?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn base_proj_default_file() -> Result<(), OtToolsIoError> {
let (test, valid) = helper_test_chksum(&get_blank_proj_dirpath().join("markers.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_noedit() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-noedit.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_1_loop() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-loop-edit.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_1_slice_1_loop() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-slice-1-looped.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_1_slice_1_noloop() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-slice-1-noloop.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_1_slice_4_noloop() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-slice-4-noloop.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_1_start_edit() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-start-edit.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn flex_slot_128_noedit() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("flex-slot-128-noedit.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn recorder_slot_1_noedit() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("recorder-slot-1-noedit.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn static_slot_1_noedit() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("static-slot-1-noedit.work"))?;
assert_eq!(test, valid);
Ok(())
}
#[test]
fn static_slot_128_noedit() -> Result<(), OtToolsIoError> {
let (test, valid) =
helper_test_chksum(&get_markers_dirpath().join("static-slot-128-noedit.work"))?;
assert_eq!(test, valid);
Ok(())
}
}
}
impl HasHeaderField for MarkersFile {
fn check_header(&self) -> Result<bool, OtToolsIoError> {
Ok(self.header == MARKERS_HEADER)
}
}
#[cfg(test)]
mod header_field {
use crate::{HasHeaderField, MarkersFile, OtToolsIoError};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
assert!(MarkersFile::default().check_header()?);
Ok(())
}
#[test]
fn invalid() -> Result<(), OtToolsIoError> {
let mut mutated = MarkersFile::default();
mutated.header[0] = 0x00;
mutated.header[20] = 111;
assert!(!mutated.check_header()?);
Ok(())
}
}
impl HasFileVersionField for MarkersFile {
fn check_file_version(&self) -> Result<bool, OtToolsIoError> {
Ok(self.datatype_version == MARKERS_FILE_VERSION)
}
}
#[cfg(test)]
mod file_version_field {
use crate::{HasFileVersionField, MarkersFile, OtToolsIoError};
#[test]
fn valid() -> Result<(), OtToolsIoError> {
assert!(MarkersFile::default().check_file_version()?);
Ok(())
}
#[test]
fn invalid() -> Result<(), OtToolsIoError> {
let mut mutated = MarkersFile {
datatype_version: 0,
..Default::default()
};
mutated.datatype_version = 0;
assert!(!mutated.check_file_version()?);
Ok(())
}
}