1use crate::error::ConfigError;
2use gilrs::Button;
3use iced::keyboard::KeyCode;
4use serde::{Deserialize};
5use std::collections::HashMap;
6use std::fmt::Debug;
7use std::path::{Path, PathBuf};
8use std::{fs, array};
9
10const KEYBOARD_BUTTON_MAPPING: [(&str, KeyCode); 136] = [
11 ("1", KeyCode::Key1),
12 ("2", KeyCode::Key2),
13 ("3", KeyCode::Key3),
14 ("4", KeyCode::Key4),
15 ("5", KeyCode::Key5),
16 ("6", KeyCode::Key6),
17 ("7", KeyCode::Key7),
18 ("8", KeyCode::Key8),
19 ("9", KeyCode::Key9),
20 ("0", KeyCode::Key0),
21 ("A", KeyCode::A),
22 ("B", KeyCode::B),
23 ("C", KeyCode::C),
24 ("D", KeyCode::D),
25 ("E", KeyCode::E),
26 ("F", KeyCode::F),
27 ("G", KeyCode::G),
28 ("H", KeyCode::H),
29 ("I", KeyCode::I),
30 ("J", KeyCode::J),
31 ("K", KeyCode::K),
32 ("L", KeyCode::L),
33 ("M", KeyCode::M),
34 ("N", KeyCode::N),
35 ("O", KeyCode::O),
36 ("P", KeyCode::P),
37 ("Q", KeyCode::Q),
38 ("R", KeyCode::R),
39 ("S", KeyCode::S),
40 ("T", KeyCode::T),
41 ("U", KeyCode::U),
42 ("V", KeyCode::V),
43 ("W", KeyCode::W),
44 ("X", KeyCode::X),
45 ("Y", KeyCode::Y),
46 ("Z", KeyCode::Z),
47 ("Escape", KeyCode::Escape),
48 ("F1", KeyCode::F1),
49 ("F2", KeyCode::F2),
50 ("F3", KeyCode::F3),
51 ("F4", KeyCode::F4),
52 ("F5", KeyCode::F5),
53 ("F6", KeyCode::F6),
54 ("F7", KeyCode::F7),
55 ("F8", KeyCode::F8),
56 ("F9", KeyCode::F9),
57 ("F10", KeyCode::F10),
58 ("F11", KeyCode::F11),
59 ("F12", KeyCode::F12),
60 ("F13", KeyCode::F13),
61 ("F14", KeyCode::F14),
62 ("F15", KeyCode::F15),
63 ("F16", KeyCode::F16),
64 ("F17", KeyCode::F17),
65 ("F18", KeyCode::F18),
66 ("F19", KeyCode::F19),
67 ("F20", KeyCode::F20),
68 ("F21", KeyCode::F21),
69 ("F22", KeyCode::F22),
70 ("F23", KeyCode::F23),
71 ("F24", KeyCode::F24),
72 ("Scroll", KeyCode::Scroll),
73 ("Pause", KeyCode::Pause),
74 ("Insert", KeyCode::Insert),
75 ("Home", KeyCode::Home),
76 ("Delete", KeyCode::Delete),
77 ("End", KeyCode::End),
78 ("PageDown", KeyCode::PageDown),
79 ("PageUp", KeyCode::PageUp),
80 ("Left", KeyCode::Left),
81 ("Up", KeyCode::Up),
82 ("Right", KeyCode::Right),
83 ("Down", KeyCode::Down),
84 ("Backspace", KeyCode::Backspace),
85 ("Enter", KeyCode::Enter),
86 ("Space", KeyCode::Space),
87 ("Compose", KeyCode::Compose),
88 ("Caret", KeyCode::Caret),
89 ("Numlock", KeyCode::Numlock),
90 ("Numpad0", KeyCode::Numpad0),
91 ("Numpad1", KeyCode::Numpad1),
92 ("Numpad2", KeyCode::Numpad2),
93 ("Numpad3", KeyCode::Numpad3),
94 ("Numpad4", KeyCode::Numpad4),
95 ("Numpad5", KeyCode::Numpad5),
96 ("Numpad6", KeyCode::Numpad6),
97 ("Numpad7", KeyCode::Numpad7),
98 ("Numpad8", KeyCode::Numpad8),
99 ("Numpad9", KeyCode::Numpad9),
100 ("NumpadAdd", KeyCode::NumpadAdd),
101 ("NumpadDivide", KeyCode::NumpadDivide),
102 ("NumpadDecimal", KeyCode::NumpadDecimal),
103 ("NumpadComma", KeyCode::NumpadComma),
104 ("NumpadEnter", KeyCode::NumpadEnter),
105 ("NumpadEquals", KeyCode::NumpadEquals),
106 ("NumpadMultiply", KeyCode::NumpadMultiply),
107 ("NumpadSubtract", KeyCode::NumpadSubtract),
108 ("Apostrophe", KeyCode::Apostrophe),
109 ("Asterisk", KeyCode::Asterisk),
110 ("Backslash", KeyCode::Backslash),
111 ("Capital", KeyCode::Capital),
112 ("Colon", KeyCode::Colon),
113 ("Comma", KeyCode::Comma),
114 ("Convert", KeyCode::Convert),
115 ("Equals", KeyCode::Equals),
116 ("Grave", KeyCode::Grave),
117 ("LAlt", KeyCode::LAlt),
118 ("LBracket", KeyCode::LBracket),
119 ("LControl", KeyCode::LControl),
120 ("LShift", KeyCode::LShift),
121 ("LWin", KeyCode::LWin),
122 ("MediaSelect", KeyCode::MediaSelect),
123 ("MediaStop", KeyCode::MediaStop),
124 ("Minus", KeyCode::Minus),
125 ("Mute", KeyCode::Mute),
126 ("NavigateForward", KeyCode::NavigateForward),
127 ("NavigateBackward", KeyCode::NavigateBackward),
128 ("NextTrack", KeyCode::NextTrack),
129 ("Period", KeyCode::Period),
130 ("PlayPause", KeyCode::PlayPause),
131 ("Plus", KeyCode::Plus),
132 ("PrevTrack", KeyCode::PrevTrack),
133 ("RAlt", KeyCode::RAlt),
134 ("RBracket", KeyCode::RBracket),
135 ("RControl", KeyCode::RControl),
136 ("RShift", KeyCode::RShift),
137 ("RWin", KeyCode::RWin),
138 ("Semicolon", KeyCode::Semicolon),
139 ("Slash", KeyCode::Slash),
140 ("Sleep", KeyCode::Sleep),
141 ("Stop", KeyCode::Stop),
142 ("Sysrq", KeyCode::Sysrq),
143 ("Tab", KeyCode::Tab),
144 ("Underline", KeyCode::Underline),
145 ("VolumeDown", KeyCode::VolumeDown),
146 ("VolumeUp", KeyCode::VolumeUp),
147];
148
149const GAMEPAD_BUTTON_MAPPING: [(&str, Button); 19] = [
150 ("South", Button::South),
151 ("East", Button::East),
152 ("North", Button::North),
153 ("West", Button::West),
154 ("C", Button::C),
155 ("Z", Button::Z),
156 ("LeftTrigger", Button::LeftTrigger),
157 ("LeftTrigger2", Button::LeftTrigger2),
158 ("RightTrigger", Button::RightTrigger),
159 ("RightTrigger2", Button::RightTrigger2),
160 ("Select", Button::Select),
161 ("Start", Button::Start),
162 ("Mode", Button::Mode),
163 ("LeftThumb", Button::LeftThumb),
164 ("RightThumb", Button::RightThumb),
165 ("DPadUp", Button::DPadUp),
166 ("DPadDown", Button::DPadDown),
167 ("DPadLeft", Button::DPadLeft),
168 ("DPadRight", Button::DPadRight),
169];
170
171fn make_keyboard_button_map() -> HashMap<&'static str, KeyCode> {
172 KEYBOARD_BUTTON_MAPPING.iter().cloned().collect()
173}
174
175fn make_valid_keyboard_button_vec() -> Vec<&'static str>{
176 array::IntoIter::new(KEYBOARD_BUTTON_MAPPING)
177 .map(|x| x.0)
178 .collect()
179}
180
181fn make_gamepad_button_map() -> HashMap<&'static str, Button> {
182 GAMEPAD_BUTTON_MAPPING.iter().cloned().collect()
183}
184
185fn make_valid_gamepad_button_vec() -> Vec<&'static str>{
186 array::IntoIter::new(GAMEPAD_BUTTON_MAPPING)
187 .map(|x| x.0)
188 .collect()
189}
190
191type SampleId = String;
195type BankId = String;
196
197#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
198pub struct BankRef {
199 pub bank_index: usize,
200}
201
202#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
203pub struct SampleRef {
204 pub sample_index: usize,
205}
206
207#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
208pub struct BankSampleRef {
209 pub bank: BankRef,
210 pub sample: SampleRef,
211}
212
213#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
214pub struct SwitchRef {
215 pub switch_index: usize,
216}
217
218#[derive(Deserialize, Debug, Clone, PartialEq)]
219#[serde(deny_unknown_fields)]
220pub struct SampleConfig {
221 pub id: SampleId,
222 pub file: PathBuf,
223
224 #[serde(skip)]
227 pub bank_sample_ref: BankSampleRef,
228
229 #[serde(skip)]
231 pub file_resolved: Option<PathBuf>,
232}
233
234#[derive(Deserialize, Debug, Clone, PartialEq)]
235#[serde(deny_unknown_fields)]
236pub struct BankConfig {
237 pub id: BankId,
238 #[serde(default)]
240 pub poly: bool,
241 #[serde(default)]
242 pub samples: Vec<SampleConfig>,
243
244 #[serde(skip)]
247 pub bank_ref: BankRef,
248}
249
250impl BankConfig {
251 pub fn sample(&self, sample_ref: SampleRef) -> &SampleConfig {
252 &self.samples[sample_ref.sample_index]
253 }
254}
255
256#[derive(Deserialize, Debug, Clone, PartialEq)]
257#[serde(deny_unknown_fields)]
258#[serde(rename_all = "camelCase")]
259pub struct Gamepad {
260 pub device_id: Option<usize>,
261
262 pub button: String,
264
265 #[serde(skip)]
268 pub gilrs_button: Button,
269}
270
271#[derive(Deserialize, Debug, Clone, PartialEq)]
272#[serde(deny_unknown_fields)]
273#[serde(rename_all = "camelCase")]
274pub struct SwitchPlay {
275 pub bank: BankId,
276 pub sample: SampleId,
277
278 #[serde(skip)]
281 pub bank_sample_ref: BankSampleRef,
282}
283
284#[derive(Deserialize, Debug, Clone, PartialEq)]
285#[serde(deny_unknown_fields)]
286#[serde(rename_all = "camelCase")]
287pub struct SwitchPlayRandom {
288 pub bank: BankId,
289
290 #[serde(skip)]
293 pub bank_ref: BankRef,
294}
295
296#[derive(Deserialize, Debug, Clone, PartialEq)]
297#[serde(deny_unknown_fields)]
298#[serde(rename_all = "camelCase")]
299pub struct SwitchPlayStep {
300 pub bank: BankId,
301 pub steps: i32,
309
310 #[serde(skip)]
313 pub bank_ref: BankRef,
314}
315
316#[derive(Deserialize, Debug, Clone, PartialEq)]
317#[serde(deny_unknown_fields)]
318#[serde(rename_all = "camelCase")]
319pub struct SwitchConfig {
320 pub title: String,
324 pub key: Option<String>,
326 pub gamepad: Option<Gamepad>,
328
329 #[serde(default)]
333 pub stop_sounds: bool,
334
335 pub play: Option<SwitchPlay>,
337
338 pub play_random: Option<SwitchPlayRandom>,
340
341 pub play_step: Option<SwitchPlayStep>,
343
344 #[serde(skip)]
347 pub switch_ref: SwitchRef,
348
349 #[serde(skip)]
351 pub key_code: Option<KeyCode>,
352}
353
354struct ConfigIdLookup {
355 bank_id_lookup: HashMap<String, BankRef>,
357
358 sample_id_lookup: HashMap<String, HashMap<String, BankSampleRef>>,
360}
361
362impl ConfigIdLookup {
363 fn new(banks: &Vec<BankConfig>) -> Self {
364 let bank_id_lookup = banks
365 .iter()
366 .map(|bank| (bank.id.clone(), bank.bank_ref))
367 .collect();
368
369 let sample_id_lookup = banks
370 .iter()
371 .map(|bank| {
372 let samples = bank.samples
373 .iter()
374 .map(|sample| (sample.id.clone(), sample.bank_sample_ref))
375 .collect();
376
377 (bank.id.clone(), samples)
378 })
379 .collect();
380
381 ConfigIdLookup { bank_id_lookup, sample_id_lookup }
382 }
383
384 fn bank_id_to_ref(&self, bank_id: &str) -> Result<BankRef, ConfigError> {
385 match self.bank_id_lookup.get(bank_id) {
386 Some(bank_ref) => Ok(*bank_ref),
387 None => Err(ConfigError::UnknownBankId {
388 bank: bank_id.to_string(),
389 }),
390 }
391 }
392
393 fn sample_id_to_ref(&self, bank_id: &str, sample_id: &str) -> Result<BankSampleRef, ConfigError> {
394 let result = self.sample_id_lookup
395 .get(bank_id)
396 .map(|map| map.get(sample_id));
397
398 match result {
399 None => Err(ConfigError::UnknownBankId {
400 bank: bank_id.to_string(),
401 }),
402 Some(None) => Err(ConfigError::UnknownSampleId {
403 bank: bank_id.to_string(),
404 sample: sample_id.to_string(),
405 }),
406 Some(Some(bank_sample_ref)) => Ok(*bank_sample_ref),
407 }
408 }
409}
410
411#[derive(Deserialize, Debug, Clone, PartialEq)]
412#[serde(deny_unknown_fields)]
413pub struct Config {
414 pub banks: Vec<BankConfig>,
415 pub switches: Vec<SwitchConfig>,
416
417 #[serde(skip)]
422 pub resolve_path: Option<PathBuf>,
423
424 #[serde(skip)]
425 keyboard_key_to_switch_lookup_table: HashMap<KeyCode, SwitchRef>,
427
428 #[serde(skip)]
429 gamepad_button_to_switch_lookup_table: HashMap<Option<usize>, HashMap<Button, SwitchRef>>,
431
432 #[serde(skip)]
433 sample_to_switch_play: HashMap<BankSampleRef, Vec<SwitchRef>>,
435
436 #[serde(skip)]
437 empty_switch_ref_vec: Vec<SwitchRef>, }
439
440impl Config {
441 pub fn from_string(yaml_string: &str, resolve_path: Option<PathBuf>) -> Result<Config, ConfigError> {
442 let mut config: Config = serde_yaml::from_str(yaml_string)?;
443 config.resolve_path = resolve_path;
444
445 config.resolve_refs()?;
446 config.resolve_bank_paths();
447 config.resolve_gamepad_button_mappings()?;
448 config.resolve_keyboard_key_to_switch_lookup_table()?;
449 config.resolve_gamepad_button_to_switch_lookup_table();
450 config.resolve_sample_to_switch_play_lookup_table();
451
452 Ok(config)
453 }
454
455 pub fn from_file(path: &Path) -> Result<Config, ConfigError> {
456 let content = fs::read_to_string(&path)?;
457
458 let mut resolve_path = path.to_path_buf();
460 resolve_path.pop();
461 Ok(Config::from_string(&content, Some(resolve_path))?)
462 }
463
464 fn resolve_refs(&mut self) -> Result<(), ConfigError> {
465 for (bank_index, bank_config) in &mut self.banks.iter_mut().enumerate() {
466 bank_config.bank_ref.bank_index = bank_index;
467
468 for (sample_index, sample_config) in &mut bank_config.samples.iter_mut().enumerate() {
469 sample_config.bank_sample_ref.bank.bank_index = bank_index;
470 sample_config.bank_sample_ref.sample.sample_index = sample_index;
471 }
472 }
473
474 for (switch_index, switch_config) in &mut self.switches.iter_mut().enumerate() {
475 switch_config.switch_ref.switch_index = switch_index;
476 }
477
478 let lookup = ConfigIdLookup::new(&self.banks);
479
480 for switch_config in &mut self.switches.iter_mut() {
481 if let Some(play) = &mut switch_config.play.as_mut() {
482 play.bank_sample_ref = lookup.sample_id_to_ref(&play.bank, &play.sample)?;
483 }
484
485 if let Some(play) = &mut switch_config.play_random.as_mut() {
486 play.bank_ref = lookup.bank_id_to_ref(&play.bank)?;
487 }
488
489 if let Some(play) = &mut switch_config.play_step.as_mut() {
490 play.bank_ref = lookup.bank_id_to_ref(&play.bank)?;
491 }
492 }
493
494 Ok(())
495 }
496
497 fn resolve_bank_paths(&mut self) {
498 let resolve_path = &self.resolve_path;
499 if resolve_path.is_none() {
500 return;
501 }
502 let resolve_path = resolve_path.as_ref().unwrap();
503
504 for bank_config in &mut self.banks {
505 for sample in &mut bank_config.samples {
506 let mut resolved_file = PathBuf::from(resolve_path);
507 resolved_file.push(&sample.file);
508 sample.file_resolved = Some(resolved_file);
509 }
510 }
511 }
512
513 fn resolve_gamepad_button_mappings(&mut self) -> Result<(), ConfigError> {
514 let mapping = make_gamepad_button_map();
515 for switch_config in &mut self.switches {
516 if let Some(gamepad) = &mut switch_config.gamepad {
517 let gilrs_button = match mapping.get(&gamepad.button.as_str()) {
518 None => {
519 return Err(ConfigError::UnknownGamepadButton {
520 button: String::from(&gamepad.button),
521 allowed_values: make_valid_gamepad_button_vec().join(", "),
522 });
523 },
524 Some(v) => v,
525 };
526 gamepad.gilrs_button = *gilrs_button;
527 }
528 }
529
530 Ok(())
531 }
532
533 fn resolve_keyboard_key_to_switch_lookup_table(&mut self) -> Result<(), ConfigError> {
534 let mapping = make_keyboard_button_map();
535 let mut lookup_table = HashMap::new();
536
537 for switch_config in (&mut self.switches).into_iter() {
538 if let Some(key) = &switch_config.key {
539 match mapping.get(key.as_str()) {
540 Some(key_code) => {
541 switch_config.key_code = Some(*key_code);
542 },
543 None => {
544 return Err(ConfigError::UnknownKeyboardButton {
545 button: key.to_string(),
546 allowed_values: make_valid_keyboard_button_vec().join(", "),
547 });
548 },
549 }
550 }
551 }
552
553 for switch_config in (&self.switches).into_iter() {
554 if let Some(key_code) = &switch_config.key_code {
555 lookup_table.insert(*key_code, switch_config.switch_ref);
556 }
557 }
558
559 self.keyboard_key_to_switch_lookup_table = lookup_table;
560 Ok(())
561 }
562
563 fn resolve_gamepad_button_to_switch_lookup_table(&mut self) {
564 let gamepad_configs = (&self.switches)
565 .into_iter()
566 .filter_map(|switch_config| {
567 match &switch_config.gamepad {
568 Some(gamepad) => Some((gamepad, switch_config.switch_ref)),
569 None => None,
570 }
571 });
572
573 let mut lookup_table = HashMap::new();
574
575 for (gamepad_config, switch_ref) in gamepad_configs {
576 if !lookup_table.contains_key(&gamepad_config.device_id) {
577 lookup_table.insert(gamepad_config.device_id, HashMap::new());
578 }
579
580 let map = lookup_table.get_mut(&gamepad_config.device_id).unwrap();
581 map.insert(gamepad_config.gilrs_button, switch_ref);
582 }
583
584 self.gamepad_button_to_switch_lookup_table = lookup_table;
585 }
586
587 fn resolve_sample_to_switch_play_lookup_table(&mut self) {
588 let mut lookup_table = HashMap::new();
589
590 for switch in &self.switches {
591 if let Some(play) = &switch.play {
592 let list = lookup_table.entry(play.bank_sample_ref).or_insert_with(|| Vec::new());
593 list.push(switch.switch_ref);
594 }
595 }
596
597 self.sample_to_switch_play = lookup_table;
598 }
599
600 pub fn find_switch_for_keyboard_key(&self, key: KeyCode) -> Option<&SwitchConfig> {
601 match self.keyboard_key_to_switch_lookup_table.get(&key) {
602 Some(switch_ref) => Some(&self.switches[switch_ref.switch_index]),
603 None => None,
604 }
605 }
606
607 pub fn find_switch_for_gamepad_button(&self, device_id: usize, button: Button) -> Option<&SwitchConfig> {
608 let switch_ref = match self.gamepad_button_to_switch_lookup_table.get(&Some(device_id)) {
610 Some(map) => map.get(&button),
611 None => None,
612 };
613
614 let switch_ref = match switch_ref {
616 Some(v) => Some(v),
617 None => {
618 match self.gamepad_button_to_switch_lookup_table.get(&None) {
619 Some(map) => map.get(&button),
620 None => None,
621 }
622 }
623 };
624
625 match switch_ref {
626 Some(switch_ref) => Some(&self.switches[switch_ref.switch_index]),
627 None => None,
628 }
629 }
630
631 pub fn find_switch_play_for_sample(&self, bank_sample_ref: BankSampleRef) -> &Vec<SwitchRef> {
632 if let Some(list) = self.sample_to_switch_play.get(&bank_sample_ref) {
633 list
634 }
635 else {
636 &self.empty_switch_ref_vec
637 }
638 }
639
640 pub fn switch(&self, switch_ref: SwitchRef) -> &SwitchConfig {
641 &self.switches[switch_ref.switch_index]
644 }
645
646 pub fn bank(&self, bank_ref: BankRef) -> &BankConfig {
647 &self.banks[bank_ref.bank_index]
648 }
649
650 pub fn sample(&self, bank_sample_ref: BankSampleRef) -> (&BankConfig, &SampleConfig) {
651 let bank = self.bank(bank_sample_ref.bank);
652 let sample = bank.sample(bank_sample_ref.sample);
653 (bank, sample)
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use crate::config::{Config, BankConfig, BankRef, SampleConfig, SampleRef, BankSampleRef, SwitchConfig, SwitchRef, SwitchPlay, SwitchPlayRandom, SwitchPlayStep, Gamepad};
660 use std::path::{PathBuf};
661 use gilrs::Button;
662 use iced::keyboard::KeyCode;
663 use pretty_assertions::{assert_eq};
664 use crate::error::ConfigError;
665
666 fn test_path(extra_parts: &[&str]) -> PathBuf {
667 let path_sep = std::path::MAIN_SEPARATOR.to_string();
668 [path_sep.as_str(), "test", "path"].iter().chain(extra_parts.iter()).collect()
669 }
670
671 #[test]
672 fn successful_config_parsing() {
673 let config_source = r###"
674banks:
675 - id: bankA
676 # poly option is false by default
677 poly: true
678 samples:
679 # Multiple samples
680 - id: foo1
681 file: foo1.mp3
682 - id: foo2
683 file: foo2.wav
684 - id: foo3
685 file: foo3.ogg
686 - id: foo4
687 file: foo4.flac
688
689 - id: bankB
690 # poly option is not set here
691 samples:
692 # sample with id "foo1" also exists in "bankA". They should not interfere
693 - id: foo1
694 file: foo1-bankB.mp3
695
696switches:
697 - title: play option
698 play:
699 bank: bankB
700 sample: foo1
701
702 - title: playRandom option
703 playRandom:
704 bank: bankB
705
706 - title: playStep option
707 playStep:
708 bank: bankA
709 steps: 1
710
711 - title: stopSounds option
712 stopSounds: true
713
714 - title: Only the required fields
715
716 - title: keyboard key
717 key: X
718
719 - title: gamepad button on any device
720 gamepad:
721 button: North
722
723 - title: gamepad button, specific device
724 gamepad:
725 deviceId: 123
726 button: North
727
728 - title: another play option, same actions as the first
729 play:
730 bank: bankB
731 sample: foo1
732"###;
733 let config = Config::from_string(config_source, Some(test_path(&[]))).unwrap();
734 assert_eq!(config, Config {
735 banks: vec![
736 BankConfig {
737 id: "bankA".to_string(),
738 poly: true,
739 samples: vec![
740 SampleConfig {
741 id: "foo1".to_string(),
742 file: PathBuf::from("foo1.mp3"),
743 bank_sample_ref: BankSampleRef {
744 bank: BankRef { bank_index: 0 },
745 sample: SampleRef { sample_index: 0 },
746 },
747 file_resolved: Some(test_path(&["foo1.mp3"])),
748 },
749 SampleConfig {
750 id: "foo2".to_string(),
751 file: PathBuf::from("foo2.wav"),
752 bank_sample_ref: BankSampleRef {
753 bank: BankRef { bank_index: 0 },
754 sample: SampleRef { sample_index: 1 },
755 },
756 file_resolved: Some(test_path(&["foo2.wav"])),
757 },
758 SampleConfig {
759 id: "foo3".to_string(),
760 file: PathBuf::from("foo3.ogg"),
761 bank_sample_ref: BankSampleRef {
762 bank: BankRef { bank_index: 0 },
763 sample: SampleRef { sample_index: 2 },
764 },
765 file_resolved: Some(test_path(&["foo3.ogg"])),
766 },
767 SampleConfig {
768 id: "foo4".to_string(),
769 file: PathBuf::from("foo4.flac"),
770 bank_sample_ref: BankSampleRef {
771 bank: BankRef { bank_index: 0 },
772 sample: SampleRef { sample_index: 3 },
773 },
774 file_resolved: Some(test_path(&["foo4.flac"])),
775 },
776 ],
777 bank_ref: BankRef { bank_index: 0 },
778 },
779 BankConfig {
780 id: "bankB".to_string(),
781 poly: false,
782 samples: vec![
783 SampleConfig {
784 id: "foo1".to_string(),
785 file: PathBuf::from("foo1-bankB.mp3"),
786 bank_sample_ref: BankSampleRef {
787 bank: BankRef { bank_index: 1 },
788 sample: SampleRef { sample_index: 0 },
789 },
790 file_resolved: Some(test_path(&["foo1-bankB.mp3"])),
791 },
792 ],
793 bank_ref: BankRef { bank_index: 1 },
794 },
795 ],
796 switches: vec![
797 SwitchConfig {
798 title: "play option".to_string(),
799 key: None,
800 gamepad: None,
801 stop_sounds: false,
802 play: Some(SwitchPlay {
803 bank: "bankB".to_string(),
804 sample: "foo1".to_string(),
805 bank_sample_ref: BankSampleRef {
806 bank: BankRef { bank_index: 1 },
807 sample: SampleRef { sample_index: 0 },
808 }
809 }),
810 play_random: None,
811 play_step: None,
812 switch_ref: SwitchRef { switch_index: 0 },
813 key_code: None
814 },
815 SwitchConfig {
816 title: "playRandom option".to_string(),
817 key: None,
818 gamepad: None,
819 stop_sounds: false,
820 play: None,
821 play_random: Some(
822 SwitchPlayRandom {
823 bank: "bankB".to_string(),
824 bank_ref: BankRef { bank_index: 1 },
825 },
826 ),
827 play_step: None,
828 switch_ref: SwitchRef { switch_index: 1 },
829 key_code: None,
830 },
831 SwitchConfig {
832 title: "playStep option".to_string(),
833 key: None,
834 gamepad: None,
835 stop_sounds: false,
836 play: None,
837 play_random: None,
838 play_step: Some(
839 SwitchPlayStep {
840 bank: "bankA".to_string(),
841 steps: 1,
842 bank_ref: BankRef { bank_index: 0 },
843 },
844 ),
845 switch_ref: SwitchRef { switch_index: 2 },
846 key_code: None,
847 },
848 SwitchConfig {
849 title: "stopSounds option".to_string(),
850 key: None,
851 gamepad: None,
852 stop_sounds: true,
853 play: None,
854 play_random: None,
855 play_step: None,
856 switch_ref: SwitchRef { switch_index: 3 },
857 key_code: None,
858 },
859 SwitchConfig {
860 title: "Only the required fields".to_string(),
861 key: None,
862 gamepad: None,
863 stop_sounds: false,
864 play: None,
865 play_random: None,
866 play_step: None,
867 switch_ref: SwitchRef { switch_index: 4 },
868 key_code: None,
869 },
870 SwitchConfig {
871 title: "keyboard key".to_string(),
872 key: Some("X".to_string()),
873 gamepad: None,
874 stop_sounds: false,
875 play: None,
876 play_random: None,
877 play_step: None,
878 switch_ref: SwitchRef { switch_index: 5 },
879 key_code: Some(KeyCode::X),
880 },
881 SwitchConfig {
882 title: "gamepad button on any device".to_string(),
883 key: None,
884 gamepad: Some(
885 Gamepad {
886 device_id: None,
887 button: "North".to_string(),
888 gilrs_button: Button::North,
889 },
890 ),
891 stop_sounds: false,
892 play: None,
893 play_random: None,
894 play_step: None,
895 switch_ref: SwitchRef { switch_index: 6 },
896 key_code: None,
897 },
898 SwitchConfig {
899 title: "gamepad button, specific device".to_string(),
900 key: None,
901 gamepad: Some(
902 Gamepad {
903 device_id: Some(123),
904 button: "North".to_string(),
905 gilrs_button: Button::North,
906 },
907 ),
908 stop_sounds: false,
909 play: None,
910 play_random: None,
911 play_step: None,
912 switch_ref: SwitchRef { switch_index: 7 },
913 key_code: None,
914 },
915 SwitchConfig {
916 title: "another play option, same actions as the first".to_string(),
917 key: None,
918 gamepad: None,
919 stop_sounds: false,
920 play: Some(SwitchPlay {
921 bank: "bankB".to_string(),
922 sample: "foo1".to_string(),
923 bank_sample_ref: BankSampleRef {
924 bank: BankRef { bank_index: 1 },
925 sample: SampleRef { sample_index: 0 },
926 }
927 }),
928 play_random: None,
929 play_step: None,
930 switch_ref: SwitchRef { switch_index: 8 },
931 key_code: None
932 },
933 ],
934 resolve_path: Some(test_path(&[])),
935
936 keyboard_key_to_switch_lookup_table: vec![
937 (KeyCode::X, SwitchRef { switch_index: 5 }),
938 ].into_iter().collect(),
939
940 gamepad_button_to_switch_lookup_table: vec![
941 (
942 None,
943 vec![
944 (Button::North, SwitchRef { switch_index: 6 }),
945 ].into_iter().collect(),
946 ),
947 (
948 Some(123),
949 vec![
950 (Button::North, SwitchRef { switch_index: 7 }),
951 ].into_iter().collect(),
952 ),
953 ].into_iter().collect(),
954
955 sample_to_switch_play: vec![
956 (
957 BankSampleRef {
958 bank: BankRef { bank_index: 1 },
959 sample: SampleRef { sample_index: 0 },
960 },
961 vec![
962 SwitchRef { switch_index: 0 },
963 SwitchRef { switch_index: 8 },
964 ],
965 )
966 ].into_iter().collect(),
967 empty_switch_ref_vec: vec![],
968 });
969 }
970
971 #[test]
972 fn find_switch_for_keyboard_key() {
973 let config_source = r###"
974banks: []
975switches:
976 - title: Key a
977 key: A
978
979 # should override the first switch
980 - title: Key a (again)
981 key: A
982
983 - title: Key b
984 key: B
985
986 - title: No key
987"###;
988
989 let config = Config::from_string(config_source, Some(test_path(&[]))).unwrap();
990 assert!(config.find_switch_for_keyboard_key(KeyCode::Z).is_none());
991 assert_eq!(config.find_switch_for_keyboard_key(KeyCode::A).unwrap().title, "Key a (again)");
992 assert_eq!(config.find_switch_for_keyboard_key(KeyCode::B).unwrap().title, "Key b");
993 }
994
995 #[test]
996 fn find_switch_for_gamepad_button() {
997 let config_source = r###"
998banks: []
999switches:
1000 - title: South, no device filter
1001 gamepad:
1002 button: South
1003
1004 # should override the first switch
1005 - title: South, no device filter (again)
1006 gamepad:
1007 button: South
1008
1009 - title: North, device 123
1010 gamepad:
1011 deviceId: 123
1012 button: North
1013
1014 # Should have less priority because it specifies no device
1015 - title: North, no device filter
1016 gamepad:
1017 button: North
1018
1019 # The only entry that specifies West
1020 - title: West, device 123
1021 gamepad:
1022 deviceId: 123
1023 button: West
1024"###;
1025 let config = Config::from_string(config_source, Some(test_path(&[]))).unwrap();
1026
1027 assert!(config.find_switch_for_gamepad_button(10, Button::West).is_none());
1028 assert_eq!(config.find_switch_for_gamepad_button(123, Button::West).unwrap().title, "West, device 123");
1029
1030 assert_eq!(config.find_switch_for_gamepad_button(123, Button::South).unwrap().title, "South, no device filter (again)");
1031
1032 assert_eq!(config.find_switch_for_gamepad_button(123, Button::North).unwrap().title, "North, device 123");
1033 assert_eq!(config.find_switch_for_gamepad_button(456, Button::North).unwrap().title, "North, no device filter");
1034
1035 assert!(config.find_switch_for_gamepad_button(10, Button::Start).is_none());
1037 }
1038
1039 #[test]
1040 fn config_with_yaml_syntax_error() {
1041 let config_source = "bla'[ ";
1042 Config::from_string(config_source, Some(test_path(&[]))).unwrap_err();
1043 }
1044
1045 #[test]
1046 fn config_with_mismatched_type() {
1047 let config_source = r###"
1048switches: this should be an array
1049"###;
1050 let error = Config::from_string(config_source, Some(test_path(&[]))).unwrap_err();
1051 let error_message = format!("{}", error);
1052 assert!(error_message.contains("invalid type: string"));
1053 }
1054
1055 #[test]
1056 fn config_with_invalid_bank_id() {
1057 let config_source = r###"
1058banks:
1059 - id: mybank
1060switches:
1061 - title: invalid bank id
1062 playRandom:
1063 bank: invalid
1064"###;
1065 let error = Config::from_string(config_source, Some(test_path(&[]))).unwrap_err();
1066 match error {
1067 ConfigError::UnknownBankId { bank } => {
1068 assert_eq!(bank.as_str(), "invalid");
1069 }
1070 _ => {
1071 panic!("Expected error to be ConfigError::UnknownBankId");
1072 }
1073 }
1074 }
1075
1076 #[test]
1077 fn config_with_invalid_sample_id() {
1078 let config_source = r###"
1079banks:
1080 - id: mybank
1081 samples:
1082 - id: mysample
1083 file: foo.mp3
1084
1085switches:
1086 - title: invalid bank id
1087 play:
1088 bank: mybank
1089 sample: invalid
1090"###;
1091 let error = Config::from_string(config_source, Some(test_path(&[]))).unwrap_err();
1092 match error {
1093 ConfigError::UnknownSampleId { bank, sample } => {
1094 assert_eq!(bank.as_str(), "mybank");
1095 assert_eq!(sample.as_str(), "invalid");
1096 }
1097 _ => {
1098 panic!("Expected error to be ConfigError::UnknownSampleId");
1099 }
1100 }
1101 }
1102}