1use 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 pub sample_type: ProjectSampleSlotType,
45
46 pub slot_id: u8,
50
51 pub path: PathBuf,
53
54 pub trim_bars_x100: u16,
57
58 pub timestrech_mode: SampleAttributeTimestrechMode,
60
61 pub loop_mode: SampleAttributeLoopMode,
63
64 pub trig_quantization_mode: SampleAttributeTrigQuantizationMode,
67
68 pub gain: i8,
70
71 pub bpm: u16,
73}
74
75#[allow(clippy::too_many_arguments)] impl 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 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 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
244impl 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 let sample_slot_type = if slot_id >= 129 {
255 "RECORDER".to_string()
256 } else {
257 hmap.get("type").unwrap().to_string()
259 };
260
261 let sample_type = ProjectSampleSlotType::from_value(&sample_slot_type)?;
262 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 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 fn from_string(data: &str) -> RBoxErr<Vec<Self>> {
293 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 samples_string.pop();
306
307 let samples: Vec<Vec<Vec<&str>>> = samples_string
308 .into_iter()
309 .map(|sample: &str| {
310 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 fn to_string(&self) -> RBoxErr<String> {
345 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 #[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}