S12: editor scale + S15/S16 control surfaces
Pure browser_scroll/note_entry/param_slider modules (+ CTest) for scroll, type-to-filter search, numeric note entry, and the AHDSR/Trigger/pitch-engine/ pitch-env control panel. Editor shell draws + routes through them; params edit the selected zone's ZonePlayParams via commitAndReload.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// 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 <vector>
|
||||
|
||||
#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<ControlRow> layoutControls(const Rect& panel,
|
||||
const std::vector<ControlDesc>& 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<ControlRow>& rows, int x, int y);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
Reference in New Issue
Block a user