1#![warn(missing_docs)]
32
33use std::borrow::Cow;
34
35#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
37#[serde(rename = "multisample")]
38pub struct Multisample<'a> {
39    #[serde(
40        borrow,
41        default,
42        rename = "@name",
43        skip_serializing_if = "str::is_empty"
44    )]
45    name: Cow<'a, str>,
46    #[serde(borrow, default, skip_serializing_if = "str::is_empty")]
47    generator: Cow<'a, str>,
48    #[serde(borrow, default, skip_serializing_if = "str::is_empty")]
49    category: Cow<'a, str>,
50    #[serde(borrow, default, skip_serializing_if = "str::is_empty")]
51    creator: Cow<'a, str>,
52    #[serde(borrow, default, skip_serializing_if = "str::is_empty")]
53    description: Cow<'a, str>,
54    #[serde(borrow, default, skip_serializing_if = "Keywords::is_empty")]
55    keywords: Keywords<'a>,
56    #[serde(borrow, default, rename = "group")]
57    groups: Cow<'a, [Group<'a>]>,
58    #[serde(borrow, default, rename = "sample")]
59    samples: Cow<'a, [Sample<'a>]>,
60}
61
62impl<'a> Multisample<'a> {
63    pub fn to_owned(self) -> Multisample<'static> {
65        Multisample {
66            name: Cow::Owned(self.name.into_owned()),
67            generator: Cow::Owned(self.generator.into_owned()),
68            category: Cow::Owned(self.category.into_owned()),
69            creator: Cow::Owned(self.creator.into_owned()),
70            description: Cow::Owned(self.description.into_owned()),
71            keywords: Keywords {
72                list: self
73                    .keywords
74                    .list
75                    .iter()
76                    .map(|s| Cow::Owned(s.to_string()))
77                    .collect(),
78            },
79            groups: self
80                .groups
81                .iter()
82                .map(|g| Group {
83                    name: Cow::Owned(g.name.to_string()),
84                    color: g.color,
85                })
86                .collect(),
87            samples: self
88                .samples
89                .iter()
90                .map(|s| Sample {
91                    file: Cow::Owned(s.file.to_path_buf()),
92                    ..s.clone()
93                })
94                .collect(),
95        }
96    }
97
98    pub fn with_name(self, name: impl Into<Cow<'a, str>>) -> Self {
100        Self {
101            name: name.into(),
102            ..self
103        }
104    }
105
106    pub fn with_generator(self, generator: impl Into<Cow<'a, str>>) -> Self {
108        Self {
109            generator: generator.into(),
110            ..self
111        }
112    }
113
114    pub fn with_category(self, category: impl Into<Cow<'a, str>>) -> Self {
116        Self {
117            category: category.into(),
118            ..self
119        }
120    }
121
122    pub fn with_creator(self, creator: impl Into<Cow<'a, str>>) -> Self {
124        Self {
125            creator: creator.into(),
126            ..self
127        }
128    }
129
130    pub fn with_description(self, description: impl Into<Cow<'a, str>>) -> Self {
132        Self {
133            description: description.into(),
134            ..self
135        }
136    }
137
138    pub fn with_keywords<S: Into<Cow<'a, str>>>(
140        self,
141        keywords: impl IntoIterator<Item = S>,
142    ) -> Self {
143        Self {
144            keywords: Keywords {
145                list: keywords.into_iter().map(Into::into).collect(),
146            },
147            ..self
148        }
149    }
150
151    pub fn with_groups(self, groups: impl IntoIterator<Item = Group<'a>>) -> Self {
153        Self {
154            groups: groups.into_iter().collect(),
155            ..self
156        }
157    }
158
159    pub fn with_samples(self, samples: impl IntoIterator<Item = Sample<'a>>) -> Self {
161        Self {
162            samples: samples.into_iter().collect(),
163            ..self
164        }
165    }
166
167    pub fn name(&self) -> &str {
169        &self.name
170    }
171
172    pub fn generator(&self) -> &str {
174        &self.generator
175    }
176
177    pub fn category(&self) -> &str {
179        &self.category
180    }
181
182    pub fn creator(&self) -> &str {
184        &self.creator
185    }
186
187    pub fn description(&self) -> &str {
189        &self.description
190    }
191
192    pub fn keywords(&self) -> &[Cow<'a, str>] {
194        &self.keywords.list
195    }
196
197    pub fn groups(&self) -> &[Group] {
199        &self.groups
200    }
201
202    pub fn samples(&self) -> &[Sample] {
204        &self.samples
205    }
206}
207
208#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
209struct Keywords<'a> {
210    #[serde(borrow, default, rename = "keyword")]
211    list: Cow<'a, [Cow<'a, str>]>,
212}
213
214impl Keywords<'_> {
215    fn is_empty(&self) -> bool {
216        self.list.is_empty()
217    }
218}
219
220#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
222pub struct Group<'a> {
223    #[serde(
224        borrow,
225        default,
226        rename = "@name",
227        skip_serializing_if = "str::is_empty"
228    )]
229    name: Cow<'a, str>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    color: Option<Color>,
232}
233
234impl<'a> Group<'a> {
235    pub fn with_name(self, name: impl Into<Cow<'a, str>>) -> Self {
237        Self {
238            name: name.into(),
239            ..self
240        }
241    }
242
243    pub fn with_color(self, color: impl Into<Option<Color>>) -> Self {
245        Self {
246            color: color.into(),
247            ..self
248        }
249    }
250
251    pub fn name(&self) -> &str {
253        &self.name
254    }
255
256    pub fn color(&self) -> Option<Color> {
258        self.color
259    }
260}
261
262pub type Color = [u8; 3];
264
265#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
267pub struct Sample<'a> {
268    #[serde(borrow, rename = "@file")]
269    file: Cow<'a, std::path::Path>,
270    #[serde(rename = "@sample-start", skip_serializing_if = "Option::is_none")]
271    sample_start: Option<f64>,
272    #[serde(rename = "@sample-stop", skip_serializing_if = "Option::is_none")]
273    sample_stop: Option<f64>,
274    #[serde(rename = "@gain", skip_serializing_if = "Option::is_none")]
275    gain: Option<f64>,
276    #[serde(rename = "@group", skip_serializing_if = "Option::is_none")]
277    group: Option<isize>,
278    #[serde(rename = "@parameter-1", skip_serializing_if = "Option::is_none")]
279    parameter_1: Option<f64>,
280    #[serde(rename = "@parameter-2", skip_serializing_if = "Option::is_none")]
281    parameter_2: Option<f64>,
282    #[serde(rename = "@parameter-3", skip_serializing_if = "Option::is_none")]
283    parameter_3: Option<f64>,
284    #[serde(rename = "@reverse", skip_serializing_if = "Option::is_none")]
285    reverse: Option<bool>,
286    #[serde(rename = "@zone-logic", skip_serializing_if = "Option::is_none")]
287    zone_logic: Option<ZoneLogic>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    key: Option<Key>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    velocity: Option<ZoneInfo>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    select: Option<ZoneInfo>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    r#loop: Option<Loop>,
296}
297
298impl<'a> Sample<'a> {
299    pub fn with_file(self, file: impl Into<Cow<'a, std::path::Path>>) -> Self {
301        Self {
302            file: file.into(),
303            ..self
304        }
305    }
306
307    pub fn with_sample_start(self, sample_start: impl Into<Option<f64>>) -> Self {
309        Self {
310            sample_start: sample_start.into(),
311            ..self
312        }
313    }
314
315    pub fn with_sample_stop(self, sample_stop: impl Into<Option<f64>>) -> Self {
317        Self {
318            sample_stop: sample_stop.into(),
319            ..self
320        }
321    }
322
323    pub fn with_gain(self, gain: impl Into<Option<f64>>) -> Self {
325        Self {
326            gain: gain.into(),
327            ..self
328        }
329    }
330
331    pub fn with_group(self, group: impl Into<Option<isize>>) -> Self {
333        Self {
334            group: group.into(),
335            ..self
336        }
337    }
338
339    pub fn with_parameter_1(self, parameter_1: impl Into<Option<f64>>) -> Self {
341        Self {
342            parameter_1: parameter_1.into(),
343            ..self
344        }
345    }
346
347    pub fn with_parameter_2(self, parameter_2: impl Into<Option<f64>>) -> Self {
349        Self {
350            parameter_2: parameter_2.into(),
351            ..self
352        }
353    }
354
355    pub fn with_parameter_3(self, parameter_3: impl Into<Option<f64>>) -> Self {
357        Self {
358            parameter_3: parameter_3.into(),
359            ..self
360        }
361    }
362
363    pub fn with_reverse(self, reverse: impl Into<Option<bool>>) -> Self {
365        Self {
366            reverse: reverse.into(),
367            ..self
368        }
369    }
370
371    pub fn with_zone_logic(self, zone_logic: impl Into<Option<ZoneLogic>>) -> Self {
373        Self {
374            zone_logic: zone_logic.into(),
375            ..self
376        }
377    }
378
379    pub fn with_key(self, key: impl Into<Option<Key>>) -> Self {
381        Self {
382            key: key.into(),
383            ..self
384        }
385    }
386
387    pub fn with_velocity(self, velocity: impl Into<Option<ZoneInfo>>) -> Self {
389        Self {
390            velocity: velocity.into(),
391            ..self
392        }
393    }
394
395    pub fn with_select(self, select: impl Into<Option<ZoneInfo>>) -> Self {
397        Self {
398            select: select.into(),
399            ..self
400        }
401    }
402
403    pub fn with_loop(self, r#loop: impl Into<Option<Loop>>) -> Self {
405        Self {
406            r#loop: r#loop.into(),
407            ..self
408        }
409    }
410
411    pub fn file(&self) -> &std::path::Path {
413        &self.file
414    }
415
416    pub fn sample_start(&self) -> Option<f64> {
418        self.sample_start
419    }
420
421    pub fn sample_stop(&self) -> Option<f64> {
423        self.sample_stop
424    }
425
426    pub fn gain(&self) -> Option<f64> {
428        self.gain
429    }
430
431    pub fn group(&self) -> Option<isize> {
433        self.group
434    }
435
436    pub fn parameter_1(&self) -> Option<f64> {
438        self.parameter_1
439    }
440
441    pub fn parameter_2(&self) -> Option<f64> {
443        self.parameter_2
444    }
445
446    pub fn parameter_3(&self) -> Option<f64> {
448        self.parameter_3
449    }
450
451    pub fn reverse(&self) -> Option<bool> {
453        self.reverse
454    }
455
456    pub fn zone_logic(&self) -> Option<ZoneLogic> {
458        self.zone_logic
459    }
460
461    pub fn key(&self) -> &Option<Key> {
463        &self.key
464    }
465
466    pub fn velocity(&self) -> &Option<ZoneInfo> {
468        &self.velocity
469    }
470
471    pub fn select(&self) -> &Option<ZoneInfo> {
473        &self.select
474    }
475
476    pub fn r#loop(&self) -> &Option<Loop> {
478        &self.r#loop
479    }
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
484#[serde(rename_all = "kebab-case")]
485pub enum ZoneLogic {
486    AlwaysPlay,
488    RoundRobin,
490}
491
492#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
494pub struct Key {
495    #[serde(rename = "@root", default, skip_serializing_if = "Option::is_none")]
496    root: Option<u8>,
497    #[serde(rename = "@track", default, skip_serializing_if = "Option::is_none")]
498    track: Option<f64>,
499    #[serde(rename = "@tune", default, skip_serializing_if = "Option::is_none")]
500    tune: Option<f64>,
501    #[serde(rename = "@low", default, skip_serializing_if = "Option::is_none")]
502    low: Option<u8>,
503    #[serde(rename = "@high", default, skip_serializing_if = "Option::is_none")]
504    high: Option<u8>,
505    #[serde(rename = "@low-fade", default, skip_serializing_if = "Option::is_none")]
506    low_fade: Option<u8>,
507    #[serde(
508        rename = "@high-fade",
509        default,
510        skip_serializing_if = "Option::is_none"
511    )]
512    high_fade: Option<u8>,
513}
514
515impl Key {
516    pub fn with_root(self, root: impl Into<Option<u8>>) -> Self {
518        Self {
519            root: root.into(),
520            ..self
521        }
522    }
523
524    pub fn with_track(self, track: impl Into<Option<f64>>) -> Self {
526        Self {
527            track: track.into(),
528            ..self
529        }
530    }
531
532    pub fn with_tune(self, tune: impl Into<Option<f64>>) -> Self {
534        Self {
535            tune: tune.into(),
536            ..self
537        }
538    }
539
540    pub fn with_low(self, low: impl Into<Option<u8>>) -> Self {
542        Self {
543            low: low.into(),
544            ..self
545        }
546    }
547
548    pub fn with_high(self, high: impl Into<Option<u8>>) -> Self {
550        Self {
551            high: high.into(),
552            ..self
553        }
554    }
555
556    pub fn with_low_fade(self, low_fade: impl Into<Option<u8>>) -> Self {
558        Self {
559            low_fade: low_fade.into(),
560            ..self
561        }
562    }
563
564    pub fn with_high_fade(self, high_fade: impl Into<Option<u8>>) -> Self {
566        Self {
567            high_fade: high_fade.into(),
568            ..self
569        }
570    }
571
572    pub fn root(&self) -> Option<u8> {
574        self.root
575    }
576
577    pub fn track(&self) -> Option<f64> {
579        self.track
580    }
581
582    pub fn tune(&self) -> Option<f64> {
584        self.tune
585    }
586
587    pub fn low(&self) -> Option<u8> {
589        self.low
590    }
591
592    pub fn high(&self) -> Option<u8> {
594        self.high
595    }
596
597    pub fn low_fade(&self) -> Option<u8> {
599        self.low_fade
600    }
601
602    pub fn high_fade(&self) -> Option<u8> {
604        self.high_fade
605    }
606}
607
608#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
610pub struct ZoneInfo {
611    #[serde(rename = "@low", default, skip_serializing_if = "Option::is_none")]
612    low: Option<u8>,
613    #[serde(rename = "@high", default, skip_serializing_if = "Option::is_none")]
614    high: Option<u8>,
615    #[serde(rename = "@low-fade", default, skip_serializing_if = "Option::is_none")]
616    low_fade: Option<u8>,
617    #[serde(
618        rename = "@high-fade",
619        default,
620        skip_serializing_if = "Option::is_none"
621    )]
622    high_fade: Option<u8>,
623}
624
625impl ZoneInfo {
626    pub fn with_low(self, low: impl Into<Option<u8>>) -> Self {
628        Self {
629            low: low.into(),
630            ..self
631        }
632    }
633
634    pub fn with_high(self, high: impl Into<Option<u8>>) -> Self {
636        Self {
637            high: high.into(),
638            ..self
639        }
640    }
641
642    pub fn with_low_fade(self, low_fade: impl Into<Option<u8>>) -> Self {
644        Self {
645            low_fade: low_fade.into(),
646            ..self
647        }
648    }
649
650    pub fn with_high_fade(self, high_fade: impl Into<Option<u8>>) -> Self {
652        Self {
653            high_fade: high_fade.into(),
654            ..self
655        }
656    }
657
658    pub fn low(&self) -> Option<u8> {
660        self.low
661    }
662
663    pub fn high(&self) -> Option<u8> {
665        self.high
666    }
667
668    pub fn low_fade(&self) -> Option<u8> {
670        self.low_fade
671    }
672
673    pub fn high_fade(&self) -> Option<u8> {
675        self.high_fade
676    }
677}
678
679#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
681pub struct Loop {
682    #[serde(rename = "@mode", skip_serializing_if = "Option::is_none")]
683    mode: Option<LoopMode>,
684    #[serde(rename = "@start", skip_serializing_if = "Option::is_none")]
685    start: Option<f64>,
686    #[serde(rename = "@stop", skip_serializing_if = "Option::is_none")]
687    stop: Option<f64>,
688    #[serde(rename = "@fade", skip_serializing_if = "Option::is_none")]
689    fade: Option<f64>,
690}
691
692impl Loop {
693    pub fn with_mode(self, mode: impl Into<Option<LoopMode>>) -> Self {
695        Self {
696            mode: mode.into(),
697            ..self
698        }
699    }
700
701    pub fn with_start(self, start: impl Into<Option<f64>>) -> Self {
703        Self {
704            start: start.into(),
705            ..self
706        }
707    }
708
709    pub fn with_stop(self, stop: impl Into<Option<f64>>) -> Self {
711        Self {
712            stop: stop.into(),
713            ..self
714        }
715    }
716
717    pub fn with_fade(self, fade: impl Into<Option<f64>>) -> Self {
719        Self {
720            fade: fade.into(),
721            ..self
722        }
723    }
724
725    pub fn mode(&self) -> Option<LoopMode> {
727        self.mode
728    }
729
730    pub fn start(&self) -> Option<f64> {
732        self.start
733    }
734
735    pub fn stop(&self) -> Option<f64> {
737        self.stop
738    }
739
740    pub fn fade(&self) -> Option<f64> {
742        self.fade
743    }
744}
745
746#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
748#[serde(rename_all = "kebab-case")]
749pub enum LoopMode {
750    #[default]
752    Off,
753    Loop,
755    PingPong,
757}