Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
microfreak
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202//! 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<Valid<Patch>, 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<String, Box<dyn Error>> { 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<String, Box<dyn Error>> { 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<Tag>,}
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,}