Skip to main content

sound_to_patch/preset/
format.rs

1//! The preset file of a synth behind two traits: [`PresetFormat`] reads and
2//! writes the bytes, and [`PresetModel`] is the decoded preset.
3//!
4//! The commands and the edit applier use only these traits. Container,
5//! number and hash rules stay inside each adapter. A model keeps every key
6//! it read, including keys the registry does not know, so a rewrite loses
7//! no feature of a newer build.
8
9use std::any::Any;
10use std::error::Error;
11use std::fmt::{self, Write as _};
12
13use crate::preset::template::TemplateSource;
14use crate::synth::{Synth, SynthError};
15
16/// The file format of one synth.
17pub trait PresetFormat: Send + Sync {
18    /// The synth the format serves.
19    fn synth(&self) -> Synth;
20
21    /// The file extension without the dot.
22    fn extension(&self) -> &'static str;
23
24    /// The largest input [`PresetFormat::read`] accepts, in bytes.
25    fn max_bytes(&self) -> usize;
26
27    /// Decodes a preset.
28    ///
29    /// # Errors
30    ///
31    /// A [`FormatError`] naming the key or offset that fails.
32    fn read(&self, bytes: &[u8]) -> Result<Box<dyn PresetModel>, FormatError>;
33
34    /// Decodes only the header fields, without the large parts.
35    ///
36    /// # Errors
37    ///
38    /// A [`FormatError`] as [`PresetFormat::read`] gives it.
39    fn read_header(&self, bytes: &[u8]) -> Result<Header, FormatError>;
40
41    /// Encodes a model of this synth.
42    ///
43    /// # Errors
44    ///
45    /// A [`FormatError`] when the model belongs to another synth, holds a
46    /// value the format cannot store or lacks a part the synth needs to
47    /// load the file.
48    fn write(&self, model: &dyn PresetModel) -> Result<Vec<u8>, FormatError>;
49
50    /// Whether a missing key and an explicit default sound the same.
51    fn absent_means_default(&self, key: &str) -> bool;
52
53    /// Where the template of this synth comes from.
54    fn template(&self) -> &dyn TemplateSource;
55
56    /// The names a modulation source may take.
57    fn route_sources(&self) -> &'static [&'static str];
58
59    /// The polarity of a route whose edit names none.
60    fn default_polarity(&self, source: &str) -> RoutePolarity;
61
62    /// Checks a stored value that the registry range allows but the writer
63    /// cannot write yet.
64    ///
65    /// # Errors
66    ///
67    /// A [`FormatError`] naming the key, the cause and the fix.
68    fn check_value(&self, key: &str, stored: f64) -> Result<(), FormatError>;
69
70    /// Scans a model after an edit list: every control holds a value of the
71    /// right wire type inside its range, and the structured parts are
72    /// whole. Returns warnings, such as a route into a switched-off module.
73    ///
74    /// # Errors
75    ///
76    /// A [`FormatError`] for a value the synth would refuse or misread.
77    fn scan(&self, model: &dyn PresetModel, edited: &[&str]) -> Result<Vec<String>, FormatError>;
78
79    /// The trailing folder names of the synth's factory preset folder,
80    /// outermost first, such as the data folder and then `Factory`. The
81    /// writer refuses to write into it. Empty when the synth has none.
82    fn factory_folder(&self) -> &'static [&'static str];
83}
84
85/// A decoded preset.
86pub trait PresetModel: fmt::Debug + Send + Sync {
87    /// The header fields.
88    fn header(&self) -> Header;
89
90    /// The value of a control as the file holds it, or `None` when the key
91    /// is absent. An explicit default is `Some`.
92    fn get(&self, key: &str) -> Option<Stored>;
93
94    /// Sets a control.
95    ///
96    /// # Errors
97    ///
98    /// A [`FormatError`] when the format cannot hold the value there. The
99    /// model is unchanged then.
100    fn set(&mut self, key: &str, value: Stored) -> Result<(), FormatError>;
101
102    /// Removes a control, or writes its default where the format needs one.
103    fn reset(&mut self, key: &str);
104
105    /// The modulation slots in order. An empty slot is `None`.
106    fn routes(&self) -> Vec<Option<Route>>;
107
108    /// Sets or clears a modulation slot.
109    ///
110    /// # Errors
111    ///
112    /// A [`FormatError`] when the slot does not exist or the route does not
113    /// fit the format. The model is unchanged then.
114    fn set_route(&mut self, slot: usize, route: Option<Route>) -> Result<(), FormatError>;
115
116    /// Sets the preset name.
117    ///
118    /// # Errors
119    ///
120    /// A [`FormatError`] when the format cannot hold the text.
121    fn set_name(&mut self, name: &str) -> Result<(), FormatError>;
122
123    /// Sets the author.
124    ///
125    /// # Errors
126    ///
127    /// A [`FormatError`] when the format cannot hold the text.
128    fn set_author(&mut self, author: &str) -> Result<(), FormatError>;
129
130    /// Sets the description.
131    ///
132    /// # Errors
133    ///
134    /// A [`FormatError`] when the format cannot hold the text.
135    fn set_description(&mut self, description: &str) -> Result<(), FormatError>;
136
137    /// Sets the tags or the style.
138    ///
139    /// # Errors
140    ///
141    /// A [`FormatError`] naming the tags the format accepts.
142    fn set_tags(&mut self, tags: &[String]) -> Result<(), FormatError>;
143
144    /// The ordered tree that `preset inspect` prints.
145    fn describe(&self, opts: &DescribeOptions) -> Description;
146
147    /// Keys in the file that the registry does not cover, as paths such as
148    /// `settings.custom_warps`. They stay in the model and survive a write.
149    fn unknown_keys(&self) -> Vec<String>;
150
151    /// The model as [`Any`], so its format can take it back for writing.
152    fn as_any(&self) -> &dyn Any;
153}
154
155/// A value as the file stores it.
156#[derive(Clone, Debug, PartialEq)]
157pub enum Stored {
158    /// A real number.
159    Float(f64),
160    /// An integer.
161    Int(i64),
162    /// A flag.
163    Flag(bool),
164    /// A text.
165    Text(String),
166}
167
168impl fmt::Display for Stored {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            Self::Float(value) => write!(f, "{value:?}"),
172            Self::Int(value) => write!(f, "{value}"),
173            Self::Flag(value) => write!(f, "{value}"),
174            Self::Text(value) => write!(f, "{value:?}"),
175        }
176    }
177}
178
179/// The direction of a modulation source.
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
181pub enum RoutePolarity {
182    /// The source moves the destination one way from its value.
183    Unipolar,
184    /// The source moves the destination both ways around its value.
185    Bipolar,
186}
187
188/// One modulation slot.
189#[derive(Clone, Debug, PartialEq)]
190pub struct Route {
191    /// The source name as the file stores it.
192    pub source: String,
193    /// The destination key as the file stores it.
194    pub destination: String,
195    /// The amount as the file stores it.
196    pub amount: f64,
197    /// The polarity of the source.
198    pub polarity: RoutePolarity,
199    /// Further fields of the slot that one synth has, by key.
200    pub extras: Vec<(String, Stored)>,
201}
202
203/// How a header field reads.
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
205pub enum Status {
206    /// As expected.
207    Ok,
208    /// Worth a look.
209    Warn,
210    /// The synth may refuse the file.
211    Bad,
212}
213
214/// One line of the header.
215#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct HeaderField {
217    /// What the field is.
218    pub label: String,
219    /// Its value as text.
220    pub value: String,
221    /// How it reads, when it can be judged.
222    pub status: Option<Status>,
223}
224
225/// The header fields of a preset, in print order. The first field is
226/// always `build`: the build stamp the file carries.
227#[derive(Clone, Debug, Default, PartialEq, Eq)]
228pub struct Header {
229    /// The fields.
230    pub fields: Vec<HeaderField>,
231}
232
233impl Header {
234    /// Adds a field.
235    pub fn push(&mut self, label: &str, value: impl Into<String>, status: Option<Status>) {
236        self.fields.push(HeaderField {
237            label: label.to_owned(),
238            value: value.into(),
239            status,
240        });
241    }
242}
243
244impl fmt::Display for Header {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        let width = self
247            .fields
248            .iter()
249            .map(|field| field.label.len())
250            .max()
251            .unwrap_or(0);
252        for field in &self.fields {
253            write!(f, "{:width$}  {}", field.label, field.value)?;
254            match field.status {
255                Some(Status::Warn) => write!(f, "  [warn]")?,
256                Some(Status::Bad) => write!(f, "  [bad]")?,
257                Some(Status::Ok) | None => {}
258            }
259            writeln!(f)?;
260        }
261        Ok(())
262    }
263}
264
265/// How much of a long list or text [`PresetModel::describe`] shows.
266#[derive(Clone, Copy, Debug, PartialEq, Eq)]
267pub struct DescribeOptions {
268    /// Show every element and every character.
269    pub full: bool,
270    /// Elements of a list shown when `full` is false.
271    pub shown: usize,
272}
273
274impl Default for DescribeOptions {
275    fn default() -> Self {
276        Self {
277            full: false,
278            shown: 3,
279        }
280    }
281}
282
283/// The longest text shown in full when `full` is false.
284pub const SHOWN_TEXT_CHARS: usize = 64;
285
286/// A node of the describe tree.
287#[derive(Clone, Debug, PartialEq)]
288pub enum Node {
289    /// Named children in file order.
290    Map(Vec<(String, Node)>),
291    /// A list with its full length and the elements shown.
292    List {
293        /// Elements in the file.
294        len: usize,
295        /// The first elements, or all of them.
296        items: Vec<Node>,
297    },
298    /// A value as text, with an optional mark such as `out of range`.
299    Scalar {
300        /// The value as text.
301        text: String,
302        /// A warning about the value.
303        mark: Option<String>,
304    },
305}
306
307impl Node {
308    /// A scalar without a mark.
309    #[must_use]
310    pub fn scalar(text: impl Into<String>) -> Self {
311        Self::Scalar {
312            text: text.into(),
313            mark: None,
314        }
315    }
316}
317
318/// The tree that `preset inspect` prints under the header.
319#[derive(Clone, Debug, PartialEq)]
320pub struct Description {
321    /// The top-level node, a map for every current format.
322    pub root: Node,
323}
324
325impl fmt::Display for Description {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        let mut out = String::new();
328        render(&self.root, 0, &mut out)?;
329        f.write_str(&out)
330    }
331}
332
333fn render(node: &Node, depth: usize, out: &mut String) -> fmt::Result {
334    let indent = "  ".repeat(depth);
335    match node {
336        Node::Map(children) => {
337            for (key, child) in children {
338                write!(out, "{indent}{key}:")?;
339                render_value(child, depth, out)?;
340            }
341        }
342        Node::List { items, len } => {
343            for item in items {
344                write!(out, "{indent}-")?;
345                render_value(item, depth, out)?;
346            }
347            if *len > items.len() {
348                writeln!(out, "{indent}- ... {} more", len - items.len())?;
349            }
350        }
351        Node::Scalar { text, mark } => {
352            write!(out, "{indent}{text}")?;
353            if let Some(mark) = mark {
354                write!(out, "  ({mark})")?;
355            }
356            writeln!(out)?;
357        }
358    }
359    Ok(())
360}
361
362/// Writes what follows a `key:` or `-` on its line and below.
363fn render_value(node: &Node, depth: usize, out: &mut String) -> fmt::Result {
364    match node {
365        Node::Scalar { text, mark } => {
366            write!(out, " {text}")?;
367            if let Some(mark) = mark {
368                write!(out, "  ({mark})")?;
369            }
370            writeln!(out)
371        }
372        Node::Map(children) if children.is_empty() => writeln!(out, " {{}}"),
373        Node::Map(_) => {
374            writeln!(out)?;
375            render(node, depth + 1, out)
376        }
377        Node::List { len, .. } => {
378            writeln!(out, " list of {len}")?;
379            render(node, depth + 1, out)
380        }
381    }
382}
383
384/// A preset that cannot be read or written.
385///
386/// The message names what failed, the cause and the fix.
387#[derive(Clone, Debug, PartialEq, Eq)]
388pub struct FormatError {
389    /// What failed.
390    pub what: String,
391    /// Why, naming the key or offset where there is one.
392    pub cause: String,
393    /// How to fix it.
394    pub fix: String,
395}
396
397impl FormatError {
398    /// Builds an error from its three parts.
399    pub fn new(what: impl Into<String>, cause: impl Into<String>, fix: impl Into<String>) -> Self {
400        Self {
401            what: what.into(),
402            cause: cause.into(),
403            fix: fix.into(),
404        }
405    }
406}
407
408impl fmt::Display for FormatError {
409    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410        write!(f, "{}: {}. {}", self.what, self.cause, self.fix)
411    }
412}
413
414impl Error for FormatError {}
415
416/// The format of a synth.
417///
418/// # Errors
419///
420/// [`SynthError::NotBuilt`] for a synth outside [`Synth::READY`].
421pub fn format_for(synth: Synth) -> Result<&'static dyn PresetFormat, SynthError> {
422    match synth.ready()? {
423        Synth::Vital => Ok(&crate::preset::vital::FORMAT),
424        Synth::Serum2 => Err(SynthError::NotBuilt(synth)),
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn describe_renders_an_indented_tree() {
434        let tree = Description {
435            root: Node::Map(vec![
436                ("name".into(), Node::scalar("\"Init\"")),
437                (
438                    "settings".into(),
439                    Node::Map(vec![
440                        (
441                            "cutoff".into(),
442                            Node::Scalar {
443                                text: "200.0".into(),
444                                mark: Some("out of range: 8.0 to 136.0".into()),
445                            },
446                        ),
447                        ("empty".into(), Node::Map(Vec::new())),
448                    ]),
449                ),
450                (
451                    "slots".into(),
452                    Node::List {
453                        len: 5,
454                        items: vec![
455                            Node::Map(vec![("source".into(), Node::scalar("\"lfo_1\""))]),
456                            Node::scalar("2"),
457                        ],
458                    },
459                ),
460            ]),
461        };
462        assert_eq!(
463            tree.to_string(),
464            "name: \"Init\"\n\
465             settings:\n  cutoff: 200.0  (out of range: 8.0 to 136.0)\n  empty: {}\n\
466             slots: list of 5\n  -\n    source: \"lfo_1\"\n  - 2\n  - ... 3 more\n"
467        );
468    }
469
470    #[test]
471    fn header_aligns_labels_and_marks_status() {
472        let mut header = Header::default();
473        header.push("build", "1.0.6", Some(Status::Ok));
474        header.push("unknown keys", "2", Some(Status::Warn));
475        assert_eq!(
476            header.to_string(),
477            "build         1.0.6\nunknown keys  2  [warn]\n"
478        );
479    }
480
481    #[test]
482    fn errors_print_three_parts() {
483        let error = FormatError::new("cannot read x", "no `settings`", "Save it again");
484        assert_eq!(
485            error.to_string(),
486            "cannot read x: no `settings`. Save it again"
487        );
488    }
489
490    #[test]
491    fn serum2_has_no_format_yet() {
492        assert_eq!(
493            format_for(Synth::Serum2).err(),
494            Some(SynthError::NotBuilt(Synth::Serum2))
495        );
496    }
497}