//! Complete author documents, contextual defaults, and YAML serialization. #[cfg(test)] mod tests; pub mod arp; pub mod cycling_envelope; pub mod envelope; pub mod filter; pub mod glide; pub mod global; pub mod keyboard; pub mod lfo; pub(crate) mod mapping; pub mod modulation; pub mod oscillator; mod spec; pub mod value; pub mod vocoder; pub(crate) mod yaml; pub use spec::Spec; use garde::{Unvalidated, Valid, Validate}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::{error::Error, fmt}; #[derive(Debug)] pub enum ParseError { Yaml(serde_yaml::Error), Validation(garde::Report), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Yaml(error) => error.fmt(f), Self::Validation(error) => error.fmt(f), } } } impl Error for ParseError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::Yaml(error) => Some(error), Self::Validation(error) => Some(error), } } } /// Resolve YAML aliases/merges before deserializing the typed model. No JSON /// conversion is involved; validation runs once at this author-input boundary. pub fn parse(source: &str) -> Result, ParseError> { let mut value: serde_yaml::Value = serde_yaml::from_str(source).map_err(ParseError::Yaml)?; value.apply_merge().map_err(ParseError::Yaml)?; // yaml_serde treats null as an empty mapping when a struct asks for one. // Serde's untagged content buffer preserves the YAML types instead, so // object defaults cannot silently turn an explicit null into a section. #[derive(Deserialize)] #[serde(untagged)] enum Document { Patch(Patch), } let Document::Patch(patch) = serde_yaml::from_value(value).map_err(ParseError::Yaml)?; Unvalidated::new(patch) .validate() .map_err(ParseError::Validation) } /// Decode supported controls from a register reader into compact YAML. /// The reader owns data access; no MIDI or other transport is opened here. /// Archive-only sample hashes are not read; use [`from_registers`] to retain them. pub fn capture( reader: &mut impl crate::parameter::RegisterReader, metadata: &Metadata, ) -> Result> { let (spec, defaults) = mapping::snapshot(reader)?; yaml::render_patch(&spec, &defaults, metadata) } /// Decode a resolved register bank into compact YAML, retaining sample identity. /// Supply all active controls, as returned by [`Spec::registers`]. pub fn from_registers( registers: &crate::parameter::Registers, metadata: &Metadata, ) -> Result> { let mut registers = registers.clone(); let (mut spec, defaults) = mapping::snapshot(&mut registers)?; let hash = registers .get(&crate::parameter::global::SAMPLE_HASH) .map_or(0, |value| value.0); if hash != 0 || spec.oscillator.sample.is_some() { spec.oscillator.sample.get_or_insert_default().hash = hash; } yaml::render_patch(&spec, &defaults, metadata) } #[derive(Debug, PartialEq, Deserialize, Serialize, JsonSchema, Validate)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Patch { #[serde(default)] /// Schema version for the patch document. #[garde(dive)] pub api_version: ApiVersion, #[serde(default)] /// Document type. #[garde(dive)] pub kind: Kind, #[serde(default)] /// Preset metadata; it does not change live controls. #[garde(dive)] pub metadata: Metadata, /// MicroFreak controls. Omitted values resolve to defaults. #[garde(dive)] pub spec: Spec, } #[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema, Validate)] #[schemars(extend("default" = Self::default()))] pub enum ApiVersion { #[default] #[serde(rename = "microfreak/v5")] V5, } #[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema, Validate)] #[schemars(extend("default" = Self::default()))] pub enum Kind { #[default] Patch, } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema, Validate)] #[serde(default, deny_unknown_fields)] pub struct Metadata { /// Printable ASCII archive name; MCC names can exceed the device display width. #[garde(pattern(r"^[\x20-\x7E]+$"))] pub name: String, /// Preset category stored in archive metadata. #[garde(skip)] pub category: Category, /// MCC preset character tags; they do not change live controls. #[garde(skip)] pub tags: Vec, } impl Default for Metadata { fn default() -> Self { Self { name: "Untitled".to_owned(), category: Category::default(), tags: Vec::new(), } } } /// MCC character-tag bit indices, shared across preset archive versions. #[derive( Clone, Copy, Debug, PartialEq, Deserialize, Serialize, JsonSchema, strum_macros::FromRepr, )] #[repr(u8)] pub enum Tag { Acid, Aggressive, Ambient, Bizarre, Bright, Complex, Dark, Digital, Ensemble, Funky, Hard, Long, Noise, Quiet, Short, Simple, Soft, Soundtrack, } #[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema, Validate)] #[schemars(extend("default" = Self::default()))] pub enum Category { Bass, Brass, Keys, Lead, Organ, Pad, Percussion, Sequence, SFX, Strings, #[default] Template, Vocoder, }