4f30124ef7
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.
105 lines
6.2 KiB
C++
105 lines
6.2 KiB
C++
// 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
|