fix(S12 review): per-zone A/D/S/R reaches voice; zones payload v4 + v3 back-compat
Voice::start reads full zone ADSR instead of folding only holdFrames into instrument-wide gateAdsr. Wire format bumped to v4 (four new fields); v3 blobs lift A/D/S/R to kTier0Nominal* constants. Single-capture editor controls now reachable. Minor comment and geometry fixes.
This commit is contained in:
@@ -299,10 +299,13 @@ void ReaSamplerEditor::upsertPickedOverride(const SetupMarkers& m) {
|
||||
|
||||
namespace {
|
||||
// The S12/S15/S16 control-surface value DOMAINS (the shell owns these — param_slider is
|
||||
// engine-free and maps only 0..1). Time sliders span [0, max] frames at a nominal rate so a
|
||||
// full-throw reaches a musically generous ceiling; the exact wall-clock is DAW-verified. These
|
||||
// are build-time residuals (one place to retune), not persisted.
|
||||
constexpr double kEnvTimeMaxFrames = 2.0 * 44100.0; // AHDSR A/H/D/R + pitch A/D throw ceiling
|
||||
// engine-free and maps only 0..1). Time sliders span [0, max] frames at the NOMINAL 44100 Hz
|
||||
// rate; the stored frame count is host-rate-independent, so at other DAW rates the same slider
|
||||
// position maps to a slightly different wall-clock duration. The ceiling is kept nominal-only
|
||||
// because the host rate is not reachable inside the editor without a processor callback, and the
|
||||
// approximation is musically negligible (±1-2 ms at typical rates). Build-time residual — one
|
||||
// place to retune; not persisted.
|
||||
constexpr double kEnvTimeMaxFrames = 2.0 * 44100.0; // AHDSR A/H/D/R + pitch A/D throw ceiling (44100 nominal)
|
||||
constexpr double kPitchDepthMaxSemis = 24.0; // AD pitch depth throw: +/-24 st, centered
|
||||
|
||||
double clamp01(double v) { return v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v); }
|
||||
@@ -600,12 +603,11 @@ Rect zonesStripArea(const EditorBands& bands) {
|
||||
|
||||
// The S12 numeric-entry field ROW area inside the Zones legend: a band to the right of the
|
||||
// sample label on the legend row. Three equal fields (low/high/root) tile it. Both draw +
|
||||
// hit-test use this single formula so they never drift.
|
||||
// hit-test use this single formula so they never drift. Anchored off zonesStripArea.bottom so
|
||||
// the legend top tracks the strip bottom without re-inlining the strip arithmetic here.
|
||||
Rect noteEntryFieldsArea(const EditorBands& bands) {
|
||||
const Rect strip = Rect{bands.content.left + 8,
|
||||
bands.content.top + 4 + 20 + 12 + kStripBandHeight,
|
||||
bands.content.right - 8, 0};
|
||||
const int top = strip.top + 8; // legendTop (== zonesStripArea.bottom + 8)
|
||||
const int stripBottom = zonesStripArea(bands).bottom;
|
||||
const int top = stripBottom + 8; // legendTop (== zonesStripArea.bottom + 8)
|
||||
return Rect{bands.content.left + 8 + 128, top, bands.content.right - 8, top + 18};
|
||||
}
|
||||
|
||||
@@ -976,8 +978,13 @@ void ReaSamplerEditor::paintZones(LICE_IBitmap* bmp, int w, int h) {
|
||||
}
|
||||
|
||||
// The S12/S15/S16 parameter surface for the selected zone (play mode + AHDSR / Trigger +
|
||||
// pitch engine + AD pitch envelope). Only when a zone is selected.
|
||||
if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
// pitch engine + AD pitch envelope). Shown for an explicit zone selection OR for the
|
||||
// single-capture face when the map is empty but a capture is picked (S15-F2 lean: the
|
||||
// single capture is already a one-zone map — one storage site serves both).
|
||||
const bool haveControlTarget =
|
||||
(selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) ||
|
||||
(map_.zones.empty() && !selectedId_.empty());
|
||||
if (haveControlTarget) {
|
||||
paintControls(bmp, zonesControlPanel(computeBands(w, h)));
|
||||
}
|
||||
}
|
||||
@@ -989,8 +996,18 @@ struct ControlLabels { const char* label; const char* seg0; const char* seg1; };
|
||||
} // namespace
|
||||
|
||||
void ReaSamplerEditor::paintControls(LICE_IBitmap* bmp, const Rect& panel) {
|
||||
if (selectedZone_ < 0 || selectedZone_ >= static_cast<int>(map_.zones.size())) return;
|
||||
const ZonePlayParams play = map_.zones[static_cast<std::size_t>(selectedZone_)].play;
|
||||
// Resolve the play params: from the selected zone when one is chosen, or from the
|
||||
// PerformanceZone product defaults when the map is empty but a capture is picked
|
||||
// (S15-F2 lean: the single-capture face shares the same storage site as a one-zone map;
|
||||
// see paintZones for the gate that reaches here).
|
||||
ZonePlayParams play;
|
||||
if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
play = map_.zones[static_cast<std::size_t>(selectedZone_)].play;
|
||||
} else if (map_.zones.empty() && !selectedId_.empty()) {
|
||||
play = PerformanceZone{}.play; // product defaults (Gate + Preserve + tier-0 ADSR)
|
||||
} else {
|
||||
return; // no control target
|
||||
}
|
||||
const std::vector<ControlDesc> descs = controlDescs(play);
|
||||
const std::vector<ControlRow> rows = layoutControls(panel, descs);
|
||||
|
||||
@@ -1259,7 +1276,29 @@ void ReaSamplerEditor::onMouseDown(int x, int y) {
|
||||
entryField_ = -1; // a click elsewhere in the Zones view cancels an in-progress entry
|
||||
|
||||
// The S12/S15/S16 parameter panel: a toggle segment flips at once (commit); a slider grab
|
||||
// starts a live drag (commit on release). Only when a zone is selected.
|
||||
// starts a live drag (commit on release). Reachable for an explicit zone selection OR for
|
||||
// the single-capture face when the map is empty but a capture is picked (S15-F2 lean).
|
||||
// In the empty-map+picked case, auto-create a full-keyboard zone for selectedId_ on first
|
||||
// control interaction (same path as "+ Add Zone"), then apply the control — the zone is
|
||||
// committed as part of the control edit.
|
||||
if (selectedZone_ < 0 && map_.zones.empty() && !selectedId_.empty()) {
|
||||
// Synthesize a probe layout with the product defaults to see if the click is in the
|
||||
// panel before committing to creating the zone.
|
||||
const Rect panel = zonesControlPanel(bands);
|
||||
const ZonePlayParams defaultPlay = PerformanceZone{}.play;
|
||||
const std::vector<ControlDesc> probeDescs = controlDescs(defaultPlay);
|
||||
const std::vector<ControlRow> probeRows = layoutControls(panel, probeDescs);
|
||||
if (controlAtPoint(probeRows, x, y) >= 0) {
|
||||
// The click lands in the control panel — materialize the zone now.
|
||||
PerformanceZone z;
|
||||
z.sampleId = selectedId_;
|
||||
z.lowNote = 0;
|
||||
z.highNote = 127;
|
||||
map_.zones.push_back(z);
|
||||
selectedZone_ = 0;
|
||||
// Fall through to the control handler below which will process the click.
|
||||
}
|
||||
}
|
||||
if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
PerformanceZone& z = map_.zones[static_cast<std::size_t>(selectedZone_)];
|
||||
const Rect panel = zonesControlPanel(bands);
|
||||
|
||||
Reference in New Issue
Block a user