Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353//! Author fields paired with stored parameters, contextual defaults, and capture.use garde::Validate;use std::{error::Error, io};
use super::global::ChordOffsets;use super::lfo::LfoHz;use super::modulation::{Sources, Target};use super::oscillator::Oscillator;use super::{Spec, cycling_envelope, glide, keyboard, lfo};use crate::parameter::{ self as param, ParamDescriptor, RegisterCodec, RegisterError, RegisterReader, Registers, StoredValue,};
type Result<T> = std::result::Result<T, Box<dyn Error>>;
// One typed field mapping supplies capture and archive encoding. Contextual// selectors and unused assignment slots stay explicit below.macro_rules! control_fields { ($type:ty { $($($field:ident).+ => $parameter:path),+ $(,)? }) => { impl $type { fn read_fields(&mut self, reader: &mut impl RegisterReader) -> Result<()> { $(reader.field($parameter, &mut self.$($field).+)?;)+ Ok(()) }
fn encode_fields(&self, registers: &mut Registers) { $(registers.insert($parameter, self.$($field).+.encode());)+ } } };}
control_fields! { Spec { global.bend_range => param::BEND_RANGE, envelope.amp_mod => param::envelope::AMP_MOD, envelope.sustain => param::envelope::SUSTAIN, envelope.legato => param::envelope::LEGATO, envelope.snap => param::envelope::SNAP, envelope.attack_ms => param::envelope::ATTACK_MS, envelope.decay_release_ms => param::envelope::DECAY_RELEASE_MS, lfo.shape => param::lfo::SHAPE, lfo.retrigger => param::lfo::RETRIGGER, filter.kind => param::filter::TYPE, filter.resonance => param::filter::RESONANCE, filter.cutoff_hz => param::filter::CUTOFF_HZ, keyboard.octave => param::keyboard::OCTAVE, keyboard.pressure_mode => param::keyboard::PRESSURE_MODE, keyboard.velocity_amp => param::keyboard::VELOCITY_AMP, keyboard.scale => param::keyboard::SCALE, keyboard.root => param::keyboard::ROOT, arp.enabled => param::arp::ENABLED, arp.range => param::arp::RANGE, arp.division => param::arp::DIVISION, arp.swing => param::arp::SWING, arp.sync => param::arp::SYNC, arp.spice => param::arp::SPICE, arp.dice => param::arp::DICE, arp.direction => param::arp::DIRECTION, arp.sequencer => param::arp::SEQUENCER, arp.rate_bpm => param::arp::RATE_BPM, global.paraphonic => param::global::PARAPHONIC, global.para_voice_mode => param::global::PARA_VOICE_MODE, global.preset_volume => param::global::PRESET_VOLUME, global.master_volume => param::global::MASTER_VOLUME, global.unison_spread => param::global::UNISON_SPREAD, global.unison_count => param::global::UNISON_COUNT, global.chord_mode => param::global::CHORD_MODE, vocoder.hiss_mode => param::vocoder::HISS_MODE, vocoder.hiss_level => param::vocoder::HISS_LEVEL, modulations.key_source => param::modulations::KEY_SOURCE, modulations.pitch.sources.cycling_envelope => param::modulations::pitch::CYCLING_ENVELOPE, modulations.pitch.sources.envelope => param::modulations::pitch::ENVELOPE, modulations.pitch.sources.lfo => param::modulations::pitch::LFO, modulations.pitch.sources.pressure => param::modulations::pitch::PRESSURE, modulations.pitch.sources.key => param::modulations::pitch::KEY, modulations.wave.sources.cycling_envelope => param::modulations::wave::CYCLING_ENVELOPE, modulations.wave.sources.envelope => param::modulations::wave::ENVELOPE, modulations.wave.sources.lfo => param::modulations::wave::LFO, modulations.wave.sources.pressure => param::modulations::wave::PRESSURE, modulations.wave.sources.key => param::modulations::wave::KEY, modulations.timbre.sources.cycling_envelope => param::modulations::timbre::CYCLING_ENVELOPE, modulations.timbre.sources.envelope => param::modulations::timbre::ENVELOPE, modulations.timbre.sources.lfo => param::modulations::timbre::LFO, modulations.timbre.sources.pressure => param::modulations::timbre::PRESSURE, modulations.timbre.sources.key => param::modulations::timbre::KEY, modulations.cutoff.sources.cycling_envelope => param::modulations::cutoff::CYCLING_ENVELOPE, modulations.cutoff.sources.envelope => param::modulations::cutoff::ENVELOPE, modulations.cutoff.sources.lfo => param::modulations::cutoff::LFO, modulations.cutoff.sources.pressure => param::modulations::cutoff::PRESSURE, modulations.cutoff.sources.key => param::modulations::cutoff::KEY, modulations.assign1.sources.cycling_envelope => param::modulations::assign1::CYCLING_ENVELOPE, modulations.assign1.sources.envelope => param::modulations::assign1::ENVELOPE, modulations.assign1.sources.lfo => param::modulations::assign1::LFO, modulations.assign1.sources.pressure => param::modulations::assign1::PRESSURE, modulations.assign1.sources.key => param::modulations::assign1::KEY, modulations.assign2.sources.cycling_envelope => param::modulations::assign2::CYCLING_ENVELOPE, modulations.assign2.sources.envelope => param::modulations::assign2::ENVELOPE, modulations.assign2.sources.lfo => param::modulations::assign2::LFO, modulations.assign2.sources.pressure => param::modulations::assign2::PRESSURE, modulations.assign2.sources.key => param::modulations::assign2::KEY, modulations.assign3.sources.cycling_envelope => param::modulations::assign3::CYCLING_ENVELOPE, modulations.assign3.sources.envelope => param::modulations::assign3::ENVELOPE, modulations.assign3.sources.lfo => param::modulations::assign3::LFO, modulations.assign3.sources.pressure => param::modulations::assign3::PRESSURE, modulations.assign3.sources.key => param::modulations::assign3::KEY, }}
control_fields! { cycling_envelope::Gated { rise_ms => param::cycling_envelope::RISE_MS, fall_ms => param::cycling_envelope::FALL_MS, rise_slope => param::cycling_envelope::RISE_SLOPE, fall_slope => param::cycling_envelope::FALL_SLOPE, amount => param::cycling_envelope::AMOUNT, sustain => param::cycling_envelope::HOLD_SUSTAIN, }}
control_fields! { cycling_envelope::Repeating { rise_ms => param::cycling_envelope::RISE_MS, fall_ms => param::cycling_envelope::FALL_MS, rise_slope => param::cycling_envelope::RISE_SLOPE, fall_slope => param::cycling_envelope::FALL_SLOPE, amount => param::cycling_envelope::AMOUNT, hold_ms => param::cycling_envelope::HOLD_SUSTAIN, }}
const CHORD_OFFSET_PARAMETERS: [ParamDescriptor; 3] = [ param::global::CHORD_OFFSET1, param::global::CHORD_OFFSET2, param::global::CHORD_OFFSET3,];
impl Spec { // Only the active LFO alternative has a target. Archive serialization filters // live-only controls by archive identity. /// Encode resolved controls as complete stored registers, including sample identity. /// Values must have passed author-input validation (for example via [`super::parse`]). pub fn registers(&self) -> Registers { let mut registers = Registers::new(); self.encode_fields(&mut registers); for (index, parameter) in CHORD_OFFSET_PARAMETERS.into_iter().enumerate() { let semitones = self .global .chord_offsets .0 .get(index) .copied() .unwrap_or(127); registers.insert( parameter, StoredValue(((i16::from(semitones) + 128) * 128) as u16), ); } self.oscillator.encode_controls(&mut registers); let mode = match &self.cycling_envelope { cycling_envelope::CyclingEnvelope::Env(envelope) => { envelope.encode_fields(&mut registers); 0 } cycling_envelope::CyclingEnvelope::Run(envelope) => { envelope.encode_fields(&mut registers); 1 } cycling_envelope::CyclingEnvelope::Loop(envelope) => { envelope.encode_fields(&mut registers); 2 } }; registers.insert(param::cycling_envelope::MODE, param::encode_bin(mode, 2)); let (glide_mode, glide_value) = match &self.keyboard.glide { glide::Glide::Time(duration) => (0, duration.encode()), glide::Glide::Sync(division) => (1, division.encode()), glide::Glide::Rate(duration) => (2, duration.encode()), }; registers.insert( param::keyboard::GLIDE_MODE, param::encode_bin(glide_mode, 2), ); registers.insert(param::keyboard::GLIDE, glide_value); let synced = match &self.lfo.rate { lfo::Rate::Free(rate) => { registers.insert(param::lfo::RATE_HZ, rate.encode()); false } lfo::Rate::Sync(rate) => { registers.insert(param::lfo::RATE_DIVISION, rate.encode()); true } }; registers.insert(param::lfo::SYNC, synced.encode()); for (parameter, target) in [ ( param::modulations::ASSIGN1_TARGET, self.modulations.assign1.target, ), ( param::modulations::ASSIGN2_TARGET, self.modulations.assign2.target, ), ( param::modulations::ASSIGN3_TARGET, self.modulations.assign3.target, ), ] { registers.insert(parameter, target.encode()); } registers }}
pub(crate) fn context_defaults( oscillator: StoredValue, cycling_mode: StoredValue, lfo_sync: StoredValue, glide_mode: StoredValue,) -> Result<Spec> { let mode = param::decode_bin::<cycling_envelope::CyclingEnvelope>(cycling_mode, 2)?; Ok(Spec { oscillator: Oscillator::from_model_register(oscillator)?, keyboard: keyboard::Keyboard { glide: glide::Glide::from_mode_register(glide_mode)?, ..keyboard::Keyboard::default() }, cycling_envelope: match mode { 0 => cycling_envelope::CyclingEnvelope::Env(cycling_envelope::Gated::default()), 1 => cycling_envelope::CyclingEnvelope::Run(cycling_envelope::Repeating::default()), 2 => cycling_envelope::CyclingEnvelope::Loop(cycling_envelope::Repeating::default()), _ => unreachable!(), }, lfo: lfo::Lfo { rate: if bool::decode(lfo_sync)? { lfo::Rate::Sync(lfo::Division::default()) } else { lfo::Rate::Free(LfoHz::default()) }, ..lfo::Lfo::default() }, ..Spec::default() })}
fn read_assignment( reader: &mut impl RegisterReader, parameter: ParamDescriptor, sources: &mut Sources, target: &mut Target,) -> Result<()> { let raw: u16 = reader.value(parameter)?; // Self-references are unassigned columns, even with leftover amounts. // Drop the amounts too, rather than applying them to the default target. if raw == parameter.id { *sources = Sources::default(); return Ok(()); } *target = Target::decode(StoredValue(raw)).map_err(|source| RegisterError { parameter, source: source.into(), })?; Ok(())}
pub(crate) fn snapshot(reader: &mut impl RegisterReader) -> Result<(Spec, Spec)> { let selectors = [ ( param::oscillator::TYPE, reader.value::<u16>(param::oscillator::TYPE)?, ), ( param::cycling_envelope::MODE, reader.value::<u16>(param::cycling_envelope::MODE)?, ), (param::lfo::SYNC, reader.value::<u16>(param::lfo::SYNC)?), ( param::keyboard::GLIDE_MODE, reader.value::<u16>(param::keyboard::GLIDE_MODE)?, ), ]; let defaults = context_defaults( StoredValue(selectors[0].1), StoredValue(selectors[1].1), StoredValue(selectors[2].1), StoredValue(selectors[3].1), )?; let mut spec = defaults.clone(); spec.oscillator.read_controls(reader)?; spec.read_fields(reader)?; let mut offsets = Vec::new(); // +127 ends the chord; later slots are inactive, not independent voices. let mut ended = false; for parameter in CHORD_OFFSET_PARAMETERS { let raw = StoredValue(reader.value(parameter)?); let raw = param::unsigned::<ChordOffsets>(raw).map_err(|source| RegisterError { parameter, source: source.into(), })?; let semitones = (i32::from(raw / 128) - 128) as i8; ended |= semitones == 127; if !ended { offsets.push(semitones); } } spec.global.chord_offsets = ChordOffsets(offsets); match &mut spec.cycling_envelope { cycling_envelope::CyclingEnvelope::Env(envelope) => envelope.read_fields(reader)?, cycling_envelope::CyclingEnvelope::Run(envelope) | cycling_envelope::CyclingEnvelope::Loop(envelope) => envelope.read_fields(reader)?, } match &mut spec.lfo.rate { lfo::Rate::Free(rate) => reader.field(param::lfo::RATE_HZ, rate)?, lfo::Rate::Sync(rate) => reader.field(param::lfo::RATE_DIVISION, rate)?, } match &mut spec.keyboard.glide { glide::Glide::Time(duration) | glide::Glide::Rate(duration) => { reader.field(param::keyboard::GLIDE, duration)?; } glide::Glide::Sync(division) => reader.field(param::keyboard::GLIDE, division)?, } read_assignment( reader, param::modulations::ASSIGN1_TARGET, &mut spec.modulations.assign1.sources, &mut spec.modulations.assign1.target, )?; read_assignment( reader, param::modulations::ASSIGN2_TARGET, &mut spec.modulations.assign2.sources, &mut spec.modulations.assign2.target, )?; read_assignment( reader, param::modulations::ASSIGN3_TARGET, &mut spec.modulations.assign3.sources, &mut spec.modulations.assign3.target, )?; for (parameter, initial) in selectors { if reader.value::<u16>(parameter)? != initial { return Err(io::Error::other( "oscillator, envelope mode, or LFO clock changed during capture; retry get", ) .into()); } } spec.validate()?; Ok((spec, defaults))}