//! 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 = std::result::Result>; // 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 { let mode = param::decode_bin::(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::(param::oscillator::TYPE)?, ), ( param::cycling_envelope::MODE, reader.value::(param::cycling_envelope::MODE)?, ), (param::lfo::SYNC, reader.value::(param::lfo::SYNC)?), ( param::keyboard::GLIDE_MODE, reader.value::(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::(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::(parameter)? != initial { return Err(io::Error::other( "oscillator, envelope mode, or LFO clock changed during capture; retry get", ) .into()); } } spec.validate()?; Ok((spec, defaults)) }