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