1use crate::projects::parse_hashmap_string_value;
30use crate::projects::ProjectParseError;
31use crate::settings::SlotType;
32
33use crate::settings::{InvalidValueError, LoopMode, TimeStretchMode, TrigQuantizationMode};
34
35use itertools::Itertools;
36use ot_tools_io_derive::IsDefaultCheck;
37use serde::de::{self, Deserializer, MapAccess, Visitor};
38use serde::{Deserialize, Serialize};
39use serde_big_array::Array;
40use std::{collections::HashMap, fmt, path::PathBuf, str::FromStr};
41
42#[derive(Serialize, PartialEq, Debug, Clone, Eq, Hash)]
53pub struct SlotAttributes {
54 pub slot_type: SlotType,
56
57 pub slot_id: u8,
61
62 pub path: Option<PathBuf>,
67
68 pub timestrech_mode: TimeStretchMode,
71
72 pub loop_mode: LoopMode,
75
76 pub trig_quantization_mode: TrigQuantizationMode,
80
81 pub gain: u8,
83
84 pub bpm: u16,
90}
91
92#[allow(clippy::too_many_arguments)] impl SlotAttributes {
94 pub fn new(
95 slot_type: SlotType,
96 slot_id: u8,
97 path: Option<PathBuf>,
98 timestretch_mode: Option<TimeStretchMode>,
99 loop_mode: Option<LoopMode>,
100 trig_quantization_mode: Option<TrigQuantizationMode>,
101 gain: Option<u8>,
102 bpm: Option<u16>,
103 ) -> Result<Self, crate::samples::SampleSettingsError> {
104 if let Some(tempo) = bpm {
105 if !(720..=7200).contains(&tempo) {
106 return Err(crate::samples::SampleSettingsError::TempoOutOfBounds {
107 value: tempo as u32,
108 });
109 }
110 }
111 if let Some(amp) = gain {
112 if !(24..=120).contains(&) {
113 return Err(crate::samples::SampleSettingsError::GainOutOfBounds {
114 value: amp as u32,
115 });
116 }
117 }
118
119 Ok(Self {
120 slot_type,
121 slot_id,
122 path,
123 timestrech_mode: timestretch_mode.unwrap_or_default(),
124 loop_mode: loop_mode.unwrap_or_default(),
125 trig_quantization_mode: trig_quantization_mode.unwrap_or_default(),
126 gain: gain.unwrap_or(72),
127 bpm: bpm.unwrap_or(2880),
128 })
129 }
130}
131
132fn parse_id(hmap: &HashMap<String, String>) -> Result<u8, ProjectParseError> {
133 let x = parse_hashmap_string_value::<u8>(hmap, "slot", None)?;
134 Ok(x)
135}
136
137fn parse_loop_mode(hmap: &HashMap<String, String>) -> Result<LoopMode, InvalidValueError> {
138 let default = LoopMode::default() as u8;
139 let default_str = format!["{default}"];
140
141 let x = parse_hashmap_string_value::<u8>(hmap, "loopmode", Some(default_str.as_str()))
142 .unwrap_or(default);
143 LoopMode::try_from(&x)
144}
145
146fn parse_tstrech_mode(
147 hmap: &HashMap<String, String>,
148) -> Result<TimeStretchMode, InvalidValueError> {
149 let default = TimeStretchMode::default() as u8;
150 let default_str = format!["{default}"];
151
152 let x = parse_hashmap_string_value::<u8>(hmap, "tsmode", Some(default_str.as_str()))
153 .unwrap_or(default);
154 TimeStretchMode::try_from(&x)
155}
156
157fn parse_trig_quantize_mode(
158 hmap: &HashMap<String, String>,
159) -> Result<TrigQuantizationMode, InvalidValueError> {
160 let default = TrigQuantizationMode::default() as u8;
161 let default_str = format!["{default}"];
162 let x = parse_hashmap_string_value::<u8>(hmap, "trigquantization", Some(default_str.as_str()))
163 .unwrap_or(default);
164 TrigQuantizationMode::try_from(x)
165}
166
167fn parse_gain(hmap: &HashMap<String, String>) -> Result<u8, ProjectParseError> {
168 let x = parse_hashmap_string_value::<u8>(hmap, "gain", Some("72")).unwrap_or(72_u8);
169 Ok(x)
170}
171
172fn parse_tempo(hmap: &HashMap<String, String>) -> Result<u16, ProjectParseError> {
173 let x = parse_hashmap_string_value::<u16>(hmap, "bpmx24", Some("2880")).unwrap_or(2880_u16);
174 Ok(x)
175}
176
177fn parse_path(hmap: &HashMap<String, String>) -> Result<PathBuf, ProjectParseError> {
178 let path_str = hmap.get("path").ok_or(ProjectParseError::HashMap)?;
179 let path = PathBuf::from_str(path_str).map_err(|_| ProjectParseError::String)?;
180 Ok(path)
181}
182
183impl TryFrom<&HashMap<String, String>> for SlotAttributes {
184 type Error = ProjectParseError;
185 fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
186 let slot_id = parse_id(value)?;
187
188 let sample_slot_type = value
189 .get("type")
190 .ok_or(ProjectParseError::HashMap)?
191 .to_string();
192 let slot_type = SlotType::try_from(sample_slot_type)?;
193
194 let path = parse_path(value)?;
195
196 let loop_mode = parse_loop_mode(value)?;
197 let timestrech_mode = parse_tstrech_mode(value)?;
198 let trig_quantization_mode = parse_trig_quantize_mode(value)?;
199 let gain = parse_gain(value)?;
200 let bpm = parse_tempo(value)?;
201
202 let sample_struct = Self {
203 slot_type,
204 slot_id,
205 path: if path.as_os_str() != "" {
206 Some(path)
207 } else {
208 None
209 },
210 timestrech_mode,
211 loop_mode,
212 trig_quantization_mode,
213 gain,
214 bpm,
215 };
216
217 Ok(sample_struct)
218 }
219}
220
221impl FromStr for SlotAttributes {
222 type Err = ProjectParseError;
223
224 fn from_str(s: &str) -> Result<Self, Self::Err> {
225 let k_v: Vec<Vec<&str>> = s
226 .strip_prefix("\r\n\r\n[SAMPLE]\r\n")
227 .ok_or(ProjectParseError::HashMap)?
228 .strip_suffix("\r\n")
229 .ok_or(ProjectParseError::HashMap)?
230 .split("\r\n")
231 .map(|x: &str| x.split('=').collect_vec())
232 .filter(|x: &Vec<&str>| x.len() == 2)
233 .collect_vec();
234
235 let mut hmap: HashMap<String, String> = HashMap::new();
236 for key_value_pair in k_v {
237 hmap.insert(
238 key_value_pair[0].to_string().to_lowercase(),
239 key_value_pair[1].to_string(),
240 );
241 }
242
243 let sample_struct = SlotAttributes::try_from(&hmap)?;
244 Ok(sample_struct)
245 }
246}
247
248impl fmt::Display for SlotAttributes {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
250 let mut s = "[SAMPLE]\r\n".to_string();
251 s.push_str(&format!("TYPE={}", self.slot_type));
252 s.push_str("\r\n");
253 s.push_str(format!("SLOT={:0>3}", self.slot_id).as_str());
255 s.push_str("\r\n");
256 if let Some(path) = &self.path {
258 s.push_str(
270 format!("PATH={path:#?}")
271 .replace('"', "") .replace("\\", "") .as_str(),
274 );
275 } else {
276 s.push_str("PATH=");
277 }
278 s.push_str("\r\n");
279 s.push_str(format!("BPMx24={}", self.bpm).as_str());
280 s.push_str("\r\n");
281 s.push_str(format!("TSMODE={}", self.timestrech_mode as u8).as_str());
282 s.push_str("\r\n");
283 s.push_str(format!("LOOPMODE={}", self.loop_mode as u8).as_str());
284 s.push_str("\r\n");
285 s.push_str(format!("GAIN={}", self.gain).as_str());
286 s.push_str("\r\n");
287 s.push_str(format!("TRIGQUANTIZATION={}", self.trig_quantization_mode as u8).as_str());
288 s.push_str("\r\n[/SAMPLE]");
289 write!(f, "{s:#}")
290 }
291}
292
293impl<'de> Deserialize<'de> for SlotAttributes {
297 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298 where
299 D: Deserializer<'de>,
300 {
301 enum Field {
302 SlotType,
303 SlotId,
304 Path,
305 Timestretch,
306 Loop,
307 Quant,
308 Gain,
309 Bpm,
310 }
311
312 const FIELDS: &[&str] = &[
314 "slot_type",
315 "slot_id",
316 "path",
317 "timestrech_mode",
318 "loop_mode",
319 "trig_quantization_mode",
320 "gain",
321 "bpm",
322 ];
323
324 impl<'de> Deserialize<'de> for Field {
325 fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
326 where
327 D: Deserializer<'de>,
328 {
329 struct FieldVisitor;
330
331 impl Visitor<'_> for FieldVisitor {
332 type Value = Field;
333
334 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
335 formatter.write_str(
336 FIELDS
337 .iter()
338 .map(|x| format!["`{x}`"])
339 .collect::<Vec<_>>()
340 .join(" or ")
341 .as_str(),
342 )
343 }
344
345 fn visit_str<E>(self, value: &str) -> Result<Field, E>
346 where
347 E: de::Error,
348 {
349 match value {
350 "slot_type" => Ok(Field::SlotType),
351 "slot_id" => Ok(Field::SlotId),
352 "path" => Ok(Field::Path),
353 "timestrech_mode" => Ok(Field::Timestretch),
354 "loop_mode" => Ok(Field::Loop),
355 "trig_quantization_mode" => Ok(Field::Quant),
356 "gain" => Ok(Field::Gain),
357 "bpm" => Ok(Field::Bpm),
358 _ => Err(de::Error::unknown_field(value, FIELDS)),
359 }
360 }
361 }
362
363 deserializer.deserialize_identifier(FieldVisitor)
364 }
365 }
366
367 struct SlotAttributesVisitor;
368
369 impl<'de> Visitor<'de> for SlotAttributesVisitor {
370 type Value = SlotAttributes;
371
372 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
373 formatter.write_str("struct SlotAttributes")
374 }
375
376 fn visit_map<V>(self, mut map: V) -> Result<SlotAttributes, V::Error>
377 where
378 V: MapAccess<'de>,
379 {
380 let mut slot_type = None;
381 let mut slot_id = None;
382 let mut path = None;
383 let mut timestretch_mode = None;
384 let mut loop_mode = None;
385 let mut trig_quantization_mode = None;
386 let mut gain = None;
387 let mut bpm = None;
388
389 while let Some(key) = map.next_key()? {
390 match key {
391 Field::SlotType => {
392 if slot_type.is_some() {
393 return Err(de::Error::duplicate_field("slot_type"));
394 }
395 slot_type = Some(map.next_value::<SlotType>()?);
396 }
397 Field::SlotId => {
398 if slot_id.is_some() {
399 return Err(de::Error::duplicate_field("slot_id"));
400 }
401 slot_id = Some(map.next_value::<u8>()?);
402 }
403 Field::Path => {
404 if path.is_some() {
405 return Err(de::Error::duplicate_field("path"));
406 }
407 path = Some(map.next_value::<PathBuf>()?);
408 }
409 Field::Timestretch => {
410 if timestretch_mode.is_some() {
411 return Err(de::Error::duplicate_field("timestretch_mode"));
412 }
413 timestretch_mode = Some(map.next_value::<TimeStretchMode>()?);
414 }
415 Field::Loop => {
416 if loop_mode.is_some() {
417 return Err(de::Error::duplicate_field("loop_mode"));
418 }
419 loop_mode = Some(map.next_value::<LoopMode>()?);
420 }
421 Field::Quant => {
422 if trig_quantization_mode.is_some() {
423 return Err(de::Error::duplicate_field("trig_quantization_mode"));
424 }
425 trig_quantization_mode =
426 Some(map.next_value::<TrigQuantizationMode>()?);
427 }
428 Field::Gain => {
429 if gain.is_some() {
430 return Err(de::Error::duplicate_field("gain"));
431 }
432 gain = Some(map.next_value::<u8>()?);
433 }
434 Field::Bpm => {
435 if bpm.is_some() {
436 return Err(de::Error::duplicate_field("bpm"));
437 }
438 bpm = Some(map.next_value::<u16>()?);
439 }
440 }
441 }
442
443 let slot = SlotAttributes {
444 slot_type: slot_type.ok_or_else(|| de::Error::missing_field("slot_type"))?,
445 slot_id: slot_id.ok_or_else(|| de::Error::missing_field("slot_type"))?,
446 path, timestrech_mode: timestretch_mode
448 .ok_or_else(|| de::Error::missing_field("trimstretch_mode"))?,
449 loop_mode: loop_mode.ok_or_else(|| de::Error::missing_field("loop_mode"))?,
450 trig_quantization_mode: trig_quantization_mode
451 .ok_or_else(|| de::Error::missing_field("trig_quantization_mode"))?,
452 gain: gain.ok_or_else(|| de::Error::missing_field("gain"))?,
453 bpm: bpm.ok_or_else(|| de::Error::missing_field("bpm"))?,
454 };
455
456 Ok(slot)
457 }
458 }
459
460 deserializer.deserialize_struct("SampleSlot", FIELDS, SlotAttributesVisitor)
461 }
462}
463
464#[derive(Eq, PartialEq, Clone, Debug, Serialize, IsDefaultCheck)]
493pub struct SlotsAttributes {
494 pub static_slots: Array<Option<SlotAttributes>, 128>,
495 pub flex_slots: Array<Option<SlotAttributes>, 136>,
496}
497
498impl<'de> Deserialize<'de> for SlotsAttributes {
502 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
503 where
504 D: Deserializer<'de>,
505 {
506 enum Field {
507 Static,
508 Flex,
509 }
510
511 impl<'de> Deserialize<'de> for Field {
512 fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
513 where
514 D: Deserializer<'de>,
515 {
516 struct FieldVisitor;
517
518 impl Visitor<'_> for FieldVisitor {
519 type Value = Field;
520
521 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
522 formatter.write_str("`static_slots` or `flex_slots`")
523 }
524
525 fn visit_str<E>(self, value: &str) -> Result<Field, E>
526 where
527 E: de::Error,
528 {
529 match value {
530 "static_slots" => Ok(Field::Static),
531 "flex_slots" => Ok(Field::Flex),
532 _ => Err(de::Error::unknown_field(value, FIELDS)),
533 }
534 }
535 }
536
537 deserializer.deserialize_identifier(FieldVisitor)
538 }
539 }
540
541 struct SampleSlotsVisitor;
542
543 impl<'de> Visitor<'de> for SampleSlotsVisitor {
544 type Value = SlotsAttributes;
545
546 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
547 formatter.write_str("struct SampleSlots")
548 }
549
550 fn visit_unit<E>(self) -> Result<SlotsAttributes, E> {
551 Ok(SlotsAttributes::default())
552 }
553 fn visit_map<V>(self, mut map: V) -> Result<SlotsAttributes, V::Error>
554 where
555 V: MapAccess<'de>,
556 {
557 let mut static_slots = None;
558 let mut flex_slots = None;
559 while let Some(key) = map.next_key()? {
560 match key {
561 Field::Static => {
562 if static_slots.is_some() {
563 return Err(de::Error::duplicate_field("static_slots"));
564 }
565 static_slots =
566 Some(map.next_value::<Array<Option<SlotAttributes>, 128>>()?);
567 }
568 Field::Flex => {
569 if flex_slots.is_some() {
570 return Err(de::Error::duplicate_field("flex_slots"));
571 }
572 flex_slots =
573 Some(map.next_value::<Array<Option<SlotAttributes>, 136>>()?);
574 }
575 }
576 }
577 let s_slots =
578 static_slots.ok_or_else(|| de::Error::missing_field("static_slots"))?;
579
580 let f_slots = flex_slots.ok_or_else(|| de::Error::missing_field("flex_slots"))?;
581
582 let slots = SlotsAttributes {
583 static_slots: s_slots,
584 flex_slots: f_slots,
585 };
586
587 Ok(slots)
588 }
589 }
590
591 const FIELDS: &[&str] = &["static_slots", "flex_slots"];
592 deserializer.deserialize_struct("SampleSlots", FIELDS, SampleSlotsVisitor)
593 }
594}
595
596fn flex_slot_default_case_switch(i: usize) -> Option<SlotAttributes> {
598 if i <= 127 {
599 None
600 } else {
601 Some(SlotAttributes {
602 slot_type: SlotType::Flex,
603 slot_id: i as u8 + 1,
605 path: None,
606 timestrech_mode: TimeStretchMode::default(),
607 loop_mode: LoopMode::default(),
608 trig_quantization_mode: TrigQuantizationMode::default(),
609 gain: 72,
610 bpm: 2880,
611 })
612 }
613}
614
615impl Default for SlotsAttributes {
616 fn default() -> Self {
617 Self {
618 static_slots: Array(std::array::from_fn(|_| None)),
619 flex_slots: Array(std::array::from_fn(flex_slot_default_case_switch)),
620 }
621 }
622}
623
624impl FromStr for SlotsAttributes {
625 type Err = ProjectParseError;
626
627 fn from_str(s: &str) -> Result<Self, Self::Err> {
628 let footer_stripped = s
629 .strip_suffix("\r\n\r\n############################\r\n\r\n")
630 .ok_or(ProjectParseError::Footer)?;
631
632 let data_window: Vec<&str> = footer_stripped
633 .split("############################\r\n# Samples\r\n############################")
634 .collect();
635
636 let mut samples_string: Vec<&str> = data_window[1].split("[/SAMPLE]").collect();
637 samples_string.pop();
639
640 let mut slots = Self::default();
642
643 for s in &samples_string {
644 let slot = SlotAttributes::from_str(s)?;
646 let zero_indexed_id = slot.slot_id as usize - 1;
647 match slot.slot_type {
648 SlotType::Static => {
649 slots.static_slots[zero_indexed_id] = Some(slot);
650 }
651 SlotType::Flex => {
652 slots.flex_slots[zero_indexed_id] = Some(slot);
653 }
654 }
655 }
656
657 Ok(slots)
658 }
659}
660
661impl fmt::Display for SlotsAttributes {
662 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
663 let mut string_slots: String = "".to_string();
664
665 let slots = vec![self.static_slots.to_vec(), self.flex_slots.to_vec()];
666
667 let slots_concat = itertools::concat(slots).into_iter().flatten();
668
669 for slot in slots_concat {
670 string_slots.push_str(&slot.to_string());
671 string_slots.push_str("\r\n\r\n");
672 }
673 string_slots = string_slots
674 .strip_suffix("\r\n\r\n")
675 .ok_or(fmt::Error)?
676 .to_string();
677 write!(f, "{string_slots:#}")
678 }
679}
680
681#[cfg(test)]
682#[allow(unused_imports)]
683mod test {
684
685 #[test]
686 fn parse_id_001_correct() {
687 let mut hmap = std::collections::HashMap::new();
688 hmap.insert("slot".to_string(), "001".to_string());
689
690 let slot_id = crate::projects::slots::parse_id(&hmap);
691
692 assert_eq!(1, slot_id.unwrap());
693 }
694
695 #[test]
696 fn parse_id_1_correct() {
697 let mut hmap = std::collections::HashMap::new();
698 hmap.insert("slot".to_string(), "1".to_string());
699
700 let slot_id = crate::projects::slots::parse_id(&hmap);
701
702 assert_eq!(1, slot_id.unwrap());
703 }
704
705 #[test]
706 fn parse_id_127_correct() {
707 let mut hmap = std::collections::HashMap::new();
708 hmap.insert("slot".to_string(), "127".to_string());
709
710 let slot_id = crate::projects::slots::parse_id(&hmap);
711
712 assert_eq!(127, slot_id.unwrap());
713 }
714
715 #[test]
716 fn parse_id_099_correct() {
717 let mut hmap = std::collections::HashMap::new();
718 hmap.insert("slot".to_string(), "099".to_string());
719
720 let slot_id = crate::projects::slots::parse_id(&hmap);
721
722 assert_eq!(99, slot_id.unwrap());
723 }
724
725 #[test]
726 fn parse_id_010_correct() {
727 let mut hmap = std::collections::HashMap::new();
728 hmap.insert("slot".to_string(), "010".to_string());
729
730 let slot_id = crate::projects::slots::parse_id(&hmap);
731
732 assert_eq!(10, slot_id.unwrap());
733 }
734
735 #[test]
736 fn test_parse_id_err_bad_value_type_err() {
737 let mut hmap = std::collections::HashMap::new();
738 hmap.insert("slot".to_string(), "AAAA".to_string());
739 let slot_id = crate::projects::slots::parse_id(&hmap);
740 assert!(slot_id.is_err());
741 }
742
743 #[test]
744 fn test_parse_tempo_correct_default() {
745 let mut hmap = std::collections::HashMap::new();
746 hmap.insert("bpmx24".to_string(), "2880".to_string());
747 let r = crate::projects::slots::parse_tempo(&hmap);
748 assert_eq!(2880_u16, r.unwrap());
749 }
750
751 #[test]
752 fn test_parse_tempo_correct_min() {
753 let mut hmap = std::collections::HashMap::new();
754 hmap.insert("bpmx24".to_string(), "720".to_string());
755 let r = crate::projects::slots::parse_tempo(&hmap);
756 assert_eq!(720_u16, r.unwrap());
757 }
758
759 #[test]
760 fn test_parse_tempo_correct_max() {
761 let mut hmap = std::collections::HashMap::new();
762 hmap.insert("bpmx24".to_string(), "7200".to_string());
763 let r = crate::projects::slots::parse_tempo(&hmap);
764 assert_eq!(7200_u16, r.unwrap());
765 }
766
767 #[test]
768 fn test_parse_tempo_bad_value_type_default_return() {
769 let mut hmap = std::collections::HashMap::new();
770 hmap.insert("bpmx24".to_string(), "AAAFSFSFSSFfssafAA".to_string());
771 let r = crate::projects::slots::parse_tempo(&hmap);
772 assert_eq!(r.unwrap(), 2880_u16);
773 }
774
775 #[test]
776 fn test_parse_gain_correct() {
777 let mut hmap = std::collections::HashMap::new();
778 hmap.insert("gain".to_string(), "72".to_string());
779 let r = crate::projects::slots::parse_gain(&hmap);
780 assert_eq!(72, r.unwrap());
781 }
782
783 #[test]
784 fn test_parse_gain_bad_value_type_default_return() {
785 let mut hmap = std::collections::HashMap::new();
786 hmap.insert("gain".to_string(), "AAAFSFSFSSFfssafAA".to_string());
787 let r = crate::projects::slots::parse_gain(&hmap);
788 assert_eq!(r.unwrap(), 72_u8);
789 }
790
791 #[test]
792 fn test_parse_loop_mode_correct_off() {
793 let mut hmap = std::collections::HashMap::new();
794 hmap.insert("loopmode".to_string(), "0".to_string());
795 let r = crate::projects::slots::parse_loop_mode(&hmap);
796 assert_eq!(r.unwrap(), crate::settings::LoopMode::Off);
797 }
798
799 #[test]
800 fn test_parse_loop_mode_correct_normal() {
801 let mut hmap = std::collections::HashMap::new();
802 hmap.insert("loopmode".to_string(), "1".to_string());
803 let r = crate::projects::slots::parse_loop_mode(&hmap);
804 assert_eq!(r.unwrap(), crate::settings::LoopMode::Normal);
805 }
806
807 #[test]
808 fn test_parse_loop_mode_correct_pingpong() {
809 let mut hmap = std::collections::HashMap::new();
810 hmap.insert("loopmode".to_string(), "2".to_string());
811 let r = crate::projects::slots::parse_loop_mode(&hmap);
812 assert_eq!(r.unwrap(), crate::settings::LoopMode::PingPong);
813 }
814
815 #[test]
816 fn test_parse_loop_mode_bad_value_type_default_return() {
817 let mut hmap = std::collections::HashMap::new();
818 hmap.insert("loopmode".to_string(), "AAAFSFSFSSFfssafAA".to_string());
819 let r = crate::projects::slots::parse_loop_mode(&hmap);
820 assert_eq!(r.unwrap(), crate::settings::LoopMode::default());
821 }
822
823 #[test]
824 fn test_parse_tstretch_correct_off() {
825 let mut hmap = std::collections::HashMap::new();
826 hmap.insert("tsmode".to_string(), "0".to_string());
827 let r = crate::projects::slots::parse_tstrech_mode(&hmap);
828 assert_eq!(crate::settings::TimeStretchMode::Off, r.unwrap());
829 }
830
831 #[test]
832 fn test_parse_tstretch_correct_normal() {
833 let mut hmap = std::collections::HashMap::new();
834 hmap.insert("tsmode".to_string(), "2".to_string());
835 let r = crate::projects::slots::parse_tstrech_mode(&hmap);
836 assert_eq!(crate::settings::TimeStretchMode::Normal, r.unwrap());
837 }
838
839 #[test]
840 fn test_parse_tstretch_correct_beat() {
841 let mut hmap = std::collections::HashMap::new();
842 hmap.insert("tsmode".to_string(), "3".to_string());
843 let r = crate::projects::slots::parse_tstrech_mode(&hmap);
844 assert_eq!(crate::settings::TimeStretchMode::Beat, r.unwrap());
845 }
846
847 #[test]
848 fn test_parse_tstretch_bad_value_type_default_return() {
849 let mut hmap = std::collections::HashMap::new();
850 hmap.insert("tsmode".to_string(), "AAAFSFSFSSFfssafAA".to_string());
851 let r = crate::projects::slots::parse_tstrech_mode(&hmap);
852 assert_eq!(r.unwrap(), crate::settings::TimeStretchMode::default());
853 }
854
855 #[test]
856 fn test_parse_tquantize_correct_off() {
857 let mut hmap = std::collections::HashMap::new();
858 hmap.insert("trigquantization".to_string(), "255".to_string());
859 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
860 assert_eq!(crate::settings::TrigQuantizationMode::Direct, r.unwrap());
861 }
862
863 #[test]
864 fn test_parse_tquantize_correct_direct() {
865 let mut hmap = std::collections::HashMap::new();
866 hmap.insert("trigquantization".to_string(), "0".to_string());
867 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
868 assert_eq!(
869 crate::settings::TrigQuantizationMode::PatternLength,
870 r.unwrap()
871 );
872 }
873
874 #[test]
875 fn test_parse_tquantize_correct_onestep() {
876 let mut hmap = std::collections::HashMap::new();
877 hmap.insert("trigquantization".to_string(), "1".to_string());
878 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
879 assert_eq!(crate::settings::TrigQuantizationMode::OneStep, r.unwrap());
880 }
881
882 #[test]
883 fn test_parse_tquantize_correct_twostep() {
884 let mut hmap = std::collections::HashMap::new();
885 hmap.insert("trigquantization".to_string(), "2".to_string());
886 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
887 assert_eq!(crate::settings::TrigQuantizationMode::TwoSteps, r.unwrap());
888 }
889
890 #[test]
891 fn test_parse_tquantize_correct_threestep() {
892 let mut hmap = std::collections::HashMap::new();
893 hmap.insert("trigquantization".to_string(), "3".to_string());
894 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
895 assert_eq!(
896 crate::settings::TrigQuantizationMode::ThreeSteps,
897 r.unwrap()
898 );
899 }
900
901 #[test]
902 fn test_parse_tquantize_correct_fourstep() {
903 let mut hmap = std::collections::HashMap::new();
904 hmap.insert("trigquantization".to_string(), "4".to_string());
905 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
906 assert_eq!(crate::settings::TrigQuantizationMode::FourSteps, r.unwrap());
907 }
908
909 #[test]
912 fn test_parse_tquantize_bad_value_type_default_return() {
913 let mut hmap = std::collections::HashMap::new();
914 hmap.insert(
915 "trigquantization".to_string(),
916 "AAAFSFSFSSFfssafAA".to_string(),
917 );
918 let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
919 assert_eq!(r.unwrap(), crate::settings::TrigQuantizationMode::default());
920 }
921
922 use std::path::PathBuf;
923 #[test]
924 fn test_parse_path_good_utf8_value() {
925 let test_path = "../AUDIO/some/file.wav";
926
927 let mut hmap = std::collections::HashMap::new();
928 hmap.insert("path".to_string(), test_path.to_string());
929 let r = crate::projects::slots::parse_path(&hmap);
930 assert_eq!(r.unwrap(), PathBuf::from(test_path));
931 }
932
933 #[test]
934 fn test_parse_empty_path() {
935 let test_path = "";
936
937 let mut hmap = std::collections::HashMap::new();
938 hmap.insert("path".to_string(), test_path.to_string());
939 let r = crate::projects::slots::parse_path(&hmap);
940 assert_eq!(r.unwrap(), PathBuf::from(test_path));
941 }
942
943 #[test]
944 fn test_parse_non_utf8_path() {
945 let test_path = "../AUDIO/🇯🇲something.wav";
946
947 let mut hmap = std::collections::HashMap::new();
948 hmap.insert("path".to_string(), test_path.to_string());
949 let r = crate::projects::slots::parse_path(&hmap);
950 assert_eq!(r.unwrap(), PathBuf::from(test_path));
951 }
952}