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