1mod slot_markers;
9
10use crate::generics::{PlaybackSlots, RecordingBufferSlots};
11use crate::traits::SwapBytes;
12use crate::{
13 HasChecksumField, HasFileVersionField, HasHeaderField, OctatrackFileIO, OtToolsIoError,
14};
15use ot_tools_io_derive::{AsMutDerive, AsRefDerive, IntegrityChecks, IsDefaultCheck};
16use serde::{Deserialize, Serialize};
17use serde_big_array::BigArray;
18pub use slot_markers::{SlotMarkers, SlotMarkersError};
19pub const MARKERS_HEADER: [u8; 21] = [
31 0x46, 0x4f, 0x52, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x44, 0x50, 0x53, 0x31, 0x53, 0x41, 0x4d, 0x50,
32 0x00, 0x00, 0x00, 0x00, 0x00,
33];
34
35pub const MARKERS_FILE_VERSION: u8 = 4;
37
38#[derive(
54 Clone,
55 Debug,
56 Eq,
57 Hash,
58 Ord,
59 PartialEq,
60 PartialOrd,
61 Serialize,
62 Deserialize,
63 AsMutDerive,
64 AsRefDerive,
65 IntegrityChecks,
66 IsDefaultCheck,
67)]
68pub struct MarkersFile {
69 #[serde(with = "BigArray")]
70 pub header: [u8; 21],
71
72 pub datatype_version: u8,
91
92 pub flex_slots: PlaybackSlots<SlotMarkers>,
96
97 pub recording_buffers: RecordingBufferSlots<SlotMarkers>,
99
100 pub static_slots: PlaybackSlots<SlotMarkers>,
102
103 pub checksum: u16,
105}
106
107impl MarkersFile {
108 pub fn new(
109 flex_slots: PlaybackSlots<SlotMarkers>,
110 recording_buffers: RecordingBufferSlots<SlotMarkers>,
111 static_slots: PlaybackSlots<SlotMarkers>,
112 ) -> Result<Self, OtToolsIoError> {
113 let mut init = Self {
114 header: MARKERS_HEADER,
115 datatype_version: MARKERS_FILE_VERSION,
116 flex_slots,
117 recording_buffers,
118 static_slots,
119 checksum: 0,
120 };
121
122 init.checksum = init.calculate_checksum()?;
123 init.validate()?;
124 Ok(init)
125 }
126
127 pub fn validate(&self) -> Result<(), OtToolsIoError> {
128 for slot in self.flex_slots.iter() {
129 slot.validate()?;
130 }
131 for slot in self.static_slots.iter() {
132 slot.validate()?;
133 }
134 Ok(())
135 }
136
137 pub fn deinit(&mut self) {
139 for slot in self.static_slots.iter_mut() {
140 slot.deinit();
141 }
142 for slot in self.recording_buffers.iter_mut() {
143 slot.deinit();
144 }
145 for slot in self.flex_slots.iter_mut() {
146 slot.deinit();
147 }
148 }
149}
150
151#[cfg(test)]
152mod deinit {
153 use crate::{IsDefault, MarkersFile, OtToolsIoError};
154 #[test]
155 fn ok() -> Result<(), OtToolsIoError> {
156 let mut x = MarkersFile::default();
157 x.deinit();
158 assert!(x.is_default());
159 Ok(())
160 }
161}
162
163#[cfg(test)]
164mod validate {
165 use crate::markers::SlotMarkersError;
166 use crate::{test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO, OtToolsIoError};
167
168 #[test]
169 fn valid() -> Result<(), OtToolsIoError> {
170 let path = get_blank_proj_dirpath().join("markers.work");
171 assert!(MarkersFile::from_data_file(&path)?.validate().is_ok());
172 Ok(())
173 }
174
175 #[test]
176 fn invalid_trim_offset() -> Result<(), OtToolsIoError> {
177 let path = get_blank_proj_dirpath().join("markers.work");
178 let mut x = MarkersFile::from_data_file(&path)?;
179 x.flex_slots[0].trim_offset = 100;
180 assert_eq!(
181 x.validate().unwrap_err().to_string(),
182 OtToolsIoError::Markers(SlotMarkersError::Trim { start: 100, end: 0 }).to_string()
183 );
184 Ok(())
185 }
186
187 #[test]
188 fn invalid_slice_count() -> Result<(), OtToolsIoError> {
189 let path = get_blank_proj_dirpath().join("markers.work");
190 let mut x = MarkersFile::from_data_file(&path)?;
191 x.flex_slots[0].slice_count = 100;
192 x.flex_slots[0].trim_end = 1000;
193 x.flex_slots[0].trim_offset = 0;
194 x.flex_slots[0].loop_point = 0;
195 assert_eq!(
196 x.validate().unwrap_err().to_string(),
197 OtToolsIoError::Markers(SlotMarkersError::SliceCount { value: 100 }).to_string()
198 );
199 Ok(())
200 }
201
202 #[test]
203 fn invalid_loop_point() -> Result<(), OtToolsIoError> {
204 let path = get_blank_proj_dirpath().join("markers.work");
205 let mut x = MarkersFile::from_data_file(&path)?;
206 x.flex_slots[0].loop_point = 100;
207 assert_eq!(
208 x.validate().unwrap_err().to_string(),
209 OtToolsIoError::Markers(SlotMarkersError::Loop { value: 100 }).to_string()
210 );
211 Ok(())
212 }
213}
214
215impl Default for MarkersFile {
216 fn default() -> Self {
217 Self::new(
218 PlaybackSlots::<SlotMarkers>::default(),
219 RecordingBufferSlots::<SlotMarkers>::default(),
220 PlaybackSlots::<SlotMarkers>::default(),
221 )
222 .unwrap()
223 }
224}
225
226impl SwapBytes for MarkersFile {
227 fn swap_bytes(self) -> Self {
228 let mut flex_slots = self.flex_slots.clone();
229 #[allow(clippy::clone_on_copy)] for (i, slot) in self.flex_slots.iter().enumerate() {
231 flex_slots[i] = slot.clone().swap_bytes();
232 }
233
234 let mut static_slots = self.static_slots.clone();
235 #[allow(clippy::clone_on_copy)] for (i, slot) in self.static_slots.iter().enumerate() {
237 static_slots[i] = slot.clone().swap_bytes();
238 }
239
240 let mut recording_buffers = self.recording_buffers;
241 #[allow(clippy::clone_on_copy)] for (i, slot) in self.recording_buffers.iter().enumerate() {
243 recording_buffers[i] = slot.clone().swap_bytes();
244 }
245
246 Self {
247 header: self.header,
248 datatype_version: self.datatype_version,
249 flex_slots,
250 recording_buffers,
251 static_slots,
252 checksum: self.checksum.swap_bytes(),
253 }
254 }
255}
256
257impl OctatrackFileIO for MarkersFile {
258 fn to_bytes(&self) -> Result<Vec<u8>, OtToolsIoError>
259 where
260 Self: Serialize,
261 {
262 let mut chkd = self.clone();
263 chkd.checksum = self.calculate_checksum()?;
264 let encoded = if cfg!(target_endian = "little") {
265 bincode::serialize(&chkd.swap_bytes())?
266 } else {
267 bincode::serialize(&chkd)?
268 };
269 Ok(encoded)
270 }
271
272 fn from_bytes(bytes: &[u8]) -> Result<Self, OtToolsIoError>
273 where
274 Self: Sized,
275 Self: for<'a> Deserialize<'a>,
276 {
277 let mut x: Self = bincode::deserialize(bytes)?;
278 #[cfg(target_endian = "little")]
279 {
280 x = x.swap_bytes();
281 }
282
283 Ok(x)
284 }
285}
286
287#[cfg(test)]
288mod from_bytes {
289 use crate::{
290 read_bin_file, test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO,
291 OtToolsIoError,
292 };
293 #[test]
294 fn valid() -> Result<(), OtToolsIoError> {
295 let path = get_blank_proj_dirpath().join("markers.work");
296 let bytes = read_bin_file(&path)?;
297 let s = MarkersFile::from_bytes(&bytes)?;
298 assert_eq!(s, MarkersFile::default());
299 Ok(())
300 }
301}
302
303#[cfg(test)]
304mod to_bytes {
305 use crate::{
306 read_bin_file, test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO,
307 OtToolsIoError,
308 };
309 #[test]
310 fn valid() -> Result<(), OtToolsIoError> {
311 let path = get_blank_proj_dirpath().join("markers.work");
312 let bytes = read_bin_file(&path)?;
313 let b = MarkersFile::default().to_bytes()?;
314 assert_eq!(b, bytes);
315 Ok(())
316 }
317}
318
319impl HasChecksumField for MarkersFile {
320 fn calculate_checksum(&self) -> Result<u16, OtToolsIoError> {
321 let bytes = bincode::serialize(self)?;
322 let mut chk: u16 = 0;
323 for byte in &bytes[16..bytes.len() - 2] {
324 chk = chk.wrapping_add(*byte as u16);
325 }
326 Ok(chk)
327 }
328
329 fn update_checksum(&mut self) -> Result<(), OtToolsIoError> {
330 self.checksum = self.calculate_checksum()?;
331 Ok(())
332 }
333
334 fn check_checksum(&self) -> Result<bool, OtToolsIoError> {
335 Ok(self.checksum == self.calculate_checksum()?)
336 }
337}
338
339#[cfg(test)]
340mod checksum_field {
341 use crate::{HasChecksumField, MarkersFile, OtToolsIoError};
342 #[test]
343 fn valid() -> Result<(), OtToolsIoError> {
344 let mut x = MarkersFile::default();
345 x.checksum = x.calculate_checksum()?;
346 assert!(x.check_checksum()?);
347 Ok(())
348 }
349
350 #[test]
351 fn invalid() -> Result<(), OtToolsIoError> {
352 let x = MarkersFile {
353 checksum: u16::MAX,
354 ..Default::default()
355 };
356 assert!(!x.check_checksum()?);
357 Ok(())
358 }
359
360 mod files {
361 use crate::test_utils::{get_blank_proj_dirpath, get_markers_dirpath};
362 use crate::{HasChecksumField, MarkersFile, OctatrackFileIO, OtToolsIoError};
363 use std::path::Path;
364
365 fn helper_test_chksum(fp: &Path) -> Result<(u16, u16), OtToolsIoError> {
366 let valid = MarkersFile::from_data_file(fp)?;
367 let mut test = valid.clone();
368 test.checksum = 0;
369 let chk = test.calculate_checksum()?;
370 Ok((chk, valid.checksum))
371 }
372
373 #[allow(clippy::field_reassign_with_default)]
374 #[test]
375 fn default_method() -> Result<(), OtToolsIoError> {
376 let (_, valid) = helper_test_chksum(&get_blank_proj_dirpath().join("markers.work"))?;
377 let mut x = MarkersFile::default();
378 x.checksum = 0;
379 let test = x.calculate_checksum()?;
380 assert_eq!(test, valid);
381 Ok(())
382 }
383
384 #[test]
385 fn base_proj_default_file() -> Result<(), OtToolsIoError> {
386 let (test, valid) = helper_test_chksum(&get_blank_proj_dirpath().join("markers.work"))?;
387 assert_eq!(test, valid);
388 Ok(())
389 }
390
391 #[test]
392 fn flex_slot_noedit() -> Result<(), OtToolsIoError> {
393 let (test, valid) =
394 helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-noedit.work"))?;
395 assert_eq!(test, valid);
396 Ok(())
397 }
398
399 #[test]
400 fn flex_slot_1_loop() -> Result<(), OtToolsIoError> {
401 let (test, valid) =
402 helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-loop-edit.work"))?;
403 assert_eq!(test, valid);
404 Ok(())
405 }
406
407 #[test]
408 fn flex_slot_1_slice_1_loop() -> Result<(), OtToolsIoError> {
409 let (test, valid) =
410 helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-slice-1-looped.work"))?;
411 assert_eq!(test, valid);
412 Ok(())
413 }
414
415 #[test]
416 fn flex_slot_1_slice_1_noloop() -> Result<(), OtToolsIoError> {
417 let (test, valid) =
418 helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-slice-1-noloop.work"))?;
419 assert_eq!(test, valid);
420 Ok(())
421 }
422
423 #[test]
424 fn flex_slot_1_slice_4_noloop() -> Result<(), OtToolsIoError> {
425 let (test, valid) =
426 helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-slice-4-noloop.work"))?;
427 assert_eq!(test, valid);
428 Ok(())
429 }
430
431 #[test]
432 fn flex_slot_1_start_edit() -> Result<(), OtToolsIoError> {
433 let (test, valid) =
434 helper_test_chksum(&get_markers_dirpath().join("flex-slot-1-start-edit.work"))?;
435 assert_eq!(test, valid);
436 Ok(())
437 }
438
439 #[test]
440 fn flex_slot_128_noedit() -> Result<(), OtToolsIoError> {
441 let (test, valid) =
442 helper_test_chksum(&get_markers_dirpath().join("flex-slot-128-noedit.work"))?;
443 assert_eq!(test, valid);
444 Ok(())
445 }
446
447 #[test]
448 fn recorder_slot_1_noedit() -> Result<(), OtToolsIoError> {
449 let (test, valid) =
450 helper_test_chksum(&get_markers_dirpath().join("recorder-slot-1-noedit.work"))?;
451 assert_eq!(test, valid);
452 Ok(())
453 }
454
455 #[test]
456 fn static_slot_1_noedit() -> Result<(), OtToolsIoError> {
457 let (test, valid) =
458 helper_test_chksum(&get_markers_dirpath().join("static-slot-1-noedit.work"))?;
459 assert_eq!(test, valid);
460 Ok(())
461 }
462
463 #[test]
464 fn static_slot_128_noedit() -> Result<(), OtToolsIoError> {
465 let (test, valid) =
466 helper_test_chksum(&get_markers_dirpath().join("static-slot-128-noedit.work"))?;
467 assert_eq!(test, valid);
468 Ok(())
469 }
470 }
471}
472
473impl HasHeaderField for MarkersFile {
474 fn check_header(&self) -> Result<bool, OtToolsIoError> {
475 Ok(self.header == MARKERS_HEADER)
476 }
477}
478
479#[cfg(test)]
480mod header_field {
481 use crate::{HasHeaderField, MarkersFile, OtToolsIoError};
482 #[test]
483 fn valid() -> Result<(), OtToolsIoError> {
484 assert!(MarkersFile::default().check_header()?);
485 Ok(())
486 }
487
488 #[test]
489 fn invalid() -> Result<(), OtToolsIoError> {
490 let mut mutated = MarkersFile::default();
491 mutated.header[0] = 0x00;
492 mutated.header[20] = 111;
493 assert!(!mutated.check_header()?);
494 Ok(())
495 }
496}
497
498impl HasFileVersionField for MarkersFile {
499 fn check_file_version(&self) -> Result<bool, OtToolsIoError> {
500 Ok(self.datatype_version == MARKERS_FILE_VERSION)
501 }
502}
503
504#[cfg(test)]
505mod file_version_field {
506 use crate::{HasFileVersionField, MarkersFile, OtToolsIoError};
507 #[test]
508 fn valid() -> Result<(), OtToolsIoError> {
509 assert!(MarkersFile::default().check_file_version()?);
510 Ok(())
511 }
512
513 #[test]
514 fn invalid() -> Result<(), OtToolsIoError> {
515 let mut mutated = MarkersFile {
516 datatype_version: 0,
517 ..Default::default()
518 };
519 mutated.datatype_version = 0;
520 assert!(!mutated.check_file_version()?);
521 Ok(())
522 }
523}