1mod deserialize;
30mod parsing;
31mod serialize;
32
33use crate::generics::{PlaybackSlots, RecordingBufferSlots, Slots};
34use crate::settings::{LoopMode, SlotType, TimeStretchMode, TrigQuantizationMode};
35use crate::OtToolsIoError;
36use ot_tools_io_derive::{AsMutDerive, AsRefDerive};
37use serde::Serialize;
38use std::array::from_fn;
39use std::path::PathBuf;
40use thiserror::Error;
41
42#[cfg(test)]
43mod test_utils {
44 use crate::projects::slots::SlotAttributes;
45 use crate::settings::LoopMode;
46 use crate::settings::SlotType;
47 use crate::settings::TimeStretchMode;
48 use crate::settings::TrigQuantizationMode;
49 use crate::OtToolsIoError;
50 use std::path::PathBuf;
51
52 pub(crate) fn new_slot_attr_full_args() -> Result<SlotAttributes, OtToolsIoError> {
53 SlotAttributes::new(
54 SlotType::Static,
55 100,
56 Some(PathBuf::from("../AUDIO/location.wav")),
57 Some(TimeStretchMode::default()),
58 Some(LoopMode::default()),
59 Some(TrigQuantizationMode::default()),
60 Some(48),
61 Some(3360),
62 )
63 }
64 pub(crate) fn new_slot_attr_minimal_args() -> Result<SlotAttributes, OtToolsIoError> {
65 SlotAttributes::new(SlotType::Static, 100, None, None, None, None, None, None)
66 }
67}
68
69#[allow(clippy::enum_variant_names)]
70#[derive(Debug, Error)]
71pub enum ProjectSlotsError {
72 #[error("invalid slot_id ({value}), must be in range 1 <= x <= 128")]
73 SlotIdOutOfBounds { value: u8 },
74 #[error("invalid tempo ({value}), must be in range 720 <= x <= 7200")]
75 TempoOutOfBounds { value: u16 },
76 #[error("invalid gain ({value}), must be in range 24 <= x <= 120")]
77 GainOutOfBounds { value: u8 },
78 #[error("failed to parse sample settings data")]
79 SettingsRead,
80 #[error("failed to canonicalize slot path ")]
81 SlotPathCanon(#[from] std::io::Error),
82}
83
84pub const DEFAULT_TEMPO: u16 = 2880;
86
87pub const DEFAULT_GAIN: u8 = 48;
89
90#[derive(
120 Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, AsMutDerive, AsRefDerive,
121)]
122pub struct SlotAttributes {
123 pub slot_type: SlotType,
125
126 pub slot_id: u8,
130
131 pub path: Option<PathBuf>,
136
137 pub timestrech_mode: TimeStretchMode,
140
141 pub loop_mode: LoopMode,
144
145 pub trig_quantization_mode: TrigQuantizationMode,
149
150 pub gain: u8,
152
153 pub bpm: u16,
159}
160
161#[allow(clippy::too_many_arguments)] impl SlotAttributes {
163 pub fn new(
164 slot_type: SlotType,
165 slot_id: u8,
166 path: Option<PathBuf>,
167 timestretch_mode: Option<TimeStretchMode>,
168 loop_mode: Option<LoopMode>,
169 trig_quantization_mode: Option<TrigQuantizationMode>,
170 gain: Option<u8>,
171 bpm: Option<u16>,
172 ) -> Result<Self, OtToolsIoError> {
173 match slot_type {
175 SlotType::Static => {
176 if !(1..=128).contains(&slot_id) {
177 return Err(ProjectSlotsError::SlotIdOutOfBounds { value: slot_id }.into());
178 }
179 }
180 SlotType::Flex => {
181 if !(1..=128).contains(&slot_id) {
182 return Err(ProjectSlotsError::SlotIdOutOfBounds { value: slot_id }.into());
183 }
184 }
185 }
186
187 if let Some(tempo) = bpm {
189 if !(720..=7200).contains(&tempo) {
190 return Err(ProjectSlotsError::TempoOutOfBounds { value: tempo }.into());
191 }
192 }
193
194 if let Some(amp) = gain {
196 if !(24..=120).contains(&) {
197 return Err(ProjectSlotsError::GainOutOfBounds { value: amp }.into());
198 }
199 }
200
201 Ok(Self {
202 slot_type,
203 slot_id,
204 path,
205 timestrech_mode: timestretch_mode.unwrap_or_default(),
206 loop_mode: loop_mode.unwrap_or_default(),
207 trig_quantization_mode: trig_quantization_mode.unwrap_or_default(),
208 gain: gain.unwrap_or(DEFAULT_GAIN),
209 bpm: bpm.unwrap_or(DEFAULT_TEMPO),
210 })
211 }
212
213 pub fn new_no_opts(
215 slot_type: SlotType,
216 slot_id: u8,
217 path: Option<PathBuf>,
218 ) -> Result<Self, OtToolsIoError> {
219 SlotAttributes::new(slot_type, slot_id, path, None, None, None, None, None)
220 }
221}
222
223#[cfg(test)]
224mod new_slot_attributes {
225 use crate::projects::slots::test_utils;
226 use crate::projects::slots::SlotAttributes;
227 use crate::settings::SlotType;
228 use crate::OtToolsIoError;
229 #[test]
230 fn valid_all_args() -> Result<(), OtToolsIoError> {
231 test_utils::new_slot_attr_full_args()?;
232 Ok(())
233 }
234
235 #[test]
236 fn valid_no_args() -> Result<(), OtToolsIoError> {
237 test_utils::new_slot_attr_minimal_args()?;
238 Ok(())
239 }
240
241 #[test]
242 fn invalid_slot_id_too_low_static() -> Result<(), OtToolsIoError> {
243 let r = SlotAttributes::new(SlotType::Static, 0, None, None, None, None, None, None);
244 assert!(r.is_err());
245 Ok(())
246 }
247
248 #[test]
249 fn invalid_slot_id_too_high_static() -> Result<(), OtToolsIoError> {
250 let r = SlotAttributes::new(SlotType::Static, 129, None, None, None, None, None, None);
251 assert!(r.is_err());
252 Ok(())
253 }
254
255 #[test]
256 fn invalid_slot_id_too_high_flex() -> Result<(), OtToolsIoError> {
257 let r = SlotAttributes::new(SlotType::Flex, 137, None, None, None, None, None, None);
258 assert!(r.is_err());
259 Ok(())
260 }
261
262 #[test]
263 fn invalid_gain_too_high() -> Result<(), OtToolsIoError> {
264 let r = SlotAttributes::new(
265 SlotType::Static,
266 100,
267 None,
268 None,
269 None,
270 None,
271 Some(200),
272 None,
273 );
274 assert!(r.is_err());
275 Ok(())
276 }
277
278 #[test]
279 fn invalid_gain_too_low() -> Result<(), OtToolsIoError> {
280 let r = SlotAttributes::new(SlotType::Static, 100, None, None, None, None, Some(1), None);
281 assert!(r.is_err());
282 Ok(())
283 }
284
285 #[test]
286 fn invalid_tempo_too_high() -> Result<(), OtToolsIoError> {
287 let r = SlotAttributes::new(
288 SlotType::Static,
289 100,
290 None,
291 None,
292 None,
293 None,
294 None,
295 Some(8000),
296 );
297 assert!(r.is_err());
298 Ok(())
299 }
300
301 #[test]
302 fn invalid_tempo_too_low() -> Result<(), OtToolsIoError> {
303 let r = SlotAttributes::new(
304 SlotType::Static,
305 100,
306 None,
307 None,
308 None,
309 None,
310 None,
311 Some(20),
312 );
313 assert!(r.is_err());
314 Ok(())
315 }
316}
317
318fn recording_buffer_defaults(i: usize) -> Option<SlotAttributes> {
320 Some(SlotAttributes {
321 slot_type: SlotType::Flex,
322 slot_id: i as u8 + 129,
324 path: None,
325 timestrech_mode: TimeStretchMode::default(),
326 loop_mode: LoopMode::default(),
327 trig_quantization_mode: TrigQuantizationMode::default(),
328 gain: 72, bpm: DEFAULT_TEMPO,
330 })
331}
332
333impl Default for Slots<Option<SlotAttributes>> {
334 fn default() -> Self {
335 Self {
336 static_slots: PlaybackSlots::<Option<SlotAttributes>>::new(from_fn(|_| None)),
337 flex_slots: PlaybackSlots::<Option<SlotAttributes>>::new(from_fn(|_| None)),
338 recording_buffers: RecordingBufferSlots::<Option<SlotAttributes>>::new(from_fn(
339 recording_buffer_defaults,
340 )),
341 }
342 }
343}