|
|
|
@@ -8,7 +8,6 @@
|
|
|
|
|
#include "shell/instrument/reasampler_editor.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath> // log/exp (the curve knob's logarithmic travel)
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <cstdio> // snprintf (deck value labels)
|
|
|
|
|
#include <string>
|
|
|
|
@@ -49,23 +48,12 @@ namespace {
|
|
|
|
|
// 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.
|
|
|
|
|
constexpr double kEnvTimeMaxSeconds = 2.0; // every stage-time knob's ceiling (seconds)
|
|
|
|
|
//
|
|
|
|
|
// 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;
|
|
|
|
|
constexpr double kPitchDepthMaxSemis = 24.0; // pitch depth throw: +/-24 st, centered
|
|
|
|
|
constexpr double kKeyTrackMax = 2.0; // key-track slider ceiling (0..200%)
|
|
|
|
|
|
|
|
|
|
// A curve exponent's knob travel is LOGARITHMIC: 0.5 is the linear neutral, so the two halves
|
|
|
|
|
// of the throw are the reciprocal shaping directions and the neutral sits at a centre detent.
|
|
|
|
|
double curveFromNorm(double norm) {
|
|
|
|
|
const double t = clamp01(norm);
|
|
|
|
|
return std::exp(std::log(util::kCurveMin) +
|
|
|
|
|
t * (std::log(util::kCurveMax) - std::log(util::kCurveMin)));
|
|
|
|
|
}
|
|
|
|
|
double normFromCurve(double curve) {
|
|
|
|
|
const double c = util::clampCurve(curve);
|
|
|
|
|
return clamp01((std::log(c) - std::log(util::kCurveMin)) /
|
|
|
|
|
(std::log(util::kCurveMax) - std::log(util::kCurveMin)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
ReaSamplerEditor::FaceLayout ReaSamplerEditor::faceLayout(int w, int h) const {
|
|
|
|
@@ -91,23 +79,23 @@ double ReaSamplerEditor::controlValue(int id, const PlaySeconds& play) const {
|
|
|
|
|
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 normFromCurve(play.adsr.attackCurve);
|
|
|
|
|
case ParamControl::kDecayCurve: return normFromCurve(play.adsr.decayCurve);
|
|
|
|
|
case ParamControl::kReleaseCurve: return normFromCurve(play.adsr.releaseCurve);
|
|
|
|
|
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 normFromCurve(play.trigAhd.attackCurve);
|
|
|
|
|
case ParamControl::kTrigDecayCurve: return normFromCurve(play.trigAhd.decayCurve);
|
|
|
|
|
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 normFromCurve(play.pitchEnv.shape.attackCurve);
|
|
|
|
|
return util::knobNormFromCurve(play.pitchEnv.shape.attackCurve);
|
|
|
|
|
case ParamControl::kPitchEnvDecayCurve:
|
|
|
|
|
return normFromCurve(play.pitchEnv.shape.decayCurve);
|
|
|
|
|
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));
|
|
|
|
@@ -129,18 +117,18 @@ double ReaSamplerEditor::controlValue(int id, const PlaySeconds& play) const {
|
|
|
|
|
case ParamControl::kFilterEnvSustain: return clamp01(play.filter.env.sustainLevel);
|
|
|
|
|
case ParamControl::kFilterEnvRelease: return secToNorm(play.filter.env.releaseSeconds);
|
|
|
|
|
case ParamControl::kFilterEnvAttackCurve:
|
|
|
|
|
return normFromCurve(play.filter.env.attackCurve);
|
|
|
|
|
return util::knobNormFromCurve(play.filter.env.attackCurve);
|
|
|
|
|
case ParamControl::kFilterEnvDecayCurve:
|
|
|
|
|
return normFromCurve(play.filter.env.decayCurve);
|
|
|
|
|
return util::knobNormFromCurve(play.filter.env.decayCurve);
|
|
|
|
|
case ParamControl::kFilterEnvReleaseCurve:
|
|
|
|
|
return normFromCurve(play.filter.env.releaseCurve);
|
|
|
|
|
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 normFromCurve(play.filter.trigEnv.attackCurve);
|
|
|
|
|
return util::knobNormFromCurve(play.filter.trigEnv.attackCurve);
|
|
|
|
|
case ParamControl::kFilterTrigDecayCurve:
|
|
|
|
|
return normFromCurve(play.filter.trigEnv.decayCurve);
|
|
|
|
|
return util::knobNormFromCurve(play.filter.trigEnv.decayCurve);
|
|
|
|
|
default: return 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -160,9 +148,9 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
|
|
|
|
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 = curveFromNorm(value); break;
|
|
|
|
|
case ParamControl::kDecayCurve: play.adsr.decayCurve = curveFromNorm(value); break;
|
|
|
|
|
case ParamControl::kReleaseCurve: play.adsr.releaseCurve = curveFromNorm(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));
|
|
|
|
@@ -171,9 +159,9 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
|
|
|
|
case ParamControl::kTrigHold: play.trigAhd.holdFraction = clamp01(value); break;
|
|
|
|
|
case ParamControl::kTrigDecay: play.trigAhd.decaySeconds = normToSec(value); break;
|
|
|
|
|
case ParamControl::kTrigAttackCurve:
|
|
|
|
|
play.trigAhd.attackCurve = curveFromNorm(value); break;
|
|
|
|
|
play.trigAhd.attackCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kTrigDecayCurve:
|
|
|
|
|
play.trigAhd.decayCurve = curveFromNorm(value); break;
|
|
|
|
|
play.trigAhd.decayCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kPitchEnvEnable:
|
|
|
|
|
play.pitchEnv.enabled = (segment == 1);
|
|
|
|
|
break;
|
|
|
|
@@ -184,9 +172,9 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
|
|
|
|
case ParamControl::kPitchEnvDecay:
|
|
|
|
|
play.pitchEnv.shape.decaySeconds = normToSec(value); break;
|
|
|
|
|
case ParamControl::kPitchEnvAttackCurve:
|
|
|
|
|
play.pitchEnv.shape.attackCurve = curveFromNorm(value); break;
|
|
|
|
|
play.pitchEnv.shape.attackCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kPitchEnvDecayCurve:
|
|
|
|
|
play.pitchEnv.shape.decayCurve = curveFromNorm(value); break;
|
|
|
|
|
play.pitchEnv.shape.decayCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kPitchEnvDepth:
|
|
|
|
|
play.pitchEnv.peakSemitones = (clamp01(value) - 0.5) * 2.0 * kPitchDepthMaxSemis;
|
|
|
|
|
break;
|
|
|
|
@@ -218,11 +206,11 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
|
|
|
|
case ParamControl::kFilterEnvRelease:
|
|
|
|
|
play.filter.env.releaseSeconds = normToSec(value); break;
|
|
|
|
|
case ParamControl::kFilterEnvAttackCurve:
|
|
|
|
|
play.filter.env.attackCurve = curveFromNorm(value); break;
|
|
|
|
|
play.filter.env.attackCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kFilterEnvDecayCurve:
|
|
|
|
|
play.filter.env.decayCurve = curveFromNorm(value); break;
|
|
|
|
|
play.filter.env.decayCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kFilterEnvReleaseCurve:
|
|
|
|
|
play.filter.env.releaseCurve = curveFromNorm(value); break;
|
|
|
|
|
play.filter.env.releaseCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kFilterTrigAttack:
|
|
|
|
|
play.filter.trigEnv.attackSeconds = normToSec(value); break;
|
|
|
|
|
case ParamControl::kFilterTrigHold:
|
|
|
|
@@ -230,9 +218,9 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
|
|
|
|
case ParamControl::kFilterTrigDecay:
|
|
|
|
|
play.filter.trigEnv.decaySeconds = normToSec(value); break;
|
|
|
|
|
case ParamControl::kFilterTrigAttackCurve:
|
|
|
|
|
play.filter.trigEnv.attackCurve = curveFromNorm(value); break;
|
|
|
|
|
play.filter.trigEnv.attackCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
case ParamControl::kFilterTrigDecayCurve:
|
|
|
|
|
play.filter.trigEnv.decayCurve = curveFromNorm(value); break;
|
|
|
|
|
play.filter.trigEnv.decayCurve = util::curveFromKnobNorm(value); break;
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -385,7 +373,7 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
|
|
|
|
case ParamControl::kFilterEnvReleaseCurve:
|
|
|
|
|
case ParamControl::kFilterTrigAttackCurve:
|
|
|
|
|
case ParamControl::kFilterTrigDecayCurve:
|
|
|
|
|
snprintf(buf, sizeof(buf), "^%.2f", curveFromNorm(controlValue(id, play)));
|
|
|
|
|
snprintf(buf, sizeof(buf), "^%.2f", util::curveFromKnobNorm(controlValue(id, play)));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// -2 (preview velocity) is labeled at its chrome call site; nothing else here.
|
|
|
|
@@ -406,15 +394,6 @@ EnvClampBounds ReaSamplerEditor::envClampBounds() const {
|
|
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ReaSamplerEditor::OverlayEnv ReaSamplerEditor::overlayEnvForRadio(int radioId) {
|
|
|
|
|
switch (static_cast<ParamControl>(radioId)) {
|
|
|
|
|
case ParamControl::kAmpEnvSelect: return OverlayEnv::kAmp;
|
|
|
|
|
case ParamControl::kPitchEnvSelect: return OverlayEnv::kPitch;
|
|
|
|
|
case ParamControl::kFilterEnvSelect: return OverlayEnv::kFilter;
|
|
|
|
|
default: return OverlayEnv::kNone;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
// The two directions of the AHDSR <-> StageEnvelope copy, so a field can only be forgotten in
|
|
|
|
|
// one place rather than two.
|
|
|
|
|