ot_tools_io/projects/
slots.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Model and parsing of a project's sample slots.
7//! Used in the [crate::projects::ProjectFile] type.
8
9/*
10Example data:
11[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=001\r\nPATH=../AUDIO/flex.wav\r\nTRIM_BARSx100=173\r\nTSMODE=2\r\nLOOPMODE=1\r\nGAIN=48\r\nTRIGQUANTIZATION=-1\r\n[/SAMPLE]
12-----
13
14[SAMPLE]
15TYPE=FLEX
16SLOT=001
17PATH=../AUDIO/flex.wav
18TRIM_BARSx100=173
19TSMODE=2
20LOOPMODE=1
21GAIN=48
22TRIGQUANTIZATION=-1
23[/SAMPLE]
24*/
25
26use itertools::Itertools;
27use serde::{Deserialize, Serialize};
28use std::{collections::HashMap, convert::TryFrom, path::PathBuf, str::FromStr};
29
30use crate::{
31    projects::{
32        options::ProjectSampleSlotType, parse_hashmap_string_value, FromHashMap, ProjectFromString,
33        ProjectToString,
34    },
35    samples::options::{
36        SampleAttributeLoopMode, SampleAttributeTimestrechMode, SampleAttributeTrigQuantizationMode,
37    },
38    OptionEnumValueConvert, OtToolsIoErrors, RBoxErr,
39};
40
41#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Eq, Hash)]
42pub struct ProjectSampleSlot {
43    /// Type of sample: STATIC or FLEX
44    pub sample_type: ProjectSampleSlotType,
45
46    /// String ID Number of the slot the sample is assigned to e.g. 001, 002, 003...
47    /// Maximum of 128 entries for STATIC sample slots, but can be up to 136 for flex
48    /// slots as there are 8 recorders + 128 flex slots.
49    pub slot_id: u8,
50
51    /// Relative path to the file on the card from the project directory.
52    pub path: PathBuf,
53
54    /// Current bar trim (float). This is multiplied by 100 on the machine.
55    /// This is not used for recording buffer 'flex' tracks.
56    pub trim_bars_x100: u16,
57
58    /// Current `SampleTimestrechModes` setting for the specific slot. Example: `TSMODE=2`
59    pub timestrech_mode: SampleAttributeTimestrechMode,
60
61    /// Current `SampleLoopModes` setting for the specific slot.
62    pub loop_mode: SampleAttributeLoopMode,
63
64    /// Current `SampleTrigQuantizationModes` setting for this specific slot.
65    /// This is not used for recording buffer 'flex' tracks.
66    pub trig_quantization_mode: SampleAttributeTrigQuantizationMode,
67
68    /// Sample gain. 48 is default as per sample attributes file. maximum 96, minimum 0.
69    pub gain: i8,
70
71    /// BPM of the sample in this slot.
72    pub bpm: u16,
73}
74
75#[allow(clippy::too_many_arguments)] // not my fault there's a bunch of inputs for this...
76impl ProjectSampleSlot {
77    pub fn new(
78        sample_type: ProjectSampleSlotType,
79        slot_id: u8,
80        path: PathBuf,
81        trim_bars_x100: Option<u16>,
82        timestretch_mode: Option<SampleAttributeTimestrechMode>,
83        loop_mode: Option<SampleAttributeLoopMode>,
84        trig_quantization_mode: Option<SampleAttributeTrigQuantizationMode>,
85        gain: Option<i8>,
86        bpm: Option<u16>,
87    ) -> RBoxErr<Self> {
88        Ok(ProjectSampleSlot {
89            sample_type,
90            slot_id,
91            path,
92            trim_bars_x100: trim_bars_x100.unwrap_or(0),
93            timestrech_mode: timestretch_mode.unwrap_or_default(),
94            loop_mode: loop_mode.unwrap_or_default(),
95            trig_quantization_mode: trig_quantization_mode.unwrap_or_default(),
96            gain: gain.unwrap_or(24),
97            bpm: bpm.unwrap_or(120),
98        })
99    }
100
101    /// Create a default vector of Project Sample Slots; 8x Recorder Buffers.
102    pub fn defaults() -> Vec<Self> {
103        let mut slots = [
104            ProjectSampleSlot {
105                sample_type: ProjectSampleSlotType::RecorderBuffer,
106                slot_id: 129,
107                path: PathBuf::from(""),
108                trim_bars_x100: 0,
109                timestrech_mode: SampleAttributeTimestrechMode::default(),
110                loop_mode: SampleAttributeLoopMode::default(),
111                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
112                gain: 24,
113                bpm: 120,
114            },
115            ProjectSampleSlot {
116                sample_type: ProjectSampleSlotType::RecorderBuffer,
117                slot_id: 130,
118                path: PathBuf::from(""),
119                trim_bars_x100: 0,
120                timestrech_mode: SampleAttributeTimestrechMode::default(),
121                loop_mode: SampleAttributeLoopMode::default(),
122                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
123                gain: 24,
124                bpm: 120,
125            },
126            ProjectSampleSlot {
127                sample_type: ProjectSampleSlotType::RecorderBuffer,
128                slot_id: 131,
129                path: PathBuf::from(""),
130                trim_bars_x100: 0,
131                timestrech_mode: SampleAttributeTimestrechMode::default(),
132                loop_mode: SampleAttributeLoopMode::default(),
133                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
134                gain: 24,
135                bpm: 120,
136            },
137            ProjectSampleSlot {
138                sample_type: ProjectSampleSlotType::RecorderBuffer,
139                slot_id: 132,
140                path: PathBuf::from(""),
141                trim_bars_x100: 0,
142                timestrech_mode: SampleAttributeTimestrechMode::default(),
143                loop_mode: SampleAttributeLoopMode::default(),
144                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
145                gain: 24,
146                bpm: 120,
147            },
148            ProjectSampleSlot {
149                sample_type: ProjectSampleSlotType::RecorderBuffer,
150                slot_id: 133,
151                path: PathBuf::from(""),
152                trim_bars_x100: 0,
153                timestrech_mode: SampleAttributeTimestrechMode::default(),
154                loop_mode: SampleAttributeLoopMode::default(),
155                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
156                gain: 24,
157                bpm: 120,
158            },
159            ProjectSampleSlot {
160                sample_type: ProjectSampleSlotType::RecorderBuffer,
161                slot_id: 134,
162                path: PathBuf::from(""),
163                trim_bars_x100: 0,
164                timestrech_mode: SampleAttributeTimestrechMode::default(),
165                loop_mode: SampleAttributeLoopMode::default(),
166                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
167                gain: 24,
168                bpm: 120,
169            },
170            ProjectSampleSlot {
171                sample_type: ProjectSampleSlotType::RecorderBuffer,
172                slot_id: 135,
173                path: PathBuf::from(""),
174                trim_bars_x100: 0,
175                timestrech_mode: SampleAttributeTimestrechMode::default(),
176                loop_mode: SampleAttributeLoopMode::default(),
177                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
178                gain: 24,
179                bpm: 120,
180            },
181            ProjectSampleSlot {
182                sample_type: ProjectSampleSlotType::RecorderBuffer,
183                slot_id: 136,
184                path: PathBuf::from(""),
185                trim_bars_x100: 0,
186                timestrech_mode: SampleAttributeTimestrechMode::default(),
187                loop_mode: SampleAttributeLoopMode::default(),
188                trig_quantization_mode: SampleAttributeTrigQuantizationMode::default(),
189                gain: 24,
190                bpm: 120,
191            },
192        ]
193        .to_vec();
194        slots.sort_by_key(|x| x.slot_id);
195        slots
196    }
197}
198
199fn parse_id(hmap: &HashMap<String, String>) -> RBoxErr<u8> {
200    let x = parse_hashmap_string_value::<u8>(hmap, "slot", None);
201
202    // ParseIntError doesn't allow ? usage
203    if x.is_err() {
204        return Err(Box::new(OtToolsIoErrors::ProjectSampleSlotParsingError));
205    }
206
207    Ok(x?)
208}
209
210fn parse_trim_bars(hmap: &HashMap<String, String>) -> RBoxErr<u16> {
211    let x = parse_hashmap_string_value::<u16>(hmap, "trim_barsx100", Some("0")).unwrap_or(0);
212    Ok(x)
213}
214
215fn parse_loop_mode(hmap: &HashMap<String, String>) -> RBoxErr<SampleAttributeLoopMode> {
216    let x = parse_hashmap_string_value::<u32>(hmap, "loopmode", Some("0")).unwrap_or(0_u32);
217    SampleAttributeLoopMode::from_value(&x)
218}
219
220fn parse_tstrech_mode(hmap: &HashMap<String, String>) -> RBoxErr<SampleAttributeTimestrechMode> {
221    let x = parse_hashmap_string_value::<u32>(hmap, "tsmode", Some("0")).unwrap_or(0_u32);
222    SampleAttributeTimestrechMode::from_value(&x)
223}
224
225fn parse_trig_quantize_mode(
226    hmap: &HashMap<String, String>,
227) -> RBoxErr<SampleAttributeTrigQuantizationMode> {
228    let x_i16 =
229        parse_hashmap_string_value::<i16>(hmap, "trigquantization", Some("255")).unwrap_or(255_i16);
230    let x_u32 = u32::try_from(x_i16).unwrap_or(255_u32);
231    SampleAttributeTrigQuantizationMode::from_value(&x_u32)
232}
233
234fn parse_gain(hmap: &HashMap<String, String>) -> RBoxErr<i8> {
235    let x = parse_hashmap_string_value::<i8>(hmap, "gain", Some("48")).unwrap_or(48_i8);
236    Ok(x - 48_i8)
237}
238
239fn parse_tempo(hmap: &HashMap<String, String>) -> RBoxErr<u16> {
240    let x = parse_hashmap_string_value::<u16>(hmap, "bpm", Some("2880")).unwrap_or(2880_u16);
241    Ok(x / 24_u16)
242}
243
244// cannot use FromProjectStringData because it expects a lone Self result, rather than a Vec.
245impl FromHashMap for ProjectSampleSlot {
246    type A = String;
247    type B = String;
248    type T = ProjectSampleSlot;
249
250    fn from_hashmap(hmap: &HashMap<Self::A, Self::B>) -> RBoxErr<Self::T> {
251        let slot_id = parse_id(hmap)?;
252
253        // recorder buffers are the only slots with IDs > 128
254        let sample_slot_type = if slot_id >= 129 {
255            "RECORDER".to_string()
256        } else {
257            // TODO: option plain unwrap
258            hmap.get("type").unwrap().to_string()
259        };
260
261        let sample_type = ProjectSampleSlotType::from_value(&sample_slot_type)?;
262        // TODO: option plain unwrap
263        let path = PathBuf::from_str(hmap.get("path").unwrap())?;
264        let trim_bars = parse_trim_bars(hmap)?;
265        let loop_mode = parse_loop_mode(hmap)?;
266        let timestrech_mode = parse_tstrech_mode(hmap)?;
267        let trig_quantization_mode = parse_trig_quantize_mode(hmap)?;
268        // todo: check gain transformation values
269        let gain = parse_gain(hmap)?;
270        let bpm = parse_tempo(hmap)?;
271
272        let sample_struct = Self {
273            sample_type,
274            slot_id,
275            path,
276            trim_bars_x100: trim_bars,
277            timestrech_mode,
278            loop_mode,
279            trig_quantization_mode,
280            gain,
281            bpm,
282        };
283
284        Ok(sample_struct)
285    }
286}
287
288impl ProjectFromString for ProjectSampleSlot {
289    type T = Vec<Self>;
290
291    /// Load project 'samples' data from the raw project ASCII file.
292    fn from_string(data: &str) -> RBoxErr<Vec<Self>> {
293        // TODO: option plain unwrap
294        let footer_stripped = data
295            .strip_suffix("\r\n\r\n############################\r\n\r\n")
296            .unwrap();
297
298        let data_window: Vec<&str> = footer_stripped
299            .split("############################\r\n# Samples\r\n############################")
300            .collect();
301
302        let mut samples_string: Vec<&str> = data_window[1].split("[/SAMPLE]").collect();
303
304        // last one is always a blank string.
305        samples_string.pop();
306
307        let samples: Vec<Vec<Vec<&str>>> = samples_string
308            .into_iter()
309            .map(|sample: &str| {
310                // TODO: option plain unwraps
311                sample
312                    .strip_prefix("\r\n\r\n[SAMPLE]\r\n")
313                    .unwrap()
314                    .strip_suffix("\r\n")
315                    .unwrap()
316                    .split("\r\n")
317                    .map(|x: &str| x.split('=').collect_vec())
318                    .filter(|x: &Vec<&str>| x.len() == 2)
319                    .collect_vec()
320            })
321            .collect();
322
323        let mut sample_structs: Vec<ProjectSampleSlot> = Vec::new();
324        for sample in samples {
325            let mut hmap: HashMap<String, String> = HashMap::new();
326            for key_value_pair in sample {
327                hmap.insert(
328                    key_value_pair[0].to_string().to_lowercase(),
329                    key_value_pair[1].to_string(),
330                );
331            }
332
333            let sample_struct = Self::from_hashmap(&hmap)?;
334
335            sample_structs.push(sample_struct);
336        }
337
338        Ok(sample_structs)
339    }
340}
341
342impl ProjectToString for ProjectSampleSlot {
343    /// Extract `OctatrackProjectMetadata` fields from the project file's ASCII data
344    fn to_string(&self) -> RBoxErr<String> {
345        // Recording buffers are actually stored as FLEX slots with
346        // a slot ID > 128.
347        let sample_type = match self.sample_type {
348            ProjectSampleSlotType::Static | ProjectSampleSlotType::Flex => {
349                self.sample_type.value()?
350            }
351            ProjectSampleSlotType::RecorderBuffer => "FLEX".to_string(),
352        };
353
354        let mut s = "[SAMPLE]\r\n".to_string();
355        s.push_str(format!("TYPE={}", sample_type).as_str());
356        s.push_str("\r\n");
357        s.push_str(format!("SLOT={}", self.slot_id).as_str());
358        s.push_str("\r\n");
359        s.push_str(format!("PATH={:#?}", self.path).replace('"', "").as_str());
360        s.push_str("\r\n");
361        s.push_str(format!("TRIM_BARSx100={}", self.trim_bars_x100).as_str());
362        s.push_str("\r\n");
363        s.push_str(format!("TSMODE={}", self.timestrech_mode.value()?).as_str());
364        s.push_str("\r\n");
365        s.push_str(format!("LOOPMODE={}", self.loop_mode.value()?).as_str());
366        s.push_str("\r\n");
367        s.push_str(format!("GAIN={}", self.gain + 48).as_str());
368        s.push_str("\r\n");
369        s.push_str(format!("TRIGQUANTIZATION={}", self.trig_quantization_mode.value()?).as_str());
370        s.push_str("\r\n[/SAMPLE]");
371
372        Ok(s)
373    }
374}
375
376#[cfg(test)]
377#[allow(unused_imports)]
378mod test {
379
380    #[test]
381    fn test_parse_id_correct() {
382        let mut hmap = std::collections::HashMap::new();
383        hmap.insert("slot".to_string(), "1".to_string());
384
385        let slot_id = crate::projects::slots::parse_id(&hmap);
386
387        assert_eq!(1, slot_id.unwrap());
388    }
389
390    #[test]
391    fn test_parse_id_err_bad_value_type_err() {
392        let mut hmap = std::collections::HashMap::new();
393        hmap.insert("slot".to_string(), "AAAA".to_string());
394        let slot_id = crate::projects::slots::parse_id(&hmap);
395        assert!(slot_id.is_err());
396    }
397
398    #[test]
399    fn test_parse_tempo_correct_default() {
400        let mut hmap = std::collections::HashMap::new();
401        hmap.insert("bpm".to_string(), "2880".to_string());
402        let r = crate::projects::slots::parse_tempo(&hmap);
403        assert_eq!(120_u16, r.unwrap());
404    }
405
406    #[test]
407    fn test_parse_tempo_correct_min() {
408        let mut hmap = std::collections::HashMap::new();
409        hmap.insert("bpm".to_string(), "720".to_string());
410        let r = crate::projects::slots::parse_tempo(&hmap);
411        assert_eq!(30_u16, r.unwrap());
412    }
413
414    #[test]
415    fn test_parse_tempo_correct_max() {
416        let mut hmap = std::collections::HashMap::new();
417        hmap.insert("bpm".to_string(), "7200".to_string());
418        let r = crate::projects::slots::parse_tempo(&hmap);
419        assert_eq!(300_u16, r.unwrap());
420    }
421
422    #[test]
423    fn test_parse_tempo_bad_value_type_default_return() {
424        let mut hmap = std::collections::HashMap::new();
425        hmap.insert("bpm".to_string(), "AAAFSFSFSSFfssafAA".to_string());
426        let r = crate::projects::slots::parse_tempo(&hmap);
427        assert_eq!(r.unwrap(), 120_u16);
428    }
429
430    #[test]
431    fn test_parse_gain_correct() {
432        let mut hmap = std::collections::HashMap::new();
433        hmap.insert("gain".to_string(), "72".to_string());
434        let r = crate::projects::slots::parse_gain(&hmap);
435        assert_eq!(24_i8, r.unwrap());
436    }
437
438    #[test]
439    fn test_parse_gain_bad_value_type_default_return() {
440        let mut hmap = std::collections::HashMap::new();
441        hmap.insert("gain".to_string(), "AAAFSFSFSSFfssafAA".to_string());
442        let r = crate::projects::slots::parse_gain(&hmap);
443        assert_eq!(r.unwrap(), 0_i8);
444    }
445
446    #[test]
447    fn test_parse_trim_bars_correct() {
448        let mut hmap = std::collections::HashMap::new();
449        hmap.insert("trim_barsx100".to_string(), "100".to_string());
450        let r = crate::projects::slots::parse_trim_bars(&hmap);
451        assert_eq!(100, r.unwrap());
452    }
453
454    #[test]
455    fn test_parse_trim_bars_bad_value_type_default_return() {
456        let mut hmap = std::collections::HashMap::new();
457        hmap.insert(
458            "trim_barsx100".to_string(),
459            "AAAFSFSFSSFfssafAA".to_string(),
460        );
461        let r = crate::projects::slots::parse_trim_bars(&hmap);
462        assert_eq!(r.unwrap(), 0);
463    }
464
465    #[test]
466    fn test_parse_loop_mode_correct_off() {
467        let mut hmap = std::collections::HashMap::new();
468        hmap.insert("loopmode".to_string(), "0".to_string());
469        let r = crate::projects::slots::parse_loop_mode(&hmap);
470        assert_eq!(
471            r.unwrap(),
472            crate::samples::options::SampleAttributeLoopMode::Off
473        );
474    }
475
476    #[test]
477    fn test_parse_loop_mode_correct_normal() {
478        let mut hmap = std::collections::HashMap::new();
479        hmap.insert("loopmode".to_string(), "1".to_string());
480        let r = crate::projects::slots::parse_loop_mode(&hmap);
481        assert_eq!(
482            r.unwrap(),
483            crate::samples::options::SampleAttributeLoopMode::Normal
484        );
485    }
486
487    #[test]
488    fn test_parse_loop_mode_correct_pingpong() {
489        let mut hmap = std::collections::HashMap::new();
490        hmap.insert("loopmode".to_string(), "2".to_string());
491        let r = crate::projects::slots::parse_loop_mode(&hmap);
492        assert_eq!(
493            r.unwrap(),
494            crate::samples::options::SampleAttributeLoopMode::PingPong
495        );
496    }
497
498    #[test]
499    fn test_parse_loop_mode_bad_value_type_default_return() {
500        let mut hmap = std::collections::HashMap::new();
501        hmap.insert("loopmode".to_string(), "AAAFSFSFSSFfssafAA".to_string());
502        let r = crate::projects::slots::parse_loop_mode(&hmap);
503        assert_eq!(
504            r.unwrap(),
505            crate::samples::options::SampleAttributeLoopMode::Off
506        );
507    }
508
509    #[test]
510    fn test_parse_tstretch_correct_off() {
511        let mut hmap = std::collections::HashMap::new();
512        hmap.insert("tsmode".to_string(), "0".to_string());
513        let r = crate::projects::slots::parse_tstrech_mode(&hmap);
514        assert_eq!(
515            crate::samples::options::SampleAttributeTimestrechMode::Off,
516            r.unwrap()
517        );
518    }
519
520    #[test]
521    fn test_parse_tstretch_correct_normal() {
522        let mut hmap = std::collections::HashMap::new();
523        hmap.insert("tsmode".to_string(), "2".to_string());
524        let r = crate::projects::slots::parse_tstrech_mode(&hmap);
525        assert_eq!(
526            crate::samples::options::SampleAttributeTimestrechMode::Normal,
527            r.unwrap()
528        );
529    }
530
531    #[test]
532    fn test_parse_tstretch_correct_beat() {
533        let mut hmap = std::collections::HashMap::new();
534        hmap.insert("tsmode".to_string(), "3".to_string());
535        let r = crate::projects::slots::parse_tstrech_mode(&hmap);
536        assert_eq!(
537            crate::samples::options::SampleAttributeTimestrechMode::Beat,
538            r.unwrap()
539        );
540    }
541
542    #[test]
543    fn test_parse_tstretch_bad_value_type_default_return() {
544        let mut hmap = std::collections::HashMap::new();
545        hmap.insert("tsmode".to_string(), "AAAFSFSFSSFfssafAA".to_string());
546        let r = crate::projects::slots::parse_tstrech_mode(&hmap);
547        assert_eq!(
548            r.unwrap(),
549            crate::samples::options::SampleAttributeTimestrechMode::Off
550        );
551    }
552
553    #[test]
554    fn test_parse_tquantize_correct_off() {
555        let mut hmap = std::collections::HashMap::new();
556        hmap.insert("trigquantization".to_string(), "255".to_string());
557        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
558        assert_eq!(
559            crate::samples::options::SampleAttributeTrigQuantizationMode::Direct,
560            r.unwrap()
561        );
562    }
563
564    #[test]
565    fn test_parse_tquantize_correct_direct() {
566        let mut hmap = std::collections::HashMap::new();
567        hmap.insert("trigquantization".to_string(), "0".to_string());
568        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
569        assert_eq!(
570            crate::samples::options::SampleAttributeTrigQuantizationMode::PatternLength,
571            r.unwrap()
572        );
573    }
574
575    #[test]
576    fn test_parse_tquantize_correct_onestep() {
577        let mut hmap = std::collections::HashMap::new();
578        hmap.insert("trigquantization".to_string(), "1".to_string());
579        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
580        assert_eq!(
581            crate::samples::options::SampleAttributeTrigQuantizationMode::OneStep,
582            r.unwrap()
583        );
584    }
585
586    #[test]
587    fn test_parse_tquantize_correct_twostep() {
588        let mut hmap = std::collections::HashMap::new();
589        hmap.insert("trigquantization".to_string(), "2".to_string());
590        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
591        assert_eq!(
592            crate::samples::options::SampleAttributeTrigQuantizationMode::TwoSteps,
593            r.unwrap()
594        );
595    }
596
597    #[test]
598    fn test_parse_tquantize_correct_threestep() {
599        let mut hmap = std::collections::HashMap::new();
600        hmap.insert("trigquantization".to_string(), "3".to_string());
601        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
602        assert_eq!(
603            crate::samples::options::SampleAttributeTrigQuantizationMode::ThreeSteps,
604            r.unwrap()
605        );
606    }
607
608    #[test]
609    fn test_parse_tquantize_correct_fourstep() {
610        let mut hmap = std::collections::HashMap::new();
611        hmap.insert("trigquantization".to_string(), "4".to_string());
612        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
613        assert_eq!(
614            crate::samples::options::SampleAttributeTrigQuantizationMode::FourSteps,
615            r.unwrap()
616        );
617    }
618
619    // i'm not going to test every single option. we do that already elsewhere.
620
621    #[test]
622    fn test_parse_tquantize_bad_value_type_default_return() {
623        let mut hmap = std::collections::HashMap::new();
624        hmap.insert(
625            "trigquantization".to_string(),
626            "AAAFSFSFSSFfssafAA".to_string(),
627        );
628        let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
629        assert_eq!(
630            r.unwrap(),
631            crate::samples::options::SampleAttributeTrigQuantizationMode::default()
632        );
633    }
634}