Skip to main content

ot_tools_io/projects/slots/
deserialize.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2026 Mike Robeson [dijksterhuis]
4*/
5
6use crate::generics::{PlaybackSlots, RecordingBufferSlots, Slots};
7use crate::projects::SlotAttributes;
8use crate::settings::{LoopMode, SlotType, TimeStretchMode, TrigQuantizationMode};
9use serde::{
10    de::{self, MapAccess, Visitor},
11    Deserialize, Deserializer,
12};
13use std::fmt;
14use std::path::PathBuf;
15
16// YAML/JSON Deserialization for Sample Slot
17// we can get away with just defining map deserialization for YAML/JSON.
18// `ToString`/`FromStr` implementations are currently used for the actual data file.
19impl<'de> Deserialize<'de> for SlotAttributes {
20    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
21    where
22        D: Deserializer<'de>,
23    {
24        enum Field {
25            SlotType,
26            SlotId,
27            Path,
28            Timestretch,
29            Loop,
30            Quant,
31            Gain,
32            Bpm,
33        }
34
35        // TODO: FIELDS_MAP: Tuple array
36        const FIELDS: &[&str] = &[
37            "slot_type",
38            "slot_id",
39            "path",
40            "timestrech_mode",
41            "loop_mode",
42            "trig_quantization_mode",
43            "gain",
44            "bpm",
45        ];
46
47        impl<'de> Deserialize<'de> for Field {
48            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
49            where
50                D: Deserializer<'de>,
51            {
52                struct FieldVisitor;
53
54                impl Visitor<'_> for FieldVisitor {
55                    type Value = Field;
56
57                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
58                        formatter.write_str(
59                            FIELDS
60                                .iter()
61                                .map(|x| format!["`{x}`"])
62                                .collect::<Vec<_>>()
63                                .join(" or ")
64                                .as_str(),
65                        )
66                    }
67
68                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
69                    where
70                        E: de::Error,
71                    {
72                        match value {
73                            "slot_type" => Ok(Field::SlotType),
74                            "slot_id" => Ok(Field::SlotId),
75                            "path" => Ok(Field::Path),
76                            "timestrech_mode" => Ok(Field::Timestretch),
77                            "loop_mode" => Ok(Field::Loop),
78                            "trig_quantization_mode" => Ok(Field::Quant),
79                            "gain" => Ok(Field::Gain),
80                            "bpm" => Ok(Field::Bpm),
81                            _ => Err(de::Error::unknown_field(value, FIELDS)),
82                        }
83                    }
84                }
85
86                deserializer.deserialize_identifier(FieldVisitor)
87            }
88        }
89
90        struct SlotAttributesVisitor;
91
92        impl<'de> Visitor<'de> for SlotAttributesVisitor {
93            type Value = SlotAttributes;
94
95            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
96                formatter.write_str("struct SlotAttributes")
97            }
98
99            fn visit_map<V>(self, mut map: V) -> Result<SlotAttributes, V::Error>
100            where
101                V: MapAccess<'de>,
102            {
103                let mut slot_type = None;
104                let mut slot_id = None;
105                let mut path = None;
106                let mut timestretch_mode = None;
107                let mut loop_mode = None;
108                let mut trig_quantization_mode = None;
109                let mut gain = None;
110                let mut bpm = None;
111
112                while let Some(key) = map.next_key()? {
113                    match key {
114                        Field::SlotType => {
115                            if slot_type.is_some() {
116                                return Err(de::Error::duplicate_field("slot_type"));
117                            }
118                            slot_type = Some(map.next_value::<SlotType>()?);
119                        }
120                        Field::SlotId => {
121                            if slot_id.is_some() {
122                                return Err(de::Error::duplicate_field("slot_id"));
123                            }
124                            slot_id = Some(map.next_value::<u8>()?);
125                        }
126                        Field::Path => {
127                            if path.is_some() {
128                                return Err(de::Error::duplicate_field("path"));
129                            }
130                            path = Some(map.next_value::<PathBuf>()?);
131                        }
132                        Field::Timestretch => {
133                            if timestretch_mode.is_some() {
134                                return Err(de::Error::duplicate_field("timestretch_mode"));
135                            }
136                            timestretch_mode = Some(map.next_value::<TimeStretchMode>()?);
137                        }
138                        Field::Loop => {
139                            if loop_mode.is_some() {
140                                return Err(de::Error::duplicate_field("loop_mode"));
141                            }
142                            loop_mode = Some(map.next_value::<LoopMode>()?);
143                        }
144                        Field::Quant => {
145                            if trig_quantization_mode.is_some() {
146                                return Err(de::Error::duplicate_field("trig_quantization_mode"));
147                            }
148                            trig_quantization_mode =
149                                Some(map.next_value::<TrigQuantizationMode>()?);
150                        }
151                        Field::Gain => {
152                            if gain.is_some() {
153                                return Err(de::Error::duplicate_field("gain"));
154                            }
155                            gain = Some(map.next_value::<u8>()?);
156                        }
157                        Field::Bpm => {
158                            if bpm.is_some() {
159                                return Err(de::Error::duplicate_field("bpm"));
160                            }
161                            bpm = Some(map.next_value::<u16>()?);
162                        }
163                    }
164                }
165
166                let slot = SlotAttributes {
167                    slot_type: slot_type.ok_or_else(|| de::Error::missing_field("slot_type"))?,
168                    slot_id: slot_id.ok_or_else(|| de::Error::missing_field("slot_type"))?,
169                    path, // allowed to be missing to handle recording buffer empty paths
170                    timestrech_mode: timestretch_mode
171                        .ok_or_else(|| de::Error::missing_field("trimstretch_mode"))?,
172                    loop_mode: loop_mode.ok_or_else(|| de::Error::missing_field("loop_mode"))?,
173                    trig_quantization_mode: trig_quantization_mode
174                        .ok_or_else(|| de::Error::missing_field("trig_quantization_mode"))?,
175                    gain: gain.ok_or_else(|| de::Error::missing_field("gain"))?,
176                    bpm: bpm.ok_or_else(|| de::Error::missing_field("bpm"))?,
177                };
178
179                Ok(slot)
180            }
181        }
182
183        deserializer.deserialize_struct("SampleSlot", FIELDS, SlotAttributesVisitor)
184    }
185}
186
187// YAML/JSON Deserialization for Sample Slots
188// we can get away with just defining map deserialization for YAML/JSON.
189// `ToString`/`FromStr` implementations are currently used for the actual data file.
190impl<'de> Deserialize<'de> for Slots<Option<SlotAttributes>> {
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: Deserializer<'de>,
194    {
195        enum Field {
196            Static,
197            Flex,
198            Recording,
199        }
200
201        impl<'de> Deserialize<'de> for Field {
202            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
203            where
204                D: Deserializer<'de>,
205            {
206                struct FieldVisitor;
207
208                impl Visitor<'_> for FieldVisitor {
209                    type Value = Field;
210
211                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
212                        formatter.write_str("`static_slots` or `flex_slots` or `recording_buffers`")
213                    }
214
215                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
216                    where
217                        E: de::Error,
218                    {
219                        match value {
220                            "static_slots" => Ok(Field::Static),
221                            "flex_slots" => Ok(Field::Flex),
222                            "recording_buffers" => Ok(Field::Recording),
223                            _ => Err(de::Error::unknown_field(value, FIELDS)),
224                        }
225                    }
226                }
227
228                deserializer.deserialize_identifier(FieldVisitor)
229            }
230        }
231
232        struct SampleSlotsVisitor;
233
234        impl<'de> Visitor<'de> for SampleSlotsVisitor {
235            type Value = Slots<Option<SlotAttributes>>;
236
237            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
238                formatter.write_str("struct Slots<Option<SlotAttributes>>")
239            }
240
241            fn visit_unit<E>(self) -> Result<Slots<Option<SlotAttributes>>, E> {
242                Ok(Slots::<Option<SlotAttributes>>::default())
243            }
244
245            fn visit_map<V>(self, mut map: V) -> Result<Slots<Option<SlotAttributes>>, V::Error>
246            where
247                V: MapAccess<'de>,
248            {
249                let mut s_slots = None;
250                let mut f_slots = None;
251                let mut r_slots = None;
252
253                while let Some(key) = map.next_key()? {
254                    match key {
255                        Field::Static => {
256                            if s_slots.is_some() {
257                                return Err(de::Error::duplicate_field("static_slots"));
258                            }
259                            s_slots =
260                                Some(map.next_value::<PlaybackSlots<Option<SlotAttributes>>>()?);
261                        }
262                        Field::Flex => {
263                            if f_slots.is_some() {
264                                return Err(de::Error::duplicate_field("flex_slots"));
265                            }
266                            f_slots =
267                                Some(map.next_value::<PlaybackSlots<Option<SlotAttributes>>>()?);
268                        }
269                        Field::Recording => {
270                            if r_slots.is_some() {
271                                return Err(de::Error::duplicate_field("recording_buffers"));
272                            }
273                            r_slots = Some(
274                                map.next_value::<RecordingBufferSlots<Option<SlotAttributes>>>()?,
275                            );
276                        }
277                    }
278                }
279
280                let static_slots =
281                    s_slots.ok_or_else(|| de::Error::missing_field("static_slots"))?;
282
283                let flex_slots = f_slots.ok_or_else(|| de::Error::missing_field("flex_slots"))?;
284
285                let recording_buffers =
286                    r_slots.ok_or_else(|| de::Error::missing_field("recording_buffers"))?;
287
288                Ok(Slots::new(flex_slots, recording_buffers, static_slots))
289            }
290        }
291
292        const FIELDS: &[&str] = &["static_slots", "flex_slots", "recording_buffers"];
293        deserializer.deserialize_struct("SampleSlots", FIELDS, SampleSlotsVisitor)
294    }
295}