feat: legible ReaSampler 9000 editor — bigger knobs, ms time constants, per-ring double-click reset, and an antialiased draw pass
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
// editor_controls.cpp — the ReaSamplerEditor's parameter plumbing: the band-stack layout
|
||||
// resolve every paint/hit-test path shares, the control-value domain maps (controlValue /
|
||||
// applyControl — seconds/fraction/frames <-> normalized 0..1), the control-id<->value binding
|
||||
// against the pure `deck_groups` module's descriptors, and the node-drag clamp bounds that must
|
||||
// match those domains. The orthogonal half — which stored struct each editor selection names —
|
||||
// is editor_models. Value logic only: no painting, no window plumbing.
|
||||
// resolve every paint/hit-test path shares, the shell's half of the control-value binding (the
|
||||
// per-instance controls the parameter set does not carry — key-track, voice count, master gain,
|
||||
// preview velocity — plus the value labels), and the node-drag clamp bounds. The parameter-set
|
||||
// half is the pure `deck_values` module. The orthogonal half — which stored struct each editor
|
||||
// selection names — is editor_models. Value logic only: no painting, no window plumbing.
|
||||
|
||||
#include "shell/instrument/reasampler_editor.h"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "core/instrument/engine/filter/filter_params.h" // the filter's own control laws
|
||||
#include "core/instrument/engine/master_gain.h" // master-gain dB<->linear<->knob taper
|
||||
#include "core/instrument/ui/deck_groups.h" // sampleDeckGroups (the deck's composition)
|
||||
#include "core/instrument/ui/deck_values.h" // the parameter-set binding + its ms units
|
||||
#include "core/instrument/ui/knob_deck.h" // deckHeight / kDeckKnobSize (the band's own height)
|
||||
#include "core/util/clamp01.h"
|
||||
#include "core/util/curve_law.h" // the ONE curve-exponent domain
|
||||
@@ -30,32 +31,22 @@ using instrument::ui::chromeRects;
|
||||
using instrument::ui::deckHeight;
|
||||
using instrument::ui::kDeckKnobSize;
|
||||
using instrument::ui::kPad;
|
||||
using instrument::ui::deckBipolarFromNorm;
|
||||
using instrument::ui::deckNormFromBipolar;
|
||||
using instrument::ui::deckParamNorm;
|
||||
using instrument::ui::formatEnvTimeMs;
|
||||
using instrument::ui::kEnvTimeMaxSeconds;
|
||||
using instrument::ui::kKeyTrackMax;
|
||||
using instrument::ui::resetDeckParam;
|
||||
using instrument::ui::sampleDeckGroups;
|
||||
using instrument::ui::setDeckParam;
|
||||
using instrument::engine::formatMasterGainLabel;
|
||||
using instrument::engine::masterGainLinearFromNorm;
|
||||
using instrument::engine::masterGainNormFromLinear;
|
||||
using instrument::engine::filter::MorphLaw;
|
||||
using instrument::engine::filter::filterCutoffHzFromNorm;
|
||||
using instrument::engine::filter::filterDriveDepthFromNorm;
|
||||
using instrument::engine::filter::filterQFromNorm;
|
||||
using util::clamp01;
|
||||
|
||||
namespace {
|
||||
// Control-surface value domains (the shell owns these — param_slider is engine-free and maps
|
||||
// only 0..1). Every stage-time knob spans [0, kEnvTimeMaxSeconds] seconds — rate-free, exactly
|
||||
// what the parameter set stores; the build resolves seconds->frames at the live rate. No knob
|
||||
// on this surface stores a source-frame count any more, so none needs a rate to draw.
|
||||
//
|
||||
// The ceiling is READ from the overlay's schematic scale rather than restated: the AHDSR
|
||||
// schematic anchors a maxed knob at the canvas edge, which only holds while the two agree.
|
||||
constexpr double kEnvTimeMaxSeconds = instrument::ui::kGateStageMaxSeconds;
|
||||
// Pitch depth throw: +/-kVelocityPitchRangeSemitones, centered. The one throw the pitch
|
||||
// envelope's peak and the velocity->pitch curve's full scale both speak (play_params.h).
|
||||
constexpr double kPitchDepthMaxSemis = kVelocityPitchRangeSemitones;
|
||||
constexpr double kKeyTrackMax = 2.0; // key-track slider ceiling (0..200%)
|
||||
|
||||
// The raw stored curve exponent for a curve-dial control id, read DIRECTLY off the field —
|
||||
// never round-tripped through curveFromKnobNorm(knobNormFromCurve(x)): the knob-norm law's
|
||||
// centre detent (curve_law.h) snaps anything near-neutral back to exactly 1.0, so a round trip
|
||||
@@ -91,191 +82,25 @@ ReaSamplerEditor::FaceLayout ReaSamplerEditor::faceLayout(int w, int h) const {
|
||||
return fl;
|
||||
}
|
||||
|
||||
// The parameter-set binding is the pure deck_values module's; these three are the shell's thin
|
||||
// int-id adapters onto it (ParamControl is an alias of DeckParam).
|
||||
double ReaSamplerEditor::controlValue(int id, const PlaySeconds& play) const {
|
||||
// Wall-clock seconds -> normalized over the seconds ceiling; fractions and normalized
|
||||
// control positions pass through; curve exponents take the log travel.
|
||||
const auto secToNorm = [](double s) { return clamp01(s / kEnvTimeMaxSeconds); };
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kPlayMode: return play.playMode == PlayMode::Trigger ? 1.0 : 0.0;
|
||||
case ParamControl::kAmpEnvMode:
|
||||
return play.ampSpline.mode == EnvMode::Spline ? 1.0 : 0.0;
|
||||
case ParamControl::kPitchEnvMode:
|
||||
return play.pitchSpline.mode == EnvMode::Spline ? 1.0 : 0.0;
|
||||
case ParamControl::kFilterEnvMode:
|
||||
return play.filterSpline.mode == EnvMode::Spline ? 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::kAttackCurve: return util::knobNormFromCurve(play.adsr.attackCurve);
|
||||
case ParamControl::kDecayCurve: return util::knobNormFromCurve(play.adsr.decayCurve);
|
||||
case ParamControl::kReleaseCurve: return util::knobNormFromCurve(play.adsr.releaseCurve);
|
||||
case ParamControl::kTrigLength: return clamp01(play.trigger.lengthFraction);
|
||||
case ParamControl::kTrigAttack: return secToNorm(play.trigAhd.attackSeconds);
|
||||
case ParamControl::kTrigHold: return clamp01(play.trigAhd.holdFraction);
|
||||
case ParamControl::kTrigDecay: return secToNorm(play.trigAhd.decaySeconds);
|
||||
case ParamControl::kTrigAttackCurve: return util::knobNormFromCurve(play.trigAhd.attackCurve);
|
||||
case ParamControl::kTrigDecayCurve: return util::knobNormFromCurve(play.trigAhd.decayCurve);
|
||||
case ParamControl::kPitchEnvEnable:return play.pitchEnv.enabled ? 1.0 : 0.0;
|
||||
case ParamControl::kPitchEnvAttack:return secToNorm(play.pitchEnv.shape.attackSeconds);
|
||||
case ParamControl::kPitchEnvHold: return clamp01(play.pitchEnv.shape.holdFraction);
|
||||
case ParamControl::kPitchEnvDecay: return secToNorm(play.pitchEnv.shape.decaySeconds);
|
||||
case ParamControl::kPitchEnvAttackCurve:
|
||||
return util::knobNormFromCurve(play.pitchEnv.shape.attackCurve);
|
||||
case ParamControl::kPitchEnvDecayCurve:
|
||||
return util::knobNormFromCurve(play.pitchEnv.shape.decayCurve);
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
// Signed depth centered at 0.5 (0.5 == 0 semitones).
|
||||
return clamp01(0.5 + play.pitchEnv.peakSemitones / (2.0 * kPitchDepthMaxSemis));
|
||||
// Filter. The four tone controls ARE the module's normalized positions — stored and
|
||||
// shown as-is, so the knob travel is exactly filter_params' own law.
|
||||
case ParamControl::kFilterEnable: return play.filter.enabled ? 1.0 : 0.0;
|
||||
case ParamControl::kFilterLaw:
|
||||
return play.filter.settings.morphLaw == MorphLaw::HighNotchLow ? 1.0 : 0.0;
|
||||
case ParamControl::kFilterMorph: return clamp01(play.filter.settings.morphNorm);
|
||||
case ParamControl::kFilterCutoff: return clamp01(play.filter.settings.cutoffNorm);
|
||||
case ParamControl::kFilterQ: return clamp01(play.filter.settings.resonanceNorm);
|
||||
case ParamControl::kFilterDrive: return clamp01(play.filter.settings.driveNorm);
|
||||
case ParamControl::kFilterModAmt: return deckNormFromBipolar(play.filter.modAmount);
|
||||
case ParamControl::kFilterVel: return deckNormFromBipolar(play.filter.velAmount);
|
||||
case ParamControl::kFilterKeyTrack:return clamp01(play.filter.keyTrack / kKeyTrackMax);
|
||||
case ParamControl::kFilterEnvAttack: return secToNorm(play.filter.env.attackSeconds);
|
||||
case ParamControl::kFilterEnvHold: return secToNorm(play.filter.env.holdSeconds);
|
||||
case ParamControl::kFilterEnvDecay: return secToNorm(play.filter.env.decaySeconds);
|
||||
case ParamControl::kFilterEnvSustain: return clamp01(play.filter.env.sustainLevel);
|
||||
case ParamControl::kFilterEnvRelease: return secToNorm(play.filter.env.releaseSeconds);
|
||||
case ParamControl::kFilterEnvAttackCurve:
|
||||
return util::knobNormFromCurve(play.filter.env.attackCurve);
|
||||
case ParamControl::kFilterEnvDecayCurve:
|
||||
return util::knobNormFromCurve(play.filter.env.decayCurve);
|
||||
case ParamControl::kFilterEnvReleaseCurve:
|
||||
return util::knobNormFromCurve(play.filter.env.releaseCurve);
|
||||
case ParamControl::kFilterTrigAttack: return secToNorm(play.filter.trigEnv.attackSeconds);
|
||||
case ParamControl::kFilterTrigHold: return clamp01(play.filter.trigEnv.holdFraction);
|
||||
case ParamControl::kFilterTrigDecay: return secToNorm(play.filter.trigEnv.decaySeconds);
|
||||
case ParamControl::kFilterTrigAttackCurve:
|
||||
return util::knobNormFromCurve(play.filter.trigEnv.attackCurve);
|
||||
case ParamControl::kFilterTrigDecayCurve:
|
||||
return util::knobNormFromCurve(play.filter.trigEnv.decayCurve);
|
||||
default: return 0.0;
|
||||
}
|
||||
return deckParamNorm(static_cast<instrument::ui::DeckParam>(id), play);
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
||||
int segment) const {
|
||||
const auto normToSec = [](double v) { return clamp01(v) * kEnvTimeMaxSeconds; };
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kPlayMode:
|
||||
// Gate is refused while any EG is drawn — see splineActive (play_params.h). The
|
||||
// segment paints Disabled for the same reason, so the refusal is never a surprise.
|
||||
if (segment == 0 && splineActive(play)) break;
|
||||
play.playMode = (segment == 1) ? PlayMode::Trigger : PlayMode::Gate;
|
||||
break;
|
||||
// Switching TO Spline drops Gate, which the spline model has no place for. Switching
|
||||
// back does NOT restore it: the previous mode is not stored, and silently re-gating an
|
||||
// instrument the user has since heard as a one-shot is the worse surprise.
|
||||
case ParamControl::kAmpEnvMode:
|
||||
case ParamControl::kPitchEnvMode:
|
||||
case ParamControl::kFilterEnvMode: {
|
||||
const EnvMode m = (segment == 1) ? EnvMode::Spline : EnvMode::Staged;
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kAmpEnvMode: play.ampSpline.mode = m; break;
|
||||
case ParamControl::kPitchEnvMode: play.pitchSpline.mode = m; break;
|
||||
default: play.filterSpline.mode = m; break;
|
||||
}
|
||||
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::kAttackCurve: play.adsr.attackCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kDecayCurve: play.adsr.decayCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kReleaseCurve: play.adsr.releaseCurve = util::curveFromKnobNorm(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::kTrigAttack: play.trigAhd.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kTrigHold: play.trigAhd.holdFraction = clamp01(value); break;
|
||||
case ParamControl::kTrigDecay: play.trigAhd.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kTrigAttackCurve:
|
||||
play.trigAhd.attackCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kTrigDecayCurve:
|
||||
play.trigAhd.decayCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kPitchEnvEnable:
|
||||
play.pitchEnv.enabled = (segment == 1);
|
||||
break;
|
||||
case ParamControl::kPitchEnvAttack:
|
||||
play.pitchEnv.shape.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kPitchEnvHold:
|
||||
play.pitchEnv.shape.holdFraction = clamp01(value); break;
|
||||
case ParamControl::kPitchEnvDecay:
|
||||
play.pitchEnv.shape.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kPitchEnvAttackCurve:
|
||||
play.pitchEnv.shape.attackCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kPitchEnvDecayCurve:
|
||||
play.pitchEnv.shape.decayCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
play.pitchEnv.peakSemitones = (clamp01(value) - 0.5) * 2.0 * kPitchDepthMaxSemis;
|
||||
break;
|
||||
case ParamControl::kFilterEnable: play.filter.enabled = (segment == 1); break;
|
||||
case ParamControl::kFilterLaw:
|
||||
play.filter.settings.morphLaw =
|
||||
(segment == 1) ? MorphLaw::HighNotchLow : MorphLaw::HighBandLow;
|
||||
break;
|
||||
case ParamControl::kFilterMorph:
|
||||
play.filter.settings.morphNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterCutoff:
|
||||
play.filter.settings.cutoffNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterQ:
|
||||
play.filter.settings.resonanceNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterDrive:
|
||||
play.filter.settings.driveNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterModAmt: play.filter.modAmount = deckBipolarFromNorm(value); break;
|
||||
case ParamControl::kFilterVel: play.filter.velAmount = deckBipolarFromNorm(value); break;
|
||||
case ParamControl::kFilterKeyTrack:
|
||||
play.filter.keyTrack = clamp01(value) * kKeyTrackMax; break;
|
||||
case ParamControl::kFilterEnvAttack:
|
||||
play.filter.env.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvHold:
|
||||
play.filter.env.holdSeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvDecay:
|
||||
play.filter.env.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvSustain:
|
||||
play.filter.env.sustainLevel = clamp01(value); break;
|
||||
case ParamControl::kFilterEnvRelease:
|
||||
play.filter.env.releaseSeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvAttackCurve:
|
||||
play.filter.env.attackCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kFilterEnvDecayCurve:
|
||||
play.filter.env.decayCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kFilterEnvReleaseCurve:
|
||||
play.filter.env.releaseCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kFilterTrigAttack:
|
||||
play.filter.trigEnv.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterTrigHold:
|
||||
play.filter.trigEnv.holdFraction = clamp01(value); break;
|
||||
case ParamControl::kFilterTrigDecay:
|
||||
play.filter.trigEnv.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterTrigAttackCurve:
|
||||
play.filter.trigEnv.attackCurve = util::curveFromKnobNorm(value); break;
|
||||
case ParamControl::kFilterTrigDecayCurve:
|
||||
play.filter.trigEnv.decayCurve = util::curveFromKnobNorm(value); break;
|
||||
default: break;
|
||||
setDeckParam(static_cast<instrument::ui::DeckParam>(id), play, value, segment);
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::resetParamControl(int id) {
|
||||
// kKeyTrack is the one knob whose value sits beside the play bundle, so it defaults from
|
||||
// InstrumentParams rather than from PlaySeconds — the same split applyParamControl draws.
|
||||
if (id == static_cast<int>(ParamControl::kKeyTrack)) {
|
||||
params_.keyTrack = InstrumentParams{}.keyTrack;
|
||||
return;
|
||||
}
|
||||
// ONE normalization point for every control that can flip splineActive — a mode toggle
|
||||
// (above) or an enable toggle (kPitchEnvEnable/kFilterEnable), whose enabling can make an
|
||||
// already-Spline pitch/filter envelope newly active. Applying it once here, rather than at
|
||||
// each site that could cause the flip, is what keeps a future such control from reopening
|
||||
// the same hole. `resolvePlay` (sample_map.cpp) is the other caller of the shared helper.
|
||||
enforceGateUnavailableWhileDrawn(play);
|
||||
resetDeckParam(static_cast<instrument::ui::DeckParam>(id), params_.play);
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::liveSampleRate() const {
|
||||
@@ -348,29 +173,29 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
const PlaySeconds& play = params_.play;
|
||||
switch (id == -2 ? ParamControl::kCount : static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.attackSeconds); break;
|
||||
formatEnvTimeMs(play.adsr.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kHold:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.holdSeconds); break;
|
||||
formatEnvTimeMs(play.adsr.holdSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.adsr.decaySeconds); break;
|
||||
formatEnvTimeMs(play.adsr.decaySeconds, buf, sizeof(buf)); 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;
|
||||
formatEnvTimeMs(play.adsr.releaseSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kTrigLength:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.trigger.lengthFraction * 100.0); break;
|
||||
case ParamControl::kTrigAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.trigAhd.attackSeconds); break;
|
||||
formatEnvTimeMs(play.trigAhd.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kTrigHold:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.trigAhd.holdFraction * 100.0); break;
|
||||
case ParamControl::kTrigDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.trigAhd.decaySeconds); break;
|
||||
formatEnvTimeMs(play.trigAhd.decaySeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kPitchEnvAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.pitchEnv.shape.attackSeconds); break;
|
||||
formatEnvTimeMs(play.pitchEnv.shape.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kPitchEnvHold:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.pitchEnv.shape.holdFraction * 100.0); break;
|
||||
case ParamControl::kPitchEnvDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.pitchEnv.shape.decaySeconds); break;
|
||||
formatEnvTimeMs(play.pitchEnv.shape.decaySeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
snprintf(buf, sizeof(buf), "%+.1fst", play.pitchEnv.peakSemitones); break;
|
||||
case ParamControl::kKeyTrack:
|
||||
@@ -407,21 +232,21 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
case ParamControl::kFilterKeyTrack:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.filter.keyTrack * 100.0); break;
|
||||
case ParamControl::kFilterEnvAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.attackSeconds); break;
|
||||
formatEnvTimeMs(play.filter.env.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterEnvHold:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.holdSeconds); break;
|
||||
formatEnvTimeMs(play.filter.env.holdSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterEnvDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.decaySeconds); break;
|
||||
formatEnvTimeMs(play.filter.env.decaySeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterEnvSustain:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.filter.env.sustainLevel * 100.0); break;
|
||||
case ParamControl::kFilterEnvRelease:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.releaseSeconds); break;
|
||||
formatEnvTimeMs(play.filter.env.releaseSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterTrigAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.trigEnv.attackSeconds); break;
|
||||
formatEnvTimeMs(play.filter.trigEnv.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterTrigHold:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.filter.trigEnv.holdFraction * 100.0); break;
|
||||
case ParamControl::kFilterTrigDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.trigEnv.decaySeconds); break;
|
||||
formatEnvTimeMs(play.filter.trigEnv.decaySeconds, buf, sizeof(buf)); break;
|
||||
// Every curve exponent reads the same way: the neutral shows as 1.00.
|
||||
case ParamControl::kAttackCurve:
|
||||
case ParamControl::kDecayCurve:
|
||||
|
||||
Reference in New Issue
Block a user