Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
microfreak
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462//! Stored parameters, paired codecs, firmware identities, and the register I/O boundary.
use std::{any::type_name, collections::BTreeMap, error::Error, fmt, io};
/// A complete stored register, not a display value or MIDI-safe wire encoding.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub struct StoredValue(pub u16);
/// Converts one validated author value to/from one complete stored register./// Decoding validates device data; encoding relies on author-boundary validation./// Quantized controls need not recover the original register when re-encoded.pub trait RegisterCodec: Sized { fn encode(&self) -> StoredValue; fn decode(raw: StoredValue) -> Result<Self, DecodeError>;}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub struct DecodeError { pub raw: StoredValue, pub value_type: &'static str,}
impl DecodeError { pub(crate) fn new<T>(raw: StoredValue) -> Self { Self { raw, value_type: type_name::<T>(), } }}
impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "unsupported {} register 0x{:04X}", self.value_type, self.raw.0 ) }}
impl Error for DecodeError {}
impl RegisterCodec for bool { fn encode(&self) -> StoredValue { StoredValue(if *self { 32767 } else { 0 }) }
fn decode(raw: StoredValue) -> Result<Self, DecodeError> { Ok(raw.0 >= 16384) }}
// Archive sample hashes retain all sixteen bits.impl RegisterCodec for u16 { fn encode(&self) -> StoredValue { StoredValue(*self) }
fn decode(raw: StoredValue) -> Result<Self, DecodeError> { Ok(raw.0) }}
pub(crate) fn unsigned<T>(raw: StoredValue) -> Result<u16, DecodeError> { if raw.0 > 32767 { return Err(DecodeError::new::<T>(raw)); } Ok(raw.0)}
pub(crate) fn encode_bin(index: u16, maximum: u16) -> StoredValue { StoredValue(((u32::from(index) * 32767 + u32::from(maximum) / 2) / u32::from(maximum)) as u16)}
pub(crate) fn decode_bin<T>(raw: StoredValue, maximum: u16) -> Result<u16, DecodeError> { Ok(((u32::from(maximum + 1) * u32::from(unsigned::<T>(raw)?)) / 32768) as u16)}
// Unit variants are ordinal bin indices. Sparse enums and assignment IDs use// explicit codecs instead; this grammar deliberately excludes discriminants.macro_rules! bin_enum { ( $(#[$attr:meta])* $vis:vis enum $name:ident { $(#[$first_attr:meta])* $first:ident, $(#[$second_attr:meta])* $second:ident $(, $(#[$variant_attr:meta])* $variant:ident)* $(,)? } ) => { #[::mfctl_derive::author] #[derive(Default)] #[schemars(extend("default" = Self::default()))] $(#[$attr])* $vis enum $name { $(#[$first_attr])* $first, $(#[$second_attr])* $second, $($(#[$variant_attr])* $variant,)* }
impl $name { const BIN_VARIANTS: &'static [Self] = &[Self::$first, Self::$second, $(Self::$variant,)*]; }
const _: () = assert!($name::BIN_VARIANTS.len() <= 32768);
impl $crate::parameter::RegisterCodec for $name { fn encode(&self) -> $crate::parameter::StoredValue { $crate::parameter::encode_bin(*self as u16, Self::BIN_VARIANTS.len() as u16 - 1) }
fn decode(raw: $crate::parameter::StoredValue) -> Result<Self, $crate::parameter::DecodeError> { let maximum = Self::BIN_VARIANTS.len() as u16 - 1; let index = $crate::parameter::decode_bin::<Self>(raw, maximum)?; Ok(Self::BIN_VARIANTS[usize::from(index)]) } } };}
pub(crate) use bin_enum;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]pub struct ParamDescriptor { pub id: u16, // Hold, Spice/Dice, and master volume are live-only. pub(crate) preset: Option<PresetIdentity>,}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]pub(crate) struct PresetIdentity { pub(crate) mode: u8, pub(crate) group: &'static str, pub(crate) name: &'static str,}
// Each declaration supplies both its constant and its module's enumeration.macro_rules! parameters { ($($name:ident: $id:literal $(=> $preset:tt)?;)+) => { $(pub const $name: $crate::parameter::ParamDescriptor = parameters!(@descriptor $id $(=> $preset)?);)+ pub(crate) const ALL: &[$crate::parameter::ParamDescriptor] = &[$($name),+]; }; (@descriptor $id:literal => ($mode:literal, $group:literal, $name:literal)) => { $crate::parameter::ParamDescriptor { id: $id, preset: Some($crate::parameter::PresetIdentity { mode: $mode, group: $group, name: $name, }), } }; (@descriptor $id:literal) => { $crate::parameter::ParamDescriptor { id: $id, preset: None } };}
pub mod oscillator { parameters! { TYPE: 0x0000 => (22, "VCO", "Type"); WAVE: 0x0001 => (238, "VCO", "Param1"); TIMBRE: 0x0003 => (238, "VCO", "Param2"); SHAPE: 0x0005 => (238, "VCO", "Param3"); }}
parameters! { BEND_RANGE: 0x0007 => (24, "VCO", "BendRng"); SAMPLE_INDEX: 0x0009 => (230, "VCO", "SmpIdx");}
pub mod filter { parameters! { TYPE: 0x0100 => (2, "VCF", "Type"); CUTOFF_HZ: 0x0101 => (0, "VCF", "Cutoff"); RESONANCE: 0x0102 => (0, "VCF", "Reso"); }}
pub mod cycling_envelope { parameters! { MODE: 0x0200 => (2, "EG1", "Mode"); RISE_MS: 0x0201 => (0, "EG1", "RiseLvl"); RISE_SLOPE: 0x0202 => (0, "EG1", "RiseSlp"); FALL_MS: 0x0203 => (0, "EG1", "FallLvl");
// Sustain in Env mode; Hold in Run/Loop modes. HOLD_SUSTAIN: 0x0204 => (0, "EG1", "Hold"); FALL_SLOPE: 0x0205 => (0, "EG1", "FallSlp"); AMOUNT: 0x0206 => (0, "EG1", "Amount"); }}
pub mod keyboard { parameters! { GLIDE: 0x0300 => (0, "Kbd", "Glide"); OCTAVE: 0x0301 => (6, "Kbd", "Octave"); PRESSURE_MODE: 0x0302 => (1, "Kbd", "Veloc"); GLIDE_MODE: 0x0304 => (2, "Kbd", "GlMode"); VELOCITY_AMP: 0x0305 => (10, "Kbd", "PresAmp"); SCALE: 0x0306 => (8, "Kbd", "Scale"); ROOT: 0x0307 => (11, "Kbd", "Root"); }}
pub mod arp { parameters! { ENABLED: 0x0400 => (1, "Arp", "Enable"); RANGE: 0x0401 => (3, "Arp", "Range"); DIVISION: 0x0402 => (10, "Arp", "Div"); RATE_BPM: 0x0403 => (0, "Arp", "Rate"); SWING: 0x0404 => (25, "Arp", "Swing"); SYNC: 0x0405 => (1, "Arp", "Sync"); SPICE: 0x0406; DICE: 0x0407; DIRECTION: 0x0408 => (3, "Arp", "Dir"); SEQUENCER: 0x0409 => (1, "Arp", "SeqOn"); }}
pub mod lfo { parameters! { SHAPE: 0x0500 => (5, "LFO", "Shape"); RATE_DIVISION: 0x0501 => (0, "LFO", "Div"); RATE_HZ: 0x0502 => (0, "LFO", "Rate"); SYNC: 0x0503 => (1, "LFO", "Sync"); RETRIGGER: 0x0504 => (2, "LFO", "Retrig"); }}
pub mod envelope { parameters! { AMP_MOD: 0x0600 => (1, "EG2", "Mode"); ATTACK_MS: 0x0601 => (0, "EG2", "Attack"); DECAY_RELEASE_MS: 0x0602 => (0, "EG2", "DecRel"); SUSTAIN: 0x0603 => (0, "EG2", "Sustain"); LEGATO: 0x0604 => (1, "EG2", "Legato"); SNAP: 0x0606 => (1, "EG2", "Snap"); }}
pub mod global { parameters! { PARAPHONIC: 0x0700 => (1, "Gen", "Parafon"); PARA_VOICE_MODE: 0x0702 => (1, "Gen", "PolyCnt"); PRESET_VOLUME: 0x0703 => (24, "Gen", "PrstVol"); MASTER_VOLUME: 0x0704; UNISON_SPREAD: 0x0705 => (233, "Gen", "UniSprd"); UNISON_COUNT: 0x0706 => (2, "Gen", "UniCnt"); CHORD_MODE: 0x0707 => (3, "Gen", "Chord"); CHORD_OFFSET1: 0x0708 => (247, "Gen", "ChOffs1"); CHORD_OFFSET2: 0x0709 => (247, "Gen", "ChOffs2"); CHORD_OFFSET3: 0x070a => (247, "Gen", "ChOffs3");
// Archive-only sample identity; never queried or written live. SAMPLE_HASH: 0x070b => (0, "Gen", "SmpHash"); }}
pub mod modulations { parameters! { ASSIGN1_TARGET: 0x0902 => (0, "Mat", "Assign1"); ASSIGN2_TARGET: 0x0903 => (0, "Mat", "Assign2"); ASSIGN3_TARGET: 0x0904 => (0, "Mat", "Assign3"); KEY_SOURCE: 0x0905 => (1, "Mat", "KyMdSrc"); }
pub mod pitch { parameters! { CYCLING_ENVELOPE: 0x0a00 => (0, "Co1", "EG1"); ENVELOPE: 0x0a01 => (0, "Co1", "EG2"); LFO: 0x0a02 => (0, "Co1", "LFO"); PRESSURE: 0x0a03 => (0, "Co1", "Xpr"); KEY: 0x0a04 => (0, "Co1", "Key"); } }
pub mod wave { parameters! { CYCLING_ENVELOPE: 0x0b00 => (0, "Co2", "EG1"); ENVELOPE: 0x0b01 => (0, "Co2", "EG2"); LFO: 0x0b02 => (0, "Co2", "LFO"); PRESSURE: 0x0b03 => (0, "Co2", "Xpr"); KEY: 0x0b04 => (0, "Co2", "Key"); } }
pub mod timbre { parameters! { CYCLING_ENVELOPE: 0x0c00 => (0, "Co3", "EG1"); ENVELOPE: 0x0c01 => (0, "Co3", "EG2"); LFO: 0x0c02 => (0, "Co3", "LFO"); PRESSURE: 0x0c03 => (0, "Co3", "Xpr"); KEY: 0x0c04 => (0, "Co3", "Key"); } }
pub mod cutoff { parameters! { CYCLING_ENVELOPE: 0x0d00 => (0, "Co4", "EG1"); ENVELOPE: 0x0d01 => (0, "Co4", "EG2"); LFO: 0x0d02 => (0, "Co4", "LFO"); PRESSURE: 0x0d03 => (0, "Co4", "Xpr"); KEY: 0x0d04 => (0, "Co4", "Key"); } }
pub mod assign1 { parameters! { CYCLING_ENVELOPE: 0x0e00 => (0, "Co5", "EG1"); ENVELOPE: 0x0e01 => (0, "Co5", "EG2"); LFO: 0x0e02 => (0, "Co5", "LFO"); PRESSURE: 0x0e03 => (0, "Co5", "Xpr"); KEY: 0x0e04 => (0, "Co5", "Key"); } }
pub mod assign2 { parameters! { CYCLING_ENVELOPE: 0x0f00 => (0, "Co6", "EG1"); ENVELOPE: 0x0f01 => (0, "Co6", "EG2"); LFO: 0x0f02 => (0, "Co6", "LFO"); PRESSURE: 0x0f03 => (0, "Co6", "Xpr"); KEY: 0x0f04 => (0, "Co6", "Key"); } }
pub mod assign3 { parameters! { CYCLING_ENVELOPE: 0x1000 => (0, "Co7", "EG1"); ENVELOPE: 0x1001 => (0, "Co7", "EG2"); LFO: 0x1002 => (0, "Co7", "LFO"); PRESSURE: 0x1003 => (0, "Co7", "Xpr"); KEY: 0x1004 => (0, "Co7", "Key"); } }}
pub mod vocoder { parameters! { HISS_MODE: 0x1300 => (2, "Voc", "HissMod"); HISS_LEVEL: 0x1301 => (20, "Voc", "HissVol"); }}
const GROUPS: &[&[ParamDescriptor]] = &[ oscillator::ALL, ALL, filter::ALL, cycling_envelope::ALL, keyboard::ALL, arp::ALL, lfo::ALL, envelope::ALL, global::ALL, modulations::ALL, modulations::pitch::ALL, modulations::wave::ALL, modulations::timbre::ALL, modulations::cutoff::ALL, modulations::assign1::ALL, modulations::assign2::ALL, modulations::assign3::ALL, vocoder::ALL,];
const DESCRIPTOR_COUNT: usize = { let mut count = 0; let mut group = 0; while group < GROUPS.len() { count += GROUPS[group].len(); group += 1; } count};
// Groups and their members are declared in ID order; concatenate at compile time.pub static DESCRIPTORS: [ParamDescriptor; DESCRIPTOR_COUNT] = { let mut descriptors = [oscillator::TYPE; DESCRIPTOR_COUNT]; let mut count = 0; let mut group = 0; while group < GROUPS.len() { let mut member = 0; while member < GROUPS[group].len() { descriptors[count] = GROUPS[group][member]; count += 1; member += 1; } group += 1; } descriptors};
pub type Registers = BTreeMap<ParamDescriptor, StoredValue>;
// Import fills contextual defaults before exposing a bank to the shared decoder.impl RegisterReader for Registers { fn read(&mut self, parameter: ParamDescriptor) -> io::Result<StoredValue> { self.get(¶meter).copied().ok_or_else(|| { io::Error::new( io::ErrorKind::NotFound, format!("missing parameter 0x{:04X}", parameter.id), ) }) }}
#[derive(Debug)]pub struct RegisterError { pub parameter: ParamDescriptor, pub source: Box<dyn Error>,}
impl fmt::Display for RegisterError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "parameter 0x{:04X}: {}", self.parameter.id, self.source) }}
impl Error for RegisterError { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(self.source.as_ref()) }}
/// Typed reads validate stored values; implementations own transport or archive access.pub trait RegisterReader { fn read(&mut self, parameter: ParamDescriptor) -> io::Result<StoredValue>;
fn value<T: RegisterCodec>(&mut self, parameter: ParamDescriptor) -> Result<T, Box<dyn Error>> { let raw = self.read(parameter).map_err(|source| RegisterError { parameter, source: source.into(), })?; T::decode(raw).map_err(|source| { RegisterError { parameter, source: source.into(), } .into() }) }
fn field<T: RegisterCodec>( &mut self, parameter: ParamDescriptor, value: &mut T, ) -> Result<(), Box<dyn Error>> { let decoded: T = self.value(parameter)?; // Preserve the declared default spelling when targets agree at codec // precision, rather than comparing decoded floating-point values. if decoded.encode() != value.encode() { *value = decoded; } Ok(()) }}
/// Writes send a full stored register; read-back, not send success, confirms it.pub trait RegisterWriter: RegisterReader { fn write(&mut self, parameter: ParamDescriptor, value: StoredValue) -> io::Result<()>;}