// param_slider.h — PURE control-surface layout + hit-test + value<->pixel mapping for the // S12/S15/S16 editor parameter panel. NO VST3, NO REAPER, NO SWELL/LICE types at the // boundary, and — deliberately — NO sampler_core / sample_map engine types either. The // mirror of keyboard_strip / waveform_view / mode_switch: the fiddly slider-track and // toggle-segment arithmetic lives here, unit-tested outside the DAW, while the editor shell // draws each row (label + track/segments + handle) and routes clicks/drags into these // functions, owning the control-id -> engine-param binding + the value DOMAIN mapping. // // WHY IT EXISTS (S12 + the S15/S16 control surfaces deferred here). The setup / Zones surface // grows a stack of parameter controls: the S15 play-mode toggle (Gate|Trigger), the AHDSR // amp-envelope sliders (attack/hold/decay/sustain/release), the Trigger %-length + fade // controls, the S16 Varispeed|Preserve engine toggle, and the AD pitch-envelope // enable/attack/decay/depth. They are two shapes only — a two-segment TOGGLE and a // horizontal SLIDER — laid out as a vertical stack of fixed-height rows. This module lays out // that stack and maps a slider's NORMALIZED value (0..1) to/from its handle pixel; the shell // converts each control's engine value (frames, seconds, a fraction, a signed semitone // depth) to/from that 0..1 with its own domain knowledge (this module stays engine-free so it // tests without the audio core). // // It reuses editor_geometry's Rect + contains() (one shared geometry idiom). #pragma once #include #include "editor_geometry.h" // Rect, contains — one shared geometry idiom namespace reasampler::vst { // Fixed control-panel metrics, exposed so the shell and tests agree. inline constexpr int kControlRowHeight = 22; // one control row (incl. its inter-row gap) inline constexpr int kControlRowGap = 4; // vertical gap below each row inline constexpr int kControlLabelWidth = 92; // the label column at the row's left inline constexpr int kSliderHandleWidth = 8; // the draggable slider handle width (px) inline constexpr int kToggleSegments = 2; // a toggle is always two segments // A control is one of two shapes. Toggle = a two-segment selector (the active segment // highlights); Slider = a horizontal track with a draggable handle over a 0..1 value. enum class ControlKind { Toggle, Slider }; // One control the shell places in the panel, in stack order. `id` is the shell's own control // identifier (an int the shell casts from its ControlId enum) returned by the hit-test so the // shell routes the interaction to the right engine param — this module never interprets it. struct ControlDesc { int id = 0; ControlKind kind = ControlKind::Slider; }; // The laid-out geometry of one control row: its full row rect plus the interactive sub-rect // (the track for a Slider, the whole control area for a Toggle — the shell splits a Toggle // into segments via toggleSegmentRect). `index` is the control's position in the stack. struct ControlRow { int id = 0; ControlKind kind = ControlKind::Slider; Rect row; // the full row (label column + control column) Rect label; // the label column at the left Rect control; // the control column to the right of the label (track / toggle area) }; // Lay out `controls` as a vertical stack of fixed-height rows inside `panel`, top-down. Each // row is kControlRowHeight tall with kControlRowGap below it; the label column takes the left // kControlLabelWidth (clamped so it never exceeds the panel), the control column the rest. A // row whose top falls past the panel bottom is still returned (the shell clips at paint / // suppresses it) so the stack geometry is deterministic regardless of panel height. An empty // control list or a degenerate panel yields an empty vector. Pure. std::vector layoutControls(const Rect& panel, const std::vector& controls); // The rect of segment `seg` (0..kToggleSegments-1) within a toggle control's `control` rect, // splitting it into kToggleSegments equal segments left-to-right (the last absorbs any width // remainder, mirror of mode_switch's segment split). An out-of-range segment or a degenerate // control rect yields an empty rect. Pure. Rect toggleSegmentRect(const Rect& control, int seg); // The toggle segment a point lands on within a toggle control's `control` rect, or -1 for a // miss (outside the control area). Pure. int toggleSegmentHitTest(const Rect& control, int x, int y); // The slider track sub-rect inside a slider control's `control` rect: the control inset so the // handle (kSliderHandleWidth) stays fully within the control at value 0 and 1 (a half-handle // margin at each end). The handle CENTER ranges across [track.left, track.right] as the value // ranges [0,1]. The shell draws the track fill + handle here. A degenerate control yields an // empty rect. Pure. Rect sliderTrackRect(const Rect& control); // The handle rect for a slider at normalized `value` (clamped to [0,1]) within `control`: a // kSliderHandleWidth-wide bar centered at the value's position along sliderTrackRect. A // degenerate control yields an empty rect. Pure — the inverse of valueAtPoint. Rect sliderHandleRect(const Rect& control, double value); // Map a point x to a normalized slider value [0,1] within `control` (the handle-center range). // x at/left of the track start -> 0; at/right of the end -> 1; linear between. A degenerate // track (zero movable span) -> 0. Pure — the inverse of sliderHandleRect's position map; the // shell converts the returned 0..1 into its engine domain (frames/seconds/fraction/semitones). double valueAtPoint(const Rect& control, int x); // The control a point lands on, given the laid-out `rows`. Returns the control id (ControlDesc // id) whose interactive area (a Slider's track, a Toggle's whole control area) contains the // point, or -1 for a miss (a gap, the label column, or outside every row). The FIRST matching // row wins (rows never overlap, so at most one matches). Pure — the shell's routing entry // point: on a hit it reads the value (valueAtPoint / toggleSegmentHitTest) and commits. int controlAtPoint(const std::vector& rows, int x, int y); } // namespace reasampler::vst