fix: close five Θ-W6-T1 review minors — headroom figure, knob-face radius, degenerate band clamp
Aligns inKnobFace's hit radius with computeKnob's draw-side min(w,h) rule and adds a non-square-rect test; clamps halfSpan for degenerate waveform bands with a test; fixes stale docs/comments and annotates an uncommitted perf measurement.
This commit is contained in:
@@ -310,7 +310,7 @@ anything for a trigger shape.
|
||||
- `browser_scroll` — scroll + type-to-filter layered over `capture_browser`: vertical scroll offset, scrollbar thumb, thumb-drag mapping, and name-substring search.
|
||||
- `param_slider` — parameter control-panel: vertical stack of TOGGLE (two-segment selector) and SLIDER (horizontal track) rows; maps normalized value to/from handle pixel.
|
||||
- `embed_strip` — compact single-row control layout for embed mode in the track FX chain.
|
||||
- `knob_deck` — pure knob-deck layout + hit-test (FB1): group-box / caption-row / compact-toggle / knob-cell geometry, deterministic whole-group wrap, `DeckLayout` / `DeckHit`. Mirror of `action_bar`/`param_slider`; no LICE or REAPER types. Carries a SECOND hit-test, `hitTestKnobFace`, resolved against the drawn CIRCLES rather than the cell: a double-click reset is aimed at a dial, so the label band and the cell margins must miss where a drag grab deliberately does not, and only a radial resolve can tell the inner curve dial from the outer ring it sits inside. **The cell/knob/label sizes and `sample_bands`' editor floor move as a pair** — wider cells need a wider floor width or the deck wraps to a fourth row. A group carries TWO caption-toggle slots, laid right-to-left: the second exists because a group whose knob row is wider than its caption row has caption slack a toggle can occupy for free, and the deck has six pixels of headroom on its first row at the editor's floor width — a `rowToggle` would widen the GROUP and wrap the deck to a fourth row, past what the minimum window holds. **A group's cell run is a RESERVED WIDTH, not a fixed cell size**: a `-1` id reserves one cell's width without a cell, and the cells present divide the whole run between them at one uniform integer width (residue in symmetric end margins). That is what lets a mode flip drop controls from a face — Trigger's AMP and FILTER ENV lose their Sustain/Release stages — without either reflowing the deck or leaving dead slots in the box; a face with fewer controls simply gets roomier cells. Do not reintroduce fixed-width cells with blank slots.
|
||||
- `knob_deck` — pure knob-deck layout + hit-test (FB1): group-box / caption-row / compact-toggle / knob-cell geometry, deterministic whole-group wrap, `DeckLayout` / `DeckHit`. Mirror of `action_bar`/`param_slider`; no LICE or REAPER types. Carries a SECOND hit-test, `hitTestKnobFace`, resolved against the drawn CIRCLES rather than the cell: a double-click reset is aimed at a dial, so the label band and the cell margins must miss where a drag grab deliberately does not, and only a radial resolve can tell the inner curve dial from the outer ring it sits inside. **The cell/knob/label sizes and `sample_bands`' editor floor move as a pair** — wider cells need a wider floor width or the deck wraps to a fourth row. A group carries TWO caption-toggle slots, laid right-to-left: the second exists because a group whose knob row is wider than its caption row has caption slack a toggle can occupy for free, and the deck has fourteen pixels of headroom on its first row at the editor's floor width — a `rowToggle` would widen the GROUP and wrap the deck to a fourth row, past what the minimum window holds. **A group's cell run is a RESERVED WIDTH, not a fixed cell size**: a `-1` id reserves one cell's width without a cell, and the cells present divide the whole run between them at one uniform integer width (residue in symmetric end margins). That is what lets a mode flip drop controls from a face — Trigger's AMP and FILTER ENV lose their Sustain/Release stages — without either reflowing the deck or leaving dead slots in the box; a face with fewer controls simply gets roomier cells. Do not reintroduce fixed-width cells with blank slots.
|
||||
- `deck_values` — the deck's control-id ↔ parameter-set BINDING and its display units, split
|
||||
from the editor shell on the same axis `deck_groups` was split from `knob_deck`: `deck_groups`
|
||||
says which controls exist, this says what each one's value MEANS. Holds `deckParamNorm` /
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// The bank is read over the live-state seam, audio over the file seam; both raw inputs
|
||||
// cross the bridge/file boundary in the shell, everything after (bank parse via the shared
|
||||
// bank_book JSON path, sample pick, channel policy, SampleData build) is pure and
|
||||
// unit-tested here. Links bank_book, wav_codec, and play_params (all pure) — deliberately
|
||||
// NOT the voice engine: the build's product is plain SampleData.
|
||||
// unit-tested here. Links bank_book, wav_codec, play_params, and play_seconds (all pure) —
|
||||
// deliberately NOT the voice engine: the build's product is plain SampleData.
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
@@ -193,7 +193,9 @@ DeckHit hitTestDeck(const DeckLayout& layout, int x, int y) {
|
||||
bool inKnobFace(const Rect& knob, int x, int y) {
|
||||
const double dx = x - (knob.x + knob.width / 2.0);
|
||||
const double dy = y - (knob.y + knob.height / 2.0);
|
||||
const double r = knob.width / 2.0;
|
||||
// Matches computeKnob's radius rule (param_slider.cpp) so the hit rule never claims more
|
||||
// circle than is actually drawn when a caller's rect is non-square (e.g. a squashed chrome row).
|
||||
const double r = (std::min)(knob.width, knob.height) / 2.0;
|
||||
return dx * dx + dy * dy < r * r;
|
||||
}
|
||||
|
||||
|
||||
@@ -153,9 +153,11 @@ struct DeckFaceHit {
|
||||
bool inner = false; // inside the concentric inner disc
|
||||
};
|
||||
|
||||
// A point inside the circle inscribed in `knob`, boundary-EXCLUSIVE. THE target rule for the
|
||||
// reset gesture wherever a radial knob is drawn — the deck's own faces and the chrome's
|
||||
// preview-velocity dial both resolve through it, so one gesture cannot grow two target rules.
|
||||
// A point inside the circle inscribed in `knob` — radius is min(width, height)/2, same rule as
|
||||
// computeKnob's draw-side circle, so a non-square rect can never claim a hit past the drawn disc
|
||||
// — boundary-EXCLUSIVE. THE target rule for the reset gesture wherever a radial knob is drawn —
|
||||
// the deck's own faces and the chrome's preview-velocity dial both resolve through it, so one
|
||||
// gesture cannot grow two target rules.
|
||||
bool inKnobFace(const Rect& knob, int x, int y);
|
||||
|
||||
// Resolved against the drawn CIRCLES, not the cell: a reset is aimed at a dial, so the label
|
||||
|
||||
@@ -102,7 +102,13 @@ WaveformBand waveformBand(int bandTop, int bandHeight) {
|
||||
b.top = bandTop;
|
||||
b.height = bandHeight;
|
||||
b.midY = bandTop + bandHeight / 2;
|
||||
b.halfSpan = (bandHeight / 2) - 2; // matches drawWaveform's 2px vertical breathing room
|
||||
// matches drawWaveform's 2px vertical breathing room; clamped at 0 so a degenerate band
|
||||
// (height <= 3, where this would otherwise go negative) can't flip a positive peak below
|
||||
// the zero line.
|
||||
const int rawHalfSpan = (bandHeight / 2) - 2;
|
||||
b.halfSpan = rawHalfSpan > 0 ? rawHalfSpan : 0;
|
||||
// lo/hi (waveformColumnSpan) are midY-height/2 .. midY+height/2-1 — asymmetric by one px for
|
||||
// even heights. Unreachable while |compressAmplitudeForDisplay| <= 1, so left as-is.
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user