Something went wrong. Try again.
YAML-based live configuration of MicroFreak datyoma.tngl.io/mfctl
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296use crate::parameter::{self as param, DecodeError, RegisterCodec, StoredValue, bin_enum};use mfctl_derive::author;use schemars::JsonSchema;use serde::{Deserialize, Serialize};
use super::{ oscillator::{self, Oscillator}, value::Bipolar,};
/// Stable modulation destinations. Oscillator axes keep their panel identities/// even though each model has different author parameter names. Discriminants/// are complete firmware parameter IDs, not enum-bin indices.#[derive( Clone, Copy, Debug, PartialEq, Deserialize, Serialize, JsonSchema, strum_macros::FromRepr,)]#[repr(u16)]pub enum Target { #[serde(rename = "oscillator.type")] OscillatorType = param::oscillator::TYPE.id, #[serde(rename = "oscillator.wave")] OscillatorWave = param::oscillator::WAVE.id, #[serde(rename = "oscillator.timbre")] OscillatorTimbre = param::oscillator::TIMBRE.id, #[serde(rename = "oscillator.shape")] OscillatorShape = param::oscillator::SHAPE.id, #[serde(rename = "sampleIndex")] SampleIndex = param::SAMPLE_INDEX.id, #[serde(rename = "filter.cutoffHz")] FilterCutoffHz = param::filter::CUTOFF_HZ.id, #[serde(rename = "filter.resonance")] FilterResonance = param::filter::RESONANCE.id, #[serde(rename = "cyclingEnvelope.riseMs")] CyclingEnvelopeRiseMs = param::cycling_envelope::RISE_MS.id, #[serde(rename = "cyclingEnvelope.fallMs")] CyclingEnvelopeFallMs = param::cycling_envelope::FALL_MS.id, #[serde(rename = "cyclingEnvelope.sustain")] CyclingEnvelopeSustain = param::cycling_envelope::HOLD_SUSTAIN.id, #[serde(rename = "cyclingEnvelope.amount")] CyclingEnvelopeAmount = param::cycling_envelope::AMOUNT.id, #[serde(rename = "keyboard.glide")] KeyboardGlide = param::keyboard::GLIDE.id, #[serde(rename = "arp.range")] ArpRange = param::arp::RANGE.id, #[serde(rename = "arp.rateBpm")] ArpRateBpm = param::arp::RATE_BPM.id, #[serde(rename = "lfo.shape")] LfoShape = param::lfo::SHAPE.id, #[serde(rename = "lfo.rateDivision")] LfoRateDivision = param::lfo::RATE_DIVISION.id, #[serde(rename = "lfo.rateHz")] LfoRateHz = param::lfo::RATE_HZ.id, #[serde(rename = "envelope.attackMs")] EnvelopeAttackMs = param::envelope::ATTACK_MS.id, #[serde(rename = "envelope.decayReleaseMs")] EnvelopeDecayReleaseMs = param::envelope::DECAY_RELEASE_MS.id, #[serde(rename = "envelope.sustain")] EnvelopeSustain = param::envelope::SUSTAIN.id, #[serde(rename = "envelope.legato")] EnvelopeLegato = param::envelope::LEGATO.id, #[serde(rename = "unisonSpread")] GlobalUnisonSpread = param::global::UNISON_SPREAD.id, #[serde(rename = "cyclingEnvelope->pitch")] CyclingEnvelopePitch = param::modulations::pitch::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->pitch")] EnvelopePitch = param::modulations::pitch::ENVELOPE.id, #[serde(rename = "lfo->pitch")] LfoPitch = param::modulations::pitch::LFO.id, #[serde(rename = "pressure->pitch")] PressurePitch = param::modulations::pitch::PRESSURE.id, #[serde(rename = "key->pitch")] KeyPitch = param::modulations::pitch::KEY.id, #[serde(rename = "cyclingEnvelope->wave")] CyclingEnvelopeWave = param::modulations::wave::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->wave")] EnvelopeWave = param::modulations::wave::ENVELOPE.id, #[serde(rename = "lfo->wave")] LfoWave = param::modulations::wave::LFO.id, #[serde(rename = "pressure->wave")] PressureWave = param::modulations::wave::PRESSURE.id, #[serde(rename = "key->wave")] KeyWave = param::modulations::wave::KEY.id, #[serde(rename = "cyclingEnvelope->timbre")] CyclingEnvelopeTimbre = param::modulations::timbre::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->timbre")] EnvelopeTimbre = param::modulations::timbre::ENVELOPE.id, #[serde(rename = "lfo->timbre")] LfoTimbre = param::modulations::timbre::LFO.id, #[serde(rename = "pressure->timbre")] PressureTimbre = param::modulations::timbre::PRESSURE.id, #[serde(rename = "key->timbre")] KeyTimbre = param::modulations::timbre::KEY.id, #[serde(rename = "cyclingEnvelope->cutoff")] CyclingEnvelopeCutoff = param::modulations::cutoff::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->cutoff")] EnvelopeCutoff = param::modulations::cutoff::ENVELOPE.id, #[serde(rename = "lfo->cutoff")] LfoCutoff = param::modulations::cutoff::LFO.id, #[serde(rename = "pressure->cutoff")] PressureCutoff = param::modulations::cutoff::PRESSURE.id, #[serde(rename = "key->cutoff")] KeyCutoff = param::modulations::cutoff::KEY.id, #[serde(rename = "cyclingEnvelope->assign1")] CyclingEnvelopeAssign1 = param::modulations::assign1::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->assign1")] EnvelopeAssign1 = param::modulations::assign1::ENVELOPE.id, #[serde(rename = "lfo->assign1")] LfoAssign1 = param::modulations::assign1::LFO.id, #[serde(rename = "pressure->assign1")] PressureAssign1 = param::modulations::assign1::PRESSURE.id, #[serde(rename = "key->assign1")] KeyAssign1 = param::modulations::assign1::KEY.id, #[serde(rename = "cyclingEnvelope->assign2")] CyclingEnvelopeAssign2 = param::modulations::assign2::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->assign2")] EnvelopeAssign2 = param::modulations::assign2::ENVELOPE.id, #[serde(rename = "lfo->assign2")] LfoAssign2 = param::modulations::assign2::LFO.id, #[serde(rename = "pressure->assign2")] PressureAssign2 = param::modulations::assign2::PRESSURE.id, #[serde(rename = "key->assign2")] KeyAssign2 = param::modulations::assign2::KEY.id, #[serde(rename = "cyclingEnvelope->assign3")] CyclingEnvelopeAssign3 = param::modulations::assign3::CYCLING_ENVELOPE.id, #[serde(rename = "envelope->assign3")] EnvelopeAssign3 = param::modulations::assign3::ENVELOPE.id, #[serde(rename = "lfo->assign3")] LfoAssign3 = param::modulations::assign3::LFO.id, #[serde(rename = "pressure->assign3")] PressureAssign3 = param::modulations::assign3::PRESSURE.id, #[serde(rename = "key->assign3")] KeyAssign3 = param::modulations::assign3::KEY.id,}
impl RegisterCodec for Target { fn encode(&self) -> StoredValue { StoredValue(*self as u16) }
fn decode(raw: StoredValue) -> Result<Self, DecodeError> { Self::from_repr(raw.0).ok_or_else(|| DecodeError::new::<Self>(raw)) }}
#[author]#[derive(Default)]#[serde(default, rename_all = "camelCase", deny_unknown_fields)]pub struct Sources { /// Cycling-envelope modulation amount (−100% to 100%). pub cycling_envelope: Bipolar, /// Main-envelope modulation amount (−100% to 100%). pub envelope: Bipolar, /// LFO modulation amount (−100% to 100%). pub lfo: Bipolar, /// Pressure modulation amount (−100% to 100%). pub pressure: Bipolar, /// Key/Arp modulation amount (−100% to 100%). pub key: Bipolar,}
#[author]#[derive(Default)]#[serde(default, deny_unknown_fields)]pub struct Destination { /// Modulation amounts routed to this destination. pub sources: Sources,}
#[author]#[serde(default, deny_unknown_fields)]pub struct Assignment1 { #[garde(skip)] /// Destination controlled by this assignment slot. pub target: Target, #[garde(dive)] /// Modulation amounts routed to this destination. pub sources: Sources,}
impl Default for Assignment1 { fn default() -> Self { Self { target: Target::OscillatorShape, sources: Sources::default(), } }}
#[author]#[serde(default, deny_unknown_fields)]pub struct Assignment2 { #[garde(skip)] /// Destination controlled by this assignment slot. pub target: Target, #[garde(dive)] /// Modulation amounts routed to this destination. pub sources: Sources,}
impl Default for Assignment2 { fn default() -> Self { Self { target: Target::CyclingEnvelopeAmount, sources: Sources::default(), } }}
#[author]#[serde(default, deny_unknown_fields)]pub struct Assignment3 { #[garde(skip)] /// Destination controlled by this assignment slot. pub target: Target, #[garde(dive)] /// Modulation amounts routed to this destination. pub sources: Sources,}
impl Default for Assignment3 { fn default() -> Self { Self { target: Target::EnvelopeCutoff, sources: Sources::default(), } }}
bin_enum! { pub enum KeySource { #[default] Linear, Random, }}
#[author]#[derive(Default)]#[serde(default, rename_all = "camelCase", deny_unknown_fields)]pub struct Modulations { /// Linear follows note pitch; Random produces a new random value for each played note. Applies to the Key/Arp matrix source. pub key_source: KeySource, /// Modulation amounts routed to pitch. pub pitch: Destination, /// Modulation amounts routed to the oscillator Wave destination. pub wave: Destination, /// Modulation amounts routed to the oscillator Timbre destination. pub timbre: Destination, /// Modulation amounts routed to filter cutoff. pub cutoff: Destination, /// First assignable modulation slot. pub assign1: Assignment1, /// Second assignable modulation slot. pub assign2: Assignment2, /// Third assignable modulation slot. pub assign3: Assignment3,}
impl Modulations { pub fn validate_for(&self, oscillator: &Oscillator) -> garde::Result { if matches!(oscillator.model, oscillator::Model::Vocoder(_)) && [ (&self.assign1.target, &self.assign1.sources), (&self.assign2.target, &self.assign2.sources), (&self.assign3.target, &self.assign3.sources), ] .iter() .any(|(target, sources)| { **target == Target::OscillatorType && **sources != Sources::default() }) { return Err(garde::Error::new( "Vocoder does not permit oscillator.type modulation", )); } Ok(()) }}
#[cfg(test)]mod tests { use super::*; use crate::parameter::{RegisterCodec, StoredValue};
#[test] fn assignment_targets_are_sparse_parameter_ids_not_bins() { assert_eq!(Target::FilterCutoffHz.encode(), StoredValue(0x0101)); assert_eq!( Target::decode(StoredValue(0x0a02)).unwrap(), Target::LfoPitch ); assert!(Target::decode(StoredValue(0x0902)).is_err()); assert!(Target::decode(StoredValue(0xffff)).is_err()); }}