Skip to main content

sound_to_patch/preset/
catalogue.rs

1//! Where each control of a synth sits in the agent reference: its layer
2//! and the pattern that groups its instances.
3//!
4//! The reference has one catalogue file per synth layer, plus one file for
5//! controls outside the layers. The layer of a key is a fact of one synth,
6//! so each adapter supplies its own map; this module only dispatches.
7//!
8//! ```
9//! use sound_to_patch::preset::catalogue::{Layer, placement};
10//! use sound_to_patch::synth::Synth;
11//!
12//! let place = placement(Synth::Vital, "osc_2_unison_detune")?;
13//! assert_eq!(place.layer, Some(Layer::Unison));
14//! assert_eq!(place.pattern, "osc_N_unison_detune");
15//! assert_eq!(place.instance.as_deref(), Some("2"));
16//! # Ok::<(), Box<dyn std::error::Error>>(())
17//! ```
18
19use std::fmt;
20
21use crate::preset::vital;
22use crate::synth::{Synth, SynthError};
23
24/// A synth layer, in signal-path order.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub enum Layer {
27    /// Pitch, glide and voicing.
28    Pitch,
29    /// The oscillator source.
30    Oscillator,
31    /// Unison voices and width.
32    Unison,
33    /// The filters.
34    Filter,
35    /// The envelopes.
36    Envelope,
37    /// LFOs, random sources, macros and the modulation matrix.
38    Modulation,
39    /// Distortion.
40    Distortion,
41    /// Space and effects.
42    Space,
43}
44
45impl Layer {
46    /// Every layer, in signal-path order.
47    pub const ALL: [Self; 8] = [
48        Self::Pitch,
49        Self::Oscillator,
50        Self::Unison,
51        Self::Filter,
52        Self::Envelope,
53        Self::Modulation,
54        Self::Distortion,
55        Self::Space,
56    ];
57
58    /// The file stem and text form, such as `oscillator`.
59    #[must_use]
60    pub fn as_str(self) -> &'static str {
61        match self {
62            Self::Pitch => "pitch",
63            Self::Oscillator => "oscillator",
64            Self::Unison => "unison",
65            Self::Filter => "filter",
66            Self::Envelope => "envelope",
67            Self::Modulation => "modulation",
68            Self::Distortion => "distortion",
69            Self::Space => "space",
70        }
71    }
72
73    /// The name for people, such as `Pitch and glide`.
74    #[must_use]
75    pub fn title(self) -> &'static str {
76        match self {
77            Self::Pitch => "Pitch and glide",
78            Self::Oscillator => "Oscillator source",
79            Self::Unison => "Unison and width",
80            Self::Filter => "Filter",
81            Self::Envelope => "Envelopes",
82            Self::Modulation => "Modulation",
83            Self::Distortion => "Distortion",
84            Self::Space => "Space and effects",
85        }
86    }
87}
88
89impl fmt::Display for Layer {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.pad(self.as_str())
92    }
93}
94
95/// Where one key sits in the reference.
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct Placement {
98    /// The layer, or `None` for a control outside the layers, such as the
99    /// tempo or the oversampling.
100    pub layer: Option<Layer>,
101    /// The key with its instance replaced by `N`, such as `osc_N_level`.
102    /// A key with one instance is its own pattern.
103    pub pattern: String,
104    /// The instance the key names, such as `2` in `osc_2_level`.
105    pub instance: Option<String>,
106}
107
108/// Where a key of `synth` sits in the reference.
109///
110/// # Errors
111///
112/// [`SynthError::NotBuilt`] for a synth without an adapter, and
113/// [`SynthError::BrokenData`] for a key the adapter's map does not place.
114pub fn placement(synth: Synth, key: &str) -> Result<Placement, SynthError> {
115    match synth.ready()? {
116        Synth::Vital => vital::placement(key).ok_or_else(|| SynthError::BrokenData {
117            synth,
118            cause: format!("the reference map places no layer for {key:?}"),
119        }),
120        Synth::Serum2 => Err(SynthError::NotBuilt(synth)),
121    }
122}
123
124/// Keys that files saved by some builds of `synth` hold and the registry
125/// does not cover. The reference lists them apart, with no range.
126///
127/// # Errors
128///
129/// [`SynthError::NotBuilt`] for a synth without an adapter.
130pub fn unread_keys(synth: Synth) -> Result<&'static [&'static str], SynthError> {
131    match synth.ready()? {
132        Synth::Vital => Ok(&vital::RETAIL_ONLY_KEYS),
133        Synth::Serum2 => Err(SynthError::NotBuilt(synth)),
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::preset::registry::registry_for;
141
142    #[test]
143    fn every_key_of_a_ready_synth_has_a_place() {
144        for synth in Synth::READY {
145            let registry = registry_for(synth).unwrap();
146            for spec in registry.iter() {
147                let place = placement(synth, &spec.key).unwrap();
148                assert!(!place.pattern.is_empty(), "{}", spec.key);
149            }
150        }
151    }
152
153    #[test]
154    fn serum2_is_not_ready() {
155        assert!(matches!(
156            placement(Synth::Serum2, "a"),
157            Err(SynthError::NotBuilt(Synth::Serum2))
158        ));
159    }
160}