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