1use crate::slices::{Slice, SliceError};
22use crate::traits::SwapBytes;
23use crate::{
24 HasChecksumField, HasFileVersionField, HasHeaderField, IsDefault, OctatrackFileIO,
25 OtToolsIoError,
26};
27use ot_tools_io_derive::{IntegrityChecks, IsDefaultCheck};
28use serde::{Deserialize, Serialize};
29use serde_big_array::{Array, BigArray};
30use std::array::from_fn;
31use thiserror::Error;
32#[derive(Debug, Error)]
43pub enum MarkersError {
44 #[error("invalid loop point: {value}")]
45 Loop { value: u32 },
46 #[error("invalid trim: start={start} end={end}")]
47 Trim { start: u32, end: u32 },
48 #[error("invalid slice count: {value}")]
49 SliceCount { value: u32 },
50 #[error("invalid slice")]
51 Slice(#[from] SliceError),
52}
53
54pub const MARKERS_HEADER: [u8; 21] = [
56 0x46, 0x4f, 0x52, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x44, 0x50, 0x53, 0x31, 0x53, 0x41, 0x4d, 0x50,
57 0x00, 0x00, 0x00, 0x00, 0x00,
58];
59
60pub const MARKERS_FILE_VERSION: u8 = 4;
62
63#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, IntegrityChecks, IsDefaultCheck)]
65pub struct MarkersFile {
66 #[serde(with = "BigArray")]
67 pub header: [u8; 21],
68
69 pub datatype_version: u8,
88
89 pub flex_slots: Box<Array<SlotMarkers, 136>>,
91
92 pub static_slots: Box<Array<SlotMarkers, 128>>,
94
95 pub checksum: u16,
97}
98
99impl MarkersFile {
100 fn new(
101 flex_slots: [SlotMarkers; 136],
102 static_slots: [SlotMarkers; 128],
103 ) -> Result<Self, OtToolsIoError> {
104 let mut init = Self {
105 header: MARKERS_HEADER,
106 datatype_version: MARKERS_FILE_VERSION,
107 flex_slots: Array(flex_slots).into(),
108 static_slots: Array(static_slots).into(),
109 checksum: 0,
110 };
111
112 init.checksum = init.calculate_checksum()?;
113 init.validate()?;
114 Ok(init)
115 }
116
117 fn validate(&self) -> Result<bool, MarkersError> {
118 for slot in self.flex_slots.iter() {
119 slot.validate()?;
120 }
121 for slot in self.static_slots.iter() {
122 slot.validate()?;
123 }
124
125 Ok(true)
126 }
127}
128
129#[cfg(test)]
130mod markers_file_validate {
131 use crate::{test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO, OtToolsIoError};
132 #[test]
133 fn valid() -> Result<(), OtToolsIoError> {
134 let path = get_blank_proj_dirpath().join("markers.work");
135 assert!(MarkersFile::from_data_file(&path)?.validate()?);
136 Ok(())
137 }
138
139 #[test]
140 fn invalid_trim_offset() -> Result<(), OtToolsIoError> {
141 let path = get_blank_proj_dirpath().join("markers.work");
142 let mut x = MarkersFile::from_data_file(&path)?;
143 x.flex_slots[0].trim_offset = 100;
144 assert_eq!(
145 x.validate().unwrap_err().to_string(),
146 "invalid trim: start=100 end=0".to_string()
147 );
148 Ok(())
149 }
150
151 #[test]
152 fn invalid_slice_count() -> Result<(), OtToolsIoError> {
153 let path = get_blank_proj_dirpath().join("markers.work");
154 let mut x = MarkersFile::from_data_file(&path)?;
155 x.flex_slots[0].slice_count = 100;
156 assert_eq!(
157 x.validate().unwrap_err().to_string(),
158 "invalid slice count: 100".to_string()
159 );
160 Ok(())
161 }
162
163 #[test]
164 fn invalid_loop_point() -> Result<(), OtToolsIoError> {
165 let path = get_blank_proj_dirpath().join("markers.work");
166 let mut x = MarkersFile::from_data_file(&path)?;
167 x.flex_slots[0].loop_point = 100;
168 assert_eq!(
169 x.validate().unwrap_err().to_string(),
170 "invalid loop point: 100".to_string()
171 );
172 Ok(())
173 }
174}
175
176impl Default for MarkersFile {
177 fn default() -> Self {
178 Self::new(
179 from_fn(|_| SlotMarkers::default()),
180 from_fn(|_| SlotMarkers::default()),
181 )
182 .unwrap()
183 }
184}
185
186impl SwapBytes for MarkersFile {
187 fn swap_bytes(self) -> Self {
188 let mut flex_slots = self.flex_slots.clone();
189 for (i, slot) in self.flex_slots.iter().enumerate() {
190 flex_slots[i] = slot.clone().swap_bytes();
191 }
192
193 let mut static_slots = self.static_slots.clone();
194 for (i, slot) in self.static_slots.iter().enumerate() {
195 static_slots[i] = slot.clone().swap_bytes();
196 }
197
198 Self {
199 header: self.header,
200 datatype_version: self.datatype_version,
201 flex_slots,
202 static_slots,
203 checksum: self.checksum.swap_bytes(),
204 }
205 }
206}
207
208impl OctatrackFileIO for MarkersFile {
209 fn encode(&self) -> Result<Vec<u8>, OtToolsIoError>
210 where
211 Self: Serialize,
212 {
213 let mut chkd = self.clone();
214 chkd.checksum = self.calculate_checksum()?;
215 let encoded = if cfg!(target_endian = "little") {
216 bincode::serialize(&chkd.swap_bytes())?
217 } else {
218 bincode::serialize(&chkd)?
219 };
220 Ok(encoded)
221 }
222
223 fn decode(bytes: &[u8]) -> Result<Self, OtToolsIoError>
224 where
225 Self: Sized,
226 Self: for<'a> Deserialize<'a>,
227 {
228 let mut x: Self = bincode::deserialize(bytes)?;
229 #[cfg(target_endian = "little")]
230 {
231 x = x.swap_bytes();
232 }
233
234 Ok(x)
235 }
236}
237
238#[cfg(test)]
239mod decode {
240 use crate::{
241 read_bin_file, test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO,
242 OtToolsIoError,
243 };
244 #[test]
245 fn valid() -> Result<(), OtToolsIoError> {
246 let path = get_blank_proj_dirpath().join("markers.work");
247 let bytes = read_bin_file(&path)?;
248 let s = MarkersFile::decode(&bytes)?;
249 assert_eq!(s, MarkersFile::default());
250 Ok(())
251 }
252}
253
254#[cfg(test)]
255mod encode {
256 use crate::{
257 read_bin_file, test_utils::get_blank_proj_dirpath, MarkersFile, OctatrackFileIO,
258 OtToolsIoError,
259 };
260 #[test]
261 fn valid() -> Result<(), OtToolsIoError> {
262 let path = get_blank_proj_dirpath().join("markers.work");
263 let bytes = read_bin_file(&path)?;
264 let b = MarkersFile::default().encode()?;
265 assert_eq!(b, bytes);
266 Ok(())
267 }
268}
269
270impl HasChecksumField for MarkersFile {
271 fn calculate_checksum(&self) -> Result<u16, OtToolsIoError> {
272 let bytes = bincode::serialize(self)?;
273 let mut chk: u16 = 0;
274 for byte in &bytes[16..bytes.len() - 2] {
275 chk = chk.wrapping_add(*byte as u16);
276 }
277 Ok(chk)
278 }
279 fn check_checksum(&self) -> Result<bool, OtToolsIoError> {
280 Ok(self.checksum == self.calculate_checksum()?)
281 }
282}
283
284#[cfg(test)]
285mod checksum_field {
286 use crate::{HasChecksumField, MarkersFile, OtToolsIoError};
287 #[test]
288 fn valid() -> Result<(), OtToolsIoError> {
289 let mut x = MarkersFile::default();
290 x.checksum = x.calculate_checksum()?;
291 assert!(x.check_checksum()?);
292 Ok(())
293 }
294
295 #[test]
296 fn invalid() -> Result<(), OtToolsIoError> {
297 let mut x = MarkersFile::default();
298 x.checksum = x.calculate_checksum()?;
299 x.checksum = 0;
300 assert!(!x.check_checksum()?);
301 Ok(())
302 }
303}
304
305impl HasHeaderField for MarkersFile {
306 fn check_header(&self) -> Result<bool, OtToolsIoError> {
307 Ok(self.header == MARKERS_HEADER)
308 }
309}
310
311#[cfg(test)]
312mod header_field {
313 use crate::{HasHeaderField, MarkersFile, OtToolsIoError};
314 #[test]
315 fn valid() -> Result<(), OtToolsIoError> {
316 assert!(MarkersFile::default().check_header()?);
317 Ok(())
318 }
319
320 #[test]
321 fn invalid() -> Result<(), OtToolsIoError> {
322 let mut mutated = MarkersFile::default();
323 mutated.header[0] = 0x00;
324 mutated.header[20] = 111;
325 assert!(!mutated.check_header()?);
326 Ok(())
327 }
328}
329
330impl HasFileVersionField for MarkersFile {
331 fn check_file_version(&self) -> Result<bool, OtToolsIoError> {
332 Ok(self.datatype_version == MARKERS_FILE_VERSION)
333 }
334}
335
336#[cfg(test)]
337mod file_version_field {
338 use crate::{HasFileVersionField, MarkersFile, OtToolsIoError};
339 #[test]
340 fn valid() -> Result<(), OtToolsIoError> {
341 assert!(MarkersFile::default().check_file_version()?);
342 Ok(())
343 }
344
345 #[test]
346 fn invalid() -> Result<(), OtToolsIoError> {
347 let mut mutated = MarkersFile {
348 datatype_version: 0,
349 ..Default::default()
350 };
351 mutated.datatype_version = 0;
352 assert!(!mutated.check_file_version()?);
353 Ok(())
354 }
355}
356
357#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
368pub struct SlotMarkers {
369 pub trim_offset: u32,
371
372 pub trim_end: u32,
374
375 pub loop_point: u32,
377
378 #[serde(with = "BigArray")]
380 pub slices: [Slice; 64],
381
382 pub slice_count: u32,
384}
385
386impl Default for SlotMarkers {
387 fn default() -> Self {
388 Self {
389 trim_offset: 0,
390 trim_end: 0,
391 loop_point: 0,
392 slices: from_fn(|_| Slice::default()),
393 slice_count: 0,
394 }
395 }
396}
397
398impl SwapBytes for SlotMarkers {
399 fn swap_bytes(self) -> Self {
400 let mut slices: [Slice; 64] = self.slices;
401
402 for (i, slice) in self.slices.iter().enumerate() {
403 slices[i] = slice.swap_bytes();
404 }
405
406 Self {
407 trim_offset: self.trim_offset.swap_bytes(),
408 trim_end: self.trim_end.swap_bytes(),
409 loop_point: self.loop_point.swap_bytes(),
410 slices,
411 slice_count: self.slice_count.swap_bytes(),
412 }
413 }
414}
415
416impl SlotMarkers {
417 fn validate(&self) -> Result<bool, MarkersError> {
418 for slice in self.slices.iter() {
419 slice.validate()?;
420 }
421 if self.trim_offset > self.trim_end {
422 return Err(MarkersError::Trim {
423 start: self.trim_offset,
424 end: self.trim_end,
425 });
426 }
427
428 let slice_count = self.slices.iter().filter(|x| !x.is_default()).count();
429
430 if self.slice_count != slice_count as u32 {
431 return Err(MarkersError::SliceCount {
432 value: self.slice_count,
433 });
434 }
435
436 if !crate::check_loop_point(self.loop_point, self.trim_offset, self.trim_end) {
437 return Err(MarkersError::Loop {
438 value: self.loop_point,
439 });
440 }
441
442 Ok(true)
443 }
444}
445
446#[cfg(test)]
447mod slot_markers_validate {
448 use super::SlotMarkers;
449 use crate::OtToolsIoError;
450 #[test]
451 fn valid() -> Result<(), OtToolsIoError> {
452 assert!(SlotMarkers::default().validate()?);
453 Ok(())
454 }
455
456 #[test]
457 fn invalid_trim_offset() -> Result<(), OtToolsIoError> {
458 let x = SlotMarkers {
459 trim_offset: 100,
460 ..SlotMarkers::default()
461 };
462 assert_eq!(
463 x.validate().unwrap_err().to_string(),
464 "invalid trim: start=100 end=0".to_string()
465 );
466 Ok(())
467 }
468
469 #[test]
470 fn invalid_slice_count() -> Result<(), OtToolsIoError> {
471 let x = SlotMarkers {
472 slice_count: 100,
473 ..SlotMarkers::default()
474 };
475 assert_eq!(
476 x.validate().unwrap_err().to_string(),
477 "invalid slice count: 100".to_string()
478 );
479 Ok(())
480 }
481
482 #[test]
483 fn invalid_loop_point() -> Result<(), OtToolsIoError> {
484 let x = SlotMarkers {
485 loop_point: 100,
486 ..SlotMarkers::default()
487 };
488 assert_eq!(
489 x.validate().unwrap_err().to_string(),
490 "invalid loop point: 100".to_string()
491 );
492 Ok(())
493 }
494}