// deck_values.h — the deck's control-id <-> parameter-set BINDING and its display units: the // normalized 0..1 a knob shows, the write back into the stored seconds/fractions/positions, the // double-click reset, and the ms time-constant formatter. Split from the editor shell so the // whole domain map is provable without a host; deck_groups owns WHICH controls exist, this owns // what each one's value MEANS. #pragma once #include #include "core/instrument/map/play_seconds.h" // PlaySeconds (the deck's edit target) #include "core/instrument/ui/deck_groups.h" // DeckParam #include "core/instrument/ui/envelope_overlay.h" // kGateStageMaxSeconds #include "core/instrument/ui/param_taper.h" // UnitCategory + the shared tapers namespace reasampler::instrument::ui { using map::PlaySeconds; // Every stage-time knob spans [0, kEnvTimeMaxSeconds] seconds — rate-free, exactly what the // parameter set stores. An ALIAS of the overlay's schematic domain, which is itself an alias of // the taper's; param_taper.h owns why the number has one home. inline constexpr double kEnvTimeMaxSeconds = kGateStageMaxSeconds; // Pitch depth throw: +/-kVelocityPitchRangeSemitones, centred. The one throw the pitch // envelope's peak and the velocity->pitch curve's full scale both speak (play_params.h). inline constexpr double kPitchDepthMaxSemis = kVelocityPitchRangeSemitones; // Key-track knob ceiling (0..200%), shared by the pitch and filter key-track controls. inline constexpr double kKeyTrackMax = 2.0; // The normalized [0,1] a control shows: stage times through the shared time taper, levels and // fractions as-is, signed depths through the centre-expanded depth taper, curve exponents over // their logarithmic travel. Controls backed by per-instance state rather than the parameter set // (voice count, master gain, the pitch key-track scalar, preview velocity) are not here — the // shell reads those from the processor. double deckParamNorm(DeckParam id, const PlaySeconds& play); // Applies a committed interaction: a knob's normalized `value`, or a toggle's `segment` (0/1). // Mutates `play` in place, touching exactly the one field the control names. void setDeckParam(DeckParam id, PlaySeconds& play, double value, int segment); // Resets `id` to its default. The default IS what a fresh PlaySeconds carries, so there is no // second table of defaults to drift from the real one, and the value is COPIED rather than // round-tripped through norm -> value. That bypass is MANDATORY: a reset must land on the stored // default bit for bit, and no round trip through a log taper over a non-power-of-two ceiling can // promise that for every control. Never "simplify" it back into a round trip. // For knob-valued controls — a toggle has no reset gesture. void resetDeckParam(DeckParam id, PlaySeconds& play); // The ADDRESS of the one stored field `id` owns — the mechanism resetDeckParam bypasses the taper // with. Exposed beyond that one caller so a test can verify a reset (or any other mutation) // against the actual stored field rather than its normalized read-back, which deckParamNorm does // not guarantee is injective. Null for a control with no reset gesture (a toggle, radio, or // curve-popup cell) or one whose value lives outside PlaySeconds (master gain, key-track). double* deckDoubleField(DeckParam id, PlaySeconds& p); // The filter's four tone controls store their normalized position as float — see deckFloatField's // definition for why that is a second resolver rather than a widened first one. float* deckFloatField(DeckParam id, PlaySeconds& p); // THE snap-unit table: which whole unit Shift snaps each control to. Includes the deck's // processor-side ids (voice count, master gain), which have no entry in the two functions above // because their VALUE lives outside the parameter set — the unit does not. UnitCategory deckParamUnit(DeckParam id); // Applies that snap to a control's normalized value. Snapping happens in the DISPLAYED unit, so // this is where each control's full scale enters: 0..100 %, 0..200 % and +/-100 % all snap to a // whole displayed percent and therefore take different norm steps. double snapDeckParamNorm(DeckParam id, double norm); // A time constant as MILLISECONDS, e.g. "12 ms". Never switches to seconds: the editor reads in // one unit so two stage times are comparable at a glance. Sub-10 ms keeps one decimal so a short // attack is not rounded to a bare "0 ms". Writes at most `len` bytes including the terminator. void formatEnvTimeMs(double seconds, char* buf, std::size_t len); } // namespace reasampler::instrument::ui