Skip to main content

proteus_lib/container/play_settings/
mod.rs

1//! Serde models for `play_settings.json` with versioned decoding.
2
3use log::{info, warn};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6pub mod legacy;
7pub mod v1;
8pub mod v2;
9
10pub use legacy::{PlaySettingsLegacy, PlaySettingsLegacyFile, PlaySettingsTrackLegacy};
11pub use v1::{PlaySettingsV1, PlaySettingsV1File};
12pub use v2::{PlaySettingsV2, PlaySettingsV2File};
13
14/// Legacy algorithmic reverb configuration.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ReverbSettings {
17    pub decay: f32,
18    pub pre_delay: f32,
19    pub mix: f32,
20    pub active: bool,
21}
22
23/// Legacy compressor configuration.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CompressorSettings {
26    pub attack: f32,
27    pub knee: f32,
28    pub ratio: f32,
29    pub release: f32,
30    pub threshold: f32,
31    pub active: bool,
32}
33
34/// Effect settings variants that can appear in the settings file.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub enum EffectSettings {
37    ReverbSettings(ReverbSettings),
38    CompressorSettings(CompressorSettings),
39    ConvolutionReverbSettings(ConvolutionReverbSettings),
40}
41
42/// Track-level configuration shared by newer settings versions.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct SettingsTrack {
45    pub level: f32,
46    pub pan: f32,
47    pub ids: Vec<u32>,
48    pub name: String,
49    pub safe_name: String,
50}
51
52/// Convolution reverb configuration.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ConvolutionReverbSettings {
55    pub impulse_response: Option<String>,
56    pub impulse_response_attachment: Option<String>,
57    pub impulse_response_path: Option<String>,
58    pub impulse_response_tail_db: Option<f32>,
59    pub impulse_response_tail: Option<f32>,
60}
61
62/// Wrapper allowing `play_settings` to be nested or flat.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(untagged)]
65pub enum PlaySettingsContainer<T> {
66    Nested { play_settings: T },
67    Flat(T),
68}
69
70impl<T> PlaySettingsContainer<T> {
71    /// Return the inner settings payload, regardless of nesting.
72    pub fn inner(&self) -> &T {
73        match self {
74            PlaySettingsContainer::Nested { play_settings } => play_settings,
75            PlaySettingsContainer::Flat(inner) => inner,
76        }
77    }
78}
79
80/// Versioned settings file representation.
81#[derive(Debug, Clone)]
82pub enum PlaySettingsFile {
83    Legacy(PlaySettingsLegacyFile),
84    V1(PlaySettingsV1File),
85    V2(PlaySettingsV2File),
86    Unknown {
87        encoder_version: Option<String>,
88        raw: serde_json::Value,
89    },
90}
91
92impl PlaySettingsFile {
93    /// Get the encoder version string, if known.
94    pub fn encoder_version(&self) -> Option<&str> {
95        match self {
96            PlaySettingsFile::Legacy(_) => None,
97            PlaySettingsFile::V1(_) => Some("1"),
98            PlaySettingsFile::V2(_) => Some("2"),
99            PlaySettingsFile::Unknown {
100                encoder_version, ..
101            } => encoder_version.as_deref(),
102        }
103    }
104}
105
106impl<'de> Deserialize<'de> for PlaySettingsFile {
107    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108    where
109        D: Deserializer<'de>,
110    {
111        let value = serde_json::Value::deserialize(deserializer)?;
112        let encoder_version = value.get("encoder_version").and_then(|raw| match raw {
113            serde_json::Value::String(version) => Some(version.clone()),
114            serde_json::Value::Number(number) => number
115                .as_f64()
116                .map(|val| {
117                    if (val - 1.0).abs() < f64::EPSILON {
118                        "1".to_string()
119                    } else if (val - 2.0).abs() < f64::EPSILON {
120                        "2".to_string()
121                    } else {
122                        number.to_string()
123                    }
124                })
125                .or_else(|| Some(number.to_string())),
126            _ => None,
127        });
128
129        info!("Encoder version: {:?}", encoder_version);
130
131        let parsed = match encoder_version.as_deref() {
132            None => serde_json::from_value::<PlaySettingsLegacyFile>(value.clone())
133                .map(PlaySettingsFile::Legacy),
134            Some("1") => serde_json::from_value::<PlaySettingsV1File>(value.clone())
135                .map(PlaySettingsFile::V1),
136            Some("2") => serde_json::from_value::<PlaySettingsV2File>(value.clone())
137                .map(PlaySettingsFile::V2),
138            Some(version) => {
139                warn!("Unknown encoder version: {:?}", version);
140                return Ok(PlaySettingsFile::Unknown {
141                    encoder_version,
142                    raw: value,
143                });
144            }
145        };
146
147        parsed.or_else(|_| {
148            Ok(PlaySettingsFile::Unknown {
149                encoder_version,
150                raw: value,
151            })
152        })
153    }
154}
155
156impl Serialize for PlaySettingsFile {
157    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
158    where
159        S: Serializer,
160    {
161        fn with_version<T, S>(payload: &T, version: &str, serializer: S) -> Result<S::Ok, S::Error>
162        where
163            T: Serialize,
164            S: Serializer,
165        {
166            let mut value =
167                serde_json::to_value(payload).map_err(serde::ser::Error::custom)?;
168            match value {
169                serde_json::Value::Object(ref mut map) => {
170                    map.insert(
171                        "encoder_version".to_string(),
172                        serde_json::Value::String(version.to_string()),
173                    );
174                }
175                other => {
176                    let mut map = serde_json::Map::new();
177                    map.insert(
178                        "encoder_version".to_string(),
179                        serde_json::Value::String(version.to_string()),
180                    );
181                    map.insert("play_settings".to_string(), other);
182                    value = serde_json::Value::Object(map);
183                }
184            }
185            value.serialize(serializer)
186        }
187
188        match self {
189            PlaySettingsFile::Legacy(file) => file.serialize(serializer),
190            PlaySettingsFile::V1(file) => with_version(file, "1", serializer),
191            PlaySettingsFile::V2(file) => with_version(file, "2", serializer),
192            PlaySettingsFile::Unknown { raw, .. } => raw.serialize(serializer),
193        }
194    }
195}