Q-W2v: split VST god-modules — editor 8 face-axis TUs (+pure layout hoist), processor 3 TUs, component_state_io codec split (extension drops the voice engine), zone_params.h, core/wire putLE; formats frozen, 61/61 green
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
// editor_controls.cpp — the ReaSamplerEditor's PARAMETER PLUMBING (Q-W2v split of
|
||||
// reasampler_editor.cpp, T4-11): the control-value domain maps (controlValue /
|
||||
// applyControl — seconds/fraction/frames <-> normalized 0..1), the r11 knob-deck
|
||||
// group descriptors + control-id<->value binding, the S-VIEW-3 envelope pack/unpack
|
||||
// (the TRIGGER SEAM converter), the curve-popup target resolution, and applyZoneControl.
|
||||
// Value logic only — no painting, no window plumbing.
|
||||
|
||||
#include "shell/instrument/reasampler_editor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio> // snprintf (deck value labels)
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/instrument/engine/master_gain.h" // r11 master-gain dB<->linear<->knob taper (FB1)
|
||||
#include "core/instrument/map/trigger_seam.h" // triggerPlayLength / fade fraction converters (S-VIEW-3)
|
||||
#include "core/util/clamp01.h"
|
||||
#include "shell/instrument/editor_internal.h" // DeckGroup ids
|
||||
#include "shell/instrument/reasampler_processor.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
using namespace reasampler::instrument::map; // ZonePlaySeconds vocabulary + trigger_seam converters
|
||||
using instrument::engine::formatMasterGainLabel;
|
||||
using instrument::engine::masterGainLinearFromNorm;
|
||||
using instrument::engine::masterGainNormFromLinear;
|
||||
using util::clamp01;
|
||||
|
||||
namespace {
|
||||
// The S12/S15/S16 control-surface value DOMAINS (the shell owns these — param_slider is
|
||||
// engine-free and maps only 0..1). WALL-CLOCK time sliders (AHDSR A/H/D/R, pitch env A/D) span
|
||||
// [0, kEnvTimeMaxSeconds] SECONDS — rate-free, exactly what the zone stores; the keymap build
|
||||
// resolves seconds->frames at the live rate. SOURCE-timeline fade sliders (Trigger fade-in/out)
|
||||
// STORE source frames (PLAN.md §S15 — never a wall-clock second; the storage domain is
|
||||
// settled-correct and unchanged), but the knob's FULL-SCALE THROW is a wall-clock intent —
|
||||
// kFadeMaxSeconds resolved against the live rate at use (fadeMaxFrames(), Q-W0 T3-03; the
|
||||
// prior 88200-frame constant baked 2 s x 44.1 kHz into src/, against the no-hardcoded-rate
|
||||
// ruling). Build-time residual — one place to retune; not persisted.
|
||||
constexpr double kEnvTimeMaxSeconds = 2.0; // AHDSR A/H/D/R + pitch A/D throw ceiling (seconds)
|
||||
constexpr double kFadeMaxSeconds = 2.0; // Trigger fade throw ceiling (wall-clock)
|
||||
constexpr double kPitchDepthMaxSemis = 24.0; // AD pitch depth throw: +/-24 st, centered
|
||||
constexpr double kKeyTrackMax = 2.0; // S-VIEW-6 key-track slider ceiling (0..200%)
|
||||
|
||||
} // namespace
|
||||
|
||||
double ReaSamplerEditor::controlValue(int id, const ZonePlaySeconds& play) const {
|
||||
// Wall-clock seconds -> normalized over the seconds ceiling; source frames -> normalized over
|
||||
// the rate-resolved frames ceiling (T3-03). Two domains, kept explicit so neither leaks a rate.
|
||||
// A stored fade exceeding fadeMaxFrames() at the current host rate reads as norm 1.0 (clamp01
|
||||
// pins it) and gets rewritten down on the next knob touch — deliberate, matching the old
|
||||
// fixed-ceiling clamp behavior in kind, just rate-dependent now instead of fixed at 88200.
|
||||
const double fadeMax = fadeMaxFrames();
|
||||
const auto secToNorm = [](double s) { return clamp01(s / kEnvTimeMaxSeconds); };
|
||||
const auto framesToNorm = [fadeMax](std::int64_t f) {
|
||||
// Ceiling unavailable (rate not yet known): inert until fadeMaxFrames() resolves.
|
||||
return fadeMax > 0.0 ? clamp01(static_cast<double>(f) / fadeMax) : 0.0;
|
||||
};
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kPlayMode: return play.playMode == PlayMode::Trigger ? 1.0 : 0.0;
|
||||
case ParamControl::kPitchEngine: return play.pitchEngine == PitchEngine::Preserve ? 1.0 : 0.0;
|
||||
case ParamControl::kAttack: return secToNorm(play.adsr.attackSeconds);
|
||||
case ParamControl::kHold: return secToNorm(play.adsr.holdSeconds);
|
||||
case ParamControl::kDecay: return secToNorm(play.adsr.decaySeconds);
|
||||
case ParamControl::kSustain: return clamp01(play.adsr.sustainLevel);
|
||||
case ParamControl::kRelease: return secToNorm(play.adsr.releaseSeconds);
|
||||
case ParamControl::kTrigLength: return clamp01(play.trigger.lengthFraction);
|
||||
case ParamControl::kTrigFadeIn: return framesToNorm(play.trigger.fadeInFrames);
|
||||
case ParamControl::kTrigFadeOut: return framesToNorm(play.trigger.fadeOutFrames);
|
||||
case ParamControl::kPitchEnvEnable:return play.pitchEnv.enabled ? 1.0 : 0.0;
|
||||
case ParamControl::kPitchEnvAttack:return secToNorm(play.pitchEnv.attackSeconds);
|
||||
case ParamControl::kPitchEnvDecay: return secToNorm(play.pitchEnv.decaySeconds);
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
// Signed depth centered at 0.5 (0.5 == 0 semitones).
|
||||
return clamp01(0.5 + play.pitchEnv.peakSemitones / (2.0 * kPitchDepthMaxSemis));
|
||||
default: return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::applyControl(int id, ZonePlaySeconds& play, double value,
|
||||
int segment) const {
|
||||
const double fadeMax = fadeMaxFrames(); // T3-03: rate-resolved knob full-scale
|
||||
const auto normToSec = [](double v) { return clamp01(v) * kEnvTimeMaxSeconds; };
|
||||
const auto normToFrames = [fadeMax](double v) -> std::int64_t {
|
||||
// Ceiling unavailable (rate not yet known): inert until fadeMaxFrames() resolves.
|
||||
if (fadeMax <= 0.0) return 0;
|
||||
return static_cast<std::int64_t>(clamp01(v) * fadeMax + 0.5);
|
||||
};
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kPlayMode:
|
||||
play.playMode = (segment == 1) ? PlayMode::Trigger : PlayMode::Gate;
|
||||
break;
|
||||
case ParamControl::kPitchEngine:
|
||||
play.pitchEngine = (segment == 1) ? PitchEngine::Preserve : PitchEngine::Varispeed;
|
||||
break;
|
||||
case ParamControl::kAttack: play.adsr.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kHold: play.adsr.holdSeconds = normToSec(value); break;
|
||||
case ParamControl::kDecay: play.adsr.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kSustain: play.adsr.sustainLevel = clamp01(value); break;
|
||||
case ParamControl::kRelease: play.adsr.releaseSeconds = normToSec(value); break;
|
||||
case ParamControl::kTrigLength:
|
||||
// lengthFraction is (0,1]; keep a small floor so a zero-length trigger never plays nothing.
|
||||
play.trigger.lengthFraction = (std::max)(0.01, clamp01(value));
|
||||
break;
|
||||
case ParamControl::kTrigFadeIn: play.trigger.fadeInFrames = normToFrames(value); break;
|
||||
case ParamControl::kTrigFadeOut: play.trigger.fadeOutFrames = normToFrames(value); break;
|
||||
case ParamControl::kPitchEnvEnable:
|
||||
play.pitchEnv.enabled = (segment == 1);
|
||||
break;
|
||||
case ParamControl::kPitchEnvAttack: play.pitchEnv.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kPitchEnvDecay: play.pitchEnv.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
play.pitchEnv.peakSemitones = (clamp01(value) - 0.5) * 2.0 * kPitchDepthMaxSemis;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::liveSampleRate() const {
|
||||
return processor_ ? processor_->sampleRate() : 0.0;
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::fadeMaxFrames() const {
|
||||
// T3-03: the Trigger-fade knob's full-scale throw is kFadeMaxSeconds (2 s wall-clock)
|
||||
// resolved against the live rate — the SAME time base the envelope overlay already uses
|
||||
// to place these source-frame fades on screen (totalSeconds = frames / liveSampleRate()),
|
||||
// and the rate captures are made at (the capture path renders at the project rate).
|
||||
// Pre-setupProcessing the rate is still 0: rather than substitute a literal rate (the
|
||||
// exact residue T3-03 removed), bail the same way paintEnvelopeOverlay does (~line 1396) —
|
||||
// callers treat a <= 0 return as "ceiling unavailable yet" and degrade the knob to inert
|
||||
// rather than guess a rate. Storage stays SOURCE FRAMES — this resolves the UI ceiling only.
|
||||
const double rate = liveSampleRate();
|
||||
if (rate <= 0.0) return 0.0;
|
||||
return kFadeMaxSeconds * rate;
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::previewVelocity01() const {
|
||||
if (!processor_) return static_cast<double>(kPreviewVelocityDefault) / 127.0;
|
||||
return static_cast<double>(processor_->previewVelocity()) / 127.0;
|
||||
}
|
||||
|
||||
std::vector<DeckGroupDesc> ReaSamplerEditor::zoneDeckGroupDescs(const ZonePlaySeconds& play) const {
|
||||
// The PER-ZONE groups — the deck grammar both surfaces share (FB2: the Zone panel renders
|
||||
// exactly these; the Sample face appends the per-instance groups in deckGroupDescs).
|
||||
// Group widths are MODE-INDEPENDENT: AMP ENVELOPE reserves its 5-cell Gate width (Trigger
|
||||
// leaves two blank cells), so a Gate<->Trigger flip repopulates in place and never reflows
|
||||
// the neighbouring groups (r11).
|
||||
std::vector<DeckGroupDesc> out;
|
||||
{
|
||||
DeckGroupDesc amp;
|
||||
amp.id = kGroupAmpEnv;
|
||||
amp.captionWidth = 78;
|
||||
amp.captionToggle = {static_cast<int>(ParamControl::kPlayMode), 44};
|
||||
if (play.playMode == PlayMode::Gate) {
|
||||
amp.cellIds = {static_cast<int>(ParamControl::kAttack),
|
||||
static_cast<int>(ParamControl::kHold),
|
||||
static_cast<int>(ParamControl::kDecay),
|
||||
static_cast<int>(ParamControl::kSustain),
|
||||
static_cast<int>(ParamControl::kRelease)};
|
||||
} else {
|
||||
// Trigger, TIME-ORDERED left-to-right (r11: Fade In · Length % · Fade Out —
|
||||
// matches the drawn envelope), plus the two reserved blanks.
|
||||
amp.cellIds = {static_cast<int>(ParamControl::kTrigFadeIn),
|
||||
static_cast<int>(ParamControl::kTrigLength),
|
||||
static_cast<int>(ParamControl::kTrigFadeOut), -1, -1};
|
||||
}
|
||||
out.push_back(std::move(amp));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc pitch;
|
||||
pitch.id = kGroupPitch;
|
||||
pitch.captionWidth = 38;
|
||||
pitch.captionToggle = {static_cast<int>(ParamControl::kPitchEngine), 48};
|
||||
pitch.cellIds = {static_cast<int>(ParamControl::kKeyTrack)};
|
||||
out.push_back(std::move(pitch));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc penv;
|
||||
penv.id = kGroupPitchEnv;
|
||||
penv.captionWidth = 58;
|
||||
penv.captionToggle = {static_cast<int>(ParamControl::kPitchEnvEnable), 32};
|
||||
penv.cellIds = {static_cast<int>(ParamControl::kPitchEnvAttack),
|
||||
static_cast<int>(ParamControl::kPitchEnvDecay),
|
||||
static_cast<int>(ParamControl::kPitchEnvDepth)};
|
||||
out.push_back(std::move(penv));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<DeckGroupDesc> ReaSamplerEditor::deckGroupDescs(const ZonePlaySeconds& play) const {
|
||||
// The full Sample-face deck: the shared per-zone groups + the per-instance VOICE + MASTER
|
||||
// groups. VOICE + MASTER are the FB1 homes for the provisional voice-deck controls and the
|
||||
// post-mixer gain — the r11 spec predates both; per-instance state (ComponentState) stays
|
||||
// OFF the Zone panel (FB2), so they are appended here, not in zoneDeckGroupDescs.
|
||||
std::vector<DeckGroupDesc> out = zoneDeckGroupDescs(play);
|
||||
{
|
||||
DeckGroupDesc voice;
|
||||
voice.id = kGroupVoice;
|
||||
voice.captionWidth = 38;
|
||||
voice.captionToggle = {static_cast<int>(ParamControl::kVoiceMode), 40};
|
||||
voice.cellIds = {static_cast<int>(ParamControl::kVoiceCount)};
|
||||
voice.rowToggle = {static_cast<int>(ParamControl::kMonoTrigger), 44};
|
||||
out.push_back(std::move(voice));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc master;
|
||||
master.id = kGroupMaster;
|
||||
master.captionWidth = 46;
|
||||
master.cellIds = {static_cast<int>(ParamControl::kMasterGain)};
|
||||
out.push_back(std::move(master));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::deckControlNorm(int id, const PerformanceZone& zone) const {
|
||||
if (id == -2) return previewVelocity01(); // the cluster's preview-velocity knob
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kKeyTrack:
|
||||
return clamp01(zone.keyTrack / kKeyTrackMax);
|
||||
case ParamControl::kVoiceCount:
|
||||
return clamp01(static_cast<double>(voiceCount_ - kMinVoiceCount) /
|
||||
static_cast<double>(kMaxVoiceCount - kMinVoiceCount));
|
||||
case ParamControl::kMasterGain:
|
||||
return masterGainNormFromLinear(processor_ ? processor_->masterGainLinear() : 1.0);
|
||||
default:
|
||||
return controlValue(id, zone.play);
|
||||
}
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::applyDeckKnob(int zoneIndex, int id, double norm) {
|
||||
if (!processor_) return;
|
||||
norm = clamp01(norm);
|
||||
if (id == -2) {
|
||||
// Preview velocity: live processor write (persisted per-instance; the setter clamps
|
||||
// to MIDI 1..127 so the knob's bottom still strikes audibly).
|
||||
processor_->setPreviewVelocity(static_cast<std::uint8_t>(norm * 127.0 + 0.5));
|
||||
return;
|
||||
}
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kVoiceCount: {
|
||||
// Stepped: quantize the continuous drag to the integer count and track it live
|
||||
// for the label/needle. The actual engine rebuild (setVoiceCount) fires ONCE on
|
||||
// WM_LBUTTONUP — not per step — so a full drag (~31 steps) costs one rebuild,
|
||||
// not thirty.
|
||||
const int count =
|
||||
kMinVoiceCount +
|
||||
static_cast<int>(norm * (kMaxVoiceCount - kMinVoiceCount) + 0.5);
|
||||
voiceCount_ = count;
|
||||
return;
|
||||
}
|
||||
case ParamControl::kMasterGain:
|
||||
// Post-mixer gain: one atomic store; the audio thread picks it up next block.
|
||||
processor_->setMasterGainLinear(masterGainLinearFromNorm(norm));
|
||||
return;
|
||||
default:
|
||||
applyZoneControl(zoneIndex, id, norm, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ReaSamplerEditor::deckValueLabel(int id, const PerformanceZone& zone) const {
|
||||
char buf[24];
|
||||
buf[0] = '\0';
|
||||
const ZonePlaySeconds& play = zone.play;
|
||||
switch (id == -2 ? ParamControl::kCount : static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.attackSeconds); break;
|
||||
case ParamControl::kHold:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.holdSeconds); break;
|
||||
case ParamControl::kDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.decaySeconds); break;
|
||||
case ParamControl::kSustain:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.adsr.sustainLevel * 100.0); break;
|
||||
case ParamControl::kRelease:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.releaseSeconds); break;
|
||||
case ParamControl::kTrigLength:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.trigger.lengthFraction * 100.0); break;
|
||||
case ParamControl::kTrigFadeIn:
|
||||
snprintf(buf, sizeof(buf), "%lldf",
|
||||
static_cast<long long>(play.trigger.fadeInFrames)); break;
|
||||
case ParamControl::kTrigFadeOut:
|
||||
snprintf(buf, sizeof(buf), "%lldf",
|
||||
static_cast<long long>(play.trigger.fadeOutFrames)); break;
|
||||
case ParamControl::kPitchEnvAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.pitchEnv.attackSeconds); break;
|
||||
case ParamControl::kPitchEnvDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.pitchEnv.decaySeconds); break;
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
snprintf(buf, sizeof(buf), "%+.1fst", play.pitchEnv.peakSemitones); break;
|
||||
case ParamControl::kKeyTrack:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", zone.keyTrack * 100.0); break;
|
||||
case ParamControl::kVoiceCount:
|
||||
snprintf(buf, sizeof(buf), "%d", voiceCount_); break;
|
||||
case ParamControl::kMasterGain:
|
||||
formatMasterGainLabel(deckControlNorm(id, zone), buf, sizeof(buf)); break;
|
||||
default:
|
||||
// -2 (preview velocity) is labeled at its cluster call site; nothing else here.
|
||||
break;
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
EnvClampBounds ReaSamplerEditor::envClampBounds() const {
|
||||
// Match the control-panel sliders' own domains so a node drag can never produce a param a
|
||||
// slider couldn't (the S-VIEW-F2 invariant). AHDSR seconds cap at kEnvTimeMaxSeconds; the
|
||||
// Trigger fade/length fractions cap at 1.0 (the natural full-span bound the sliders use).
|
||||
EnvClampBounds b;
|
||||
b.maxAttackSeconds = kEnvTimeMaxSeconds;
|
||||
b.maxHoldSeconds = kEnvTimeMaxSeconds;
|
||||
b.maxDecaySeconds = kEnvTimeMaxSeconds;
|
||||
b.maxReleaseSeconds = kEnvTimeMaxSeconds;
|
||||
b.maxFadeInFraction = 1.0;
|
||||
b.maxFadeOutFraction = 1.0;
|
||||
b.maxLengthFraction = 1.0;
|
||||
return b;
|
||||
}
|
||||
|
||||
AmpEnvelope ReaSamplerEditor::packEnvelope(const ZonePlaySeconds& play, std::int64_t frames,
|
||||
std::int64_t startFrame) const {
|
||||
AmpEnvelope env;
|
||||
env.mode = (play.playMode == PlayMode::Trigger) ? EnvMode::Trigger : EnvMode::Gate;
|
||||
// AHDSR seconds copy 1-to-1 (rate-free, the same domain the overlay draws).
|
||||
env.attackSeconds = play.adsr.attackSeconds;
|
||||
env.holdSeconds = play.adsr.holdSeconds;
|
||||
env.decaySeconds = play.adsr.decaySeconds;
|
||||
env.sustainLevel = play.adsr.sustainLevel;
|
||||
env.releaseSeconds = play.adsr.releaseSeconds;
|
||||
// Trigger: lengthFraction copies 1-to-1; the fades are DERIVED — source frames over the played
|
||||
// span (the TRIGGER SEAM converter, PACK direction). startFrame is the zone's effective start
|
||||
// point so the fraction denominator matches the voice's actual post-start span. A zero play
|
||||
// length yields 0 fractions.
|
||||
env.lengthFraction = play.trigger.lengthFraction;
|
||||
const std::int64_t playLen =
|
||||
triggerPlayLength(play.trigger.lengthFraction, frames, startFrame);
|
||||
env.fadeInFraction = framesToFadeFraction(play.trigger.fadeInFrames, playLen);
|
||||
env.fadeOutFraction = framesToFadeFraction(play.trigger.fadeOutFrames, playLen);
|
||||
return env;
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::unpackEnvelope(const AmpEnvelope& env, std::int64_t frames,
|
||||
std::int64_t startFrame, ZonePlaySeconds& play) const {
|
||||
if (env.mode == EnvMode::Gate) {
|
||||
play.adsr.attackSeconds = env.attackSeconds;
|
||||
play.adsr.holdSeconds = env.holdSeconds;
|
||||
play.adsr.decaySeconds = env.decaySeconds;
|
||||
play.adsr.sustainLevel = env.sustainLevel;
|
||||
play.adsr.releaseSeconds = env.releaseSeconds;
|
||||
} else {
|
||||
// Trigger: lengthFraction copies back; the fades convert fractions -> source frames over
|
||||
// the played span (the TRIGGER SEAM converter, UNPACK direction). startFrame is the zone's
|
||||
// effective start point so the frame denominator matches the voice's actual post-start span.
|
||||
// Keep the same (0,1] floor on lengthFraction the slider path enforces so a zero-length
|
||||
// trigger never plays nothing.
|
||||
play.trigger.lengthFraction = (std::max)(0.01, env.lengthFraction);
|
||||
const std::int64_t playLen =
|
||||
triggerPlayLength(play.trigger.lengthFraction, frames, startFrame);
|
||||
play.trigger.fadeInFrames = fadeFractionToFrames(env.fadeInFraction, playLen);
|
||||
play.trigger.fadeOutFrames = fadeFractionToFrames(env.fadeOutFraction, playLen);
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceZone ReaSamplerEditor::popupZone() const {
|
||||
// The zone the popup displays: the Zone surface's SELECTED zone (FB2), else the Sample
|
||||
// face's one-zone site (a read-only resolve — an edit materializes via popupZoneIndex).
|
||||
if (view_ == View::kZone && selectedZone_ >= 0 &&
|
||||
selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
return map_.zones[static_cast<std::size_t>(selectedZone_)];
|
||||
}
|
||||
return effectiveSampleZone();
|
||||
}
|
||||
|
||||
int ReaSamplerEditor::popupZoneIndex() {
|
||||
// The map_.zones index a popup edit lands on, or -1 when there is no valid target. The
|
||||
// Zone surface never materializes (the button only shows for an explicit selection); the
|
||||
// Sample face finds-or-materializes the picked id's one-zone site.
|
||||
if (view_ == View::kZone) {
|
||||
return (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size()))
|
||||
? selectedZone_
|
||||
: -1;
|
||||
}
|
||||
return ensureSampleZone();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void ReaSamplerEditor::applyZoneControl(int zoneIndex, int id, double value, int segment) {
|
||||
if (zoneIndex < 0 || zoneIndex >= static_cast<int>(map_.zones.size())) return;
|
||||
PerformanceZone& z = map_.zones[static_cast<std::size_t>(zoneIndex)];
|
||||
if (id == static_cast<int>(ParamControl::kKeyTrack)) {
|
||||
// keyTrack lives on the zone (0..200% over kKeyTrackMax); the slider maps 0..1.
|
||||
z.keyTrack = clamp01(value) * kKeyTrackMax;
|
||||
} else {
|
||||
applyControl(id, z.play, value, segment);
|
||||
}
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
} // namespace reasampler::vst
|
||||
Reference in New Issue
Block a user