ot_tools_io/slices.rs
1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Slice data structs for sample files (`.ot` files).
7
8use crate::markers::SlotMarkers;
9use crate::traits::SwapBytes;
10use ot_tools_io_derive::IsDefaultCheck;
11use serde::{Deserialize, Serialize};
12use std::array::from_fn;
13use thiserror::Error;
14
15#[derive(Debug, Error)]
16pub enum SliceError {
17 #[error("invalid slice loop point: {value}")]
18 LoopPoint { value: u32 },
19 #[error("invalid slice trim: start={start} end={end}")]
20 Trim { start: u32, end: u32 },
21}
22
23/// Positions of a 'slice' within a single WAV file.
24/// IMPORTANT: slice points are not measured in bars like `SampleAttributes`,
25/// but instead use *audio sample* positions from the audio file.
26#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, Hash, IsDefaultCheck)]
27pub struct Slice {
28 /// Start position for the `Slice`.
29 pub trim_start: u32,
30
31 /// End position for the `Slice`.
32 pub trim_end: u32,
33
34 /// Loop start position for the `Slice`. This is actually `Loop Point` in
35 /// the Octatrack manual.
36 /// > If a loop point is set, the sample will play from the start point to the
37 /// > end point, then loop from the loop point to the end point
38 ///
39 /// Note that a `0xFFFFFFFF` value disables the loop point for the slice.
40 pub loop_start: u32,
41}
42
43pub const SLICE_LOOP_POINT_DISABLED: u32 = 0xFFFFFFFF;
44
45impl Slice {
46 /// Create a new slice.
47 ///
48 /// WARNING: The default setting for `loop_start` changes depending on whether you are working
49 /// with a [`crate::SampleSettingsFile`] or a [`crate::MarkersFile`] ... and whether a sample has
50 /// been loaded into a slot or not for a [`crate::MarkersFile`].
51 ///
52 /// Providing `None` as the `loop_start` argument will default to the
53 /// [`SLICE_LOOP_POINT_DISABLED`] value (`0xFFFFFFFF`).
54 ///
55 /// ```rust
56 /// use ot_tools_io::slices::{Slice, SLICE_LOOP_POINT_DISABLED};
57 ///
58 /// assert_eq!(
59 /// Slice::new(100, 200, None).unwrap(),
60 /// Slice { trim_start: 100, trim_end: 200, loop_start: SLICE_LOOP_POINT_DISABLED}
61 /// );
62 /// assert_eq!(
63 /// Slice::new(100, 200, Some(0)).unwrap(),
64 /// Slice { trim_start: 100, trim_end: 200, loop_start: 0}
65 /// );
66 /// assert_eq!(
67 /// Slice::new(100, 200, Some(150)).unwrap(),
68 /// Slice { trim_start: 100, trim_end: 200, loop_start: 150}
69 /// );
70 /// // cannot have a loop point at the trim_end marker
71 /// assert_eq!(
72 /// Slice::new(100, 200, Some(200)).unwrap_err().to_string(),
73 /// "invalid slice loop point: 200".to_string()
74 /// );
75 /// // can have a loop point at the trim_start marker
76 /// assert_eq!(
77 /// Slice::new(100, 200, Some(100)).unwrap(),
78 /// Slice { trim_start: 100, trim_end: 200, loop_start: 100}
79 /// );
80 /// // cannot have a loop point out of the trim range
81 /// assert_eq!(
82 /// Slice::new(100, 200, Some(500)).unwrap_err().to_string(),
83 /// "invalid slice loop point: 500".to_string()
84 /// );
85 /// // cannot have a loop point out of the trim range
86 /// assert_eq!(
87 /// Slice::new(100, 200, Some(50)).unwrap_err().to_string(),
88 /// "invalid slice loop point: 50".to_string()
89 /// );
90 /// ```
91 pub fn new(
92 trim_start: u32,
93 trim_end: u32,
94 loop_start: Option<u32>,
95 ) -> Result<Self, SliceError> {
96 if trim_start > trim_end {
97 return Err(SliceError::Trim {
98 start: trim_start,
99 end: trim_end,
100 });
101 }
102
103 // default is disabled
104 // TODO: ONLY IN OT FILES!
105 let loop_point = loop_start.unwrap_or(SLICE_LOOP_POINT_DISABLED);
106
107 let x = Self {
108 trim_start,
109 trim_end,
110 loop_start: loop_point,
111 };
112
113 x.validate()?;
114
115 Ok(x)
116 }
117
118 // TODO: Check the logic of this
119 pub fn validate(&self) -> Result<bool, SliceError> {
120 // NOTE: Trim start can be equal to trim end when an empty slice
121 if self.trim_start > self.trim_end {
122 return Err(SliceError::Trim {
123 start: self.trim_start,
124 end: self.trim_end,
125 });
126 }
127 if !crate::check_loop_point(self.loop_start, self.trim_start, self.trim_end) {
128 return Err(SliceError::LoopPoint {
129 value: self.loop_start,
130 });
131 }
132
133 Ok(true)
134 }
135}
136
137#[allow(clippy::derivable_impls)]
138impl Default for Slice {
139 fn default() -> Self {
140 Self {
141 trim_start: 0,
142 trim_end: 0,
143 // initialized data for slices is always 0 ...
144 // BUT when we create a slice on the device, the loop point is set
145 // to DISABLED (0xFFFFFFFF) ...
146 //
147 // but users don't see that the data is actually mutated after
148 // initialization ... so this is one of those weird places where
149 // 'default' is not what a user might expect to see as 'default'
150 loop_start: 0,
151 }
152 }
153}
154
155impl SwapBytes for Slice {
156 fn swap_bytes(self) -> Self {
157 Self {
158 trim_start: self.trim_start.swap_bytes(),
159 trim_end: self.trim_end.swap_bytes(),
160 loop_start: self.loop_start.swap_bytes(),
161 }
162 }
163}
164
165/// A collection of `Slice` objects.
166#[derive(Debug)]
167pub struct Slices {
168 /// `Slice` objects, must be 64 elements in length.
169 pub slices: [Slice; 64],
170
171 /// Number of non-zero valued `Slice` objects in the `slices` field array.
172 pub count: u32,
173}
174
175impl Default for Slices {
176 fn default() -> Self {
177 let slices: [Slice; 64] = from_fn(|_| Slice::default());
178 Slices { slices, count: 0 }
179 }
180}
181
182impl From<SlotMarkers> for Slices {
183 fn from(value: SlotMarkers) -> Self {
184 Self {
185 slices: value.slices,
186 count: value.slice_count,
187 }
188 }
189}
190
191impl From<&SlotMarkers> for Slices {
192 fn from(value: &SlotMarkers) -> Self {
193 Self::from(value.clone())
194 }
195}
196
197#[cfg(test)]
198mod test {
199
200 use crate::slices::Slice;
201
202 #[test]
203 fn ok_no_offset_no_loop() {
204 let valid = Slice {
205 trim_start: 0,
206 trim_end: 1000,
207 loop_start: 0xFFFFFFFF,
208 };
209
210 let s = Slice::new(0, 1000, None);
211
212 assert!(s.is_ok());
213 assert_eq!(valid, s.unwrap());
214 }
215
216 #[test]
217 fn ok_loop_point() {
218 let valid = Slice {
219 trim_start: 0,
220 trim_end: 1000,
221 loop_start: 0,
222 };
223
224 let s = Slice::new(0, 1000, Some(0));
225
226 assert!(s.is_ok());
227 assert_eq!(valid, s.unwrap());
228 }
229
230 #[test]
231 fn err_loop_end() {
232 let s = Slice::new(0, 1000, Some(1000));
233 assert!(s.is_err());
234 assert_eq!(s.unwrap_err().to_string(), "invalid slice loop point: 1000");
235 }
236
237 #[test]
238 fn err_trim() {
239 let s = Slice::new(1001, 1000, None);
240 assert!(s.is_err());
241 assert_eq!(
242 s.unwrap_err().to_string(),
243 "invalid slice trim: start=1001 end=1000"
244 );
245 }
246
247 #[test]
248 fn ok_offset_100_no_loop() {
249 let valid = Slice {
250 trim_start: 100,
251 trim_end: 1100,
252 loop_start: 0xFFFFFFFF,
253 };
254
255 let s = Slice::new(100, 1100, None);
256
257 assert!(s.is_ok());
258 assert_eq!(valid, s.unwrap());
259 }
260
261 #[test]
262 fn ok_offset_100_with_loop_200() {
263 let valid = Slice {
264 trim_start: 100,
265 trim_end: 1100,
266 loop_start: 200,
267 };
268
269 let s = Slice::new(100, 1100, Some(200));
270
271 assert!(s.is_ok());
272 assert_eq!(valid, s.unwrap());
273 }
274}