175 lines
7.3 KiB
C++
175 lines
7.3 KiB
C++
// editor_models.cpp — the ReaSamplerEditor's model-selection half: which STORED struct each
|
|
// transient editor selection names. The overlay selection maps onto a staged envelope
|
|
// (pack/unpack) or a drawn contour; a curve-popup target maps onto one of the three velocity
|
|
// curves. One switch per family, so a moved field or a fourth curve is a one-place edit.
|
|
// Split from editor_controls, which owns the orthogonal half: the control-value domain maps.
|
|
|
|
#include "shell/instrument/reasampler_editor.h"
|
|
|
|
#include <cstdint>
|
|
#include <utility> // std::as_const (the const/non-const accessor pairs)
|
|
|
|
#include "core/instrument/map/trigger_seam.h" // triggerPlayLength (the Trigger play span)
|
|
#include "shell/instrument/reasampler_processor.h"
|
|
|
|
namespace reasampler::vst {
|
|
|
|
using namespace reasampler::instrument::map; // PlaySeconds vocabulary + trigger_seam
|
|
|
|
namespace {
|
|
// The two directions of the AHDSR <-> StageEnvelope copy, so a field can only be forgotten in
|
|
// one place rather than two.
|
|
void packAhdsr(const AdsrSeconds& a, StageEnvelope& env) {
|
|
env.kind = instrument::ui::EnvKind::Ahdsr;
|
|
env.attackSeconds = a.attackSeconds;
|
|
env.holdSeconds = a.holdSeconds;
|
|
env.decaySeconds = a.decaySeconds;
|
|
env.sustainLevel = a.sustainLevel;
|
|
env.releaseSeconds = a.releaseSeconds;
|
|
env.attackCurve = a.attackCurve;
|
|
env.decayCurve = a.decayCurve;
|
|
env.releaseCurve = a.releaseCurve;
|
|
}
|
|
void unpackAhdsr(const StageEnvelope& env, AdsrSeconds& a) {
|
|
a.attackSeconds = env.attackSeconds;
|
|
a.holdSeconds = env.holdSeconds;
|
|
a.decaySeconds = env.decaySeconds;
|
|
a.sustainLevel = env.sustainLevel;
|
|
a.releaseSeconds = env.releaseSeconds;
|
|
a.attackCurve = env.attackCurve;
|
|
a.decayCurve = env.decayCurve;
|
|
a.releaseCurve = env.releaseCurve;
|
|
}
|
|
void packAhd(const AhdSeconds& a, double originSeconds, double spanSeconds, StageEnvelope& env) {
|
|
env.kind = instrument::ui::EnvKind::Ahd;
|
|
env.attackSeconds = a.attackSeconds;
|
|
env.decaySeconds = a.decaySeconds;
|
|
env.holdFraction = a.holdFraction;
|
|
env.attackCurve = a.attackCurve;
|
|
env.decayCurve = a.decayCurve;
|
|
env.originSeconds = originSeconds;
|
|
env.spanSeconds = spanSeconds;
|
|
}
|
|
void unpackAhd(const StageEnvelope& env, AhdSeconds& a) {
|
|
a.attackSeconds = env.attackSeconds;
|
|
a.decaySeconds = env.decaySeconds;
|
|
a.holdFraction = env.holdFraction;
|
|
a.attackCurve = env.attackCurve;
|
|
a.decayCurve = env.decayCurve;
|
|
}
|
|
} // namespace
|
|
|
|
StageEnvelope ReaSamplerEditor::packEnvelope(OverlayEnv which, const PlaySeconds& play,
|
|
std::int64_t frames,
|
|
std::int64_t startFrame) const {
|
|
StageEnvelope env;
|
|
const double rate = liveSampleRate();
|
|
const double t0 = rate > 0.0 ? static_cast<double>(startFrame) / rate : 0.0;
|
|
// The Trigger amp and filter AHDs live over the PLAY span; the pitch AHD over the whole
|
|
// post-start span, since it keeps running after a Trigger one-shot's amplitude has ended.
|
|
// effectiveLengthFraction, not the stored knob: the overlay's amp/filter AHD must read the
|
|
// SAME span the engine plays, or its drawn shape and node drags cover only a fraction of
|
|
// what the note actually does.
|
|
const std::int64_t playLen =
|
|
triggerPlayLength(effectiveLengthFraction(play), frames, startFrame);
|
|
const double fullSpan =
|
|
rate > 0.0 ? static_cast<double>((std::max)(std::int64_t{0}, frames - startFrame)) / rate
|
|
: 0.0;
|
|
const double playSpan = rate > 0.0 ? static_cast<double>(playLen) / rate : 0.0;
|
|
const bool trigger = (play.playMode == PlayMode::Trigger);
|
|
switch (which) {
|
|
case OverlayEnv::kPitch:
|
|
packAhd(play.pitchEnv.shape, t0, fullSpan, env);
|
|
break;
|
|
case OverlayEnv::kFilter:
|
|
if (trigger) packAhd(play.filter.trigEnv, t0, playSpan, env);
|
|
else packAhdsr(play.filter.env, env);
|
|
break;
|
|
case OverlayEnv::kAmp:
|
|
if (trigger) packAhd(play.trigAhd, t0, playSpan, env);
|
|
else packAhdsr(play.adsr, env);
|
|
break;
|
|
case OverlayEnv::kNone:
|
|
break; // nothing is overlay-active; a default-constructed StageEnvelope, not amp
|
|
}
|
|
return env;
|
|
}
|
|
|
|
void ReaSamplerEditor::unpackEnvelope(OverlayEnv which, const StageEnvelope& env,
|
|
PlaySeconds& play) const {
|
|
const bool trigger = (play.playMode == PlayMode::Trigger);
|
|
switch (which) {
|
|
case OverlayEnv::kPitch:
|
|
unpackAhd(env, play.pitchEnv.shape);
|
|
break;
|
|
case OverlayEnv::kFilter:
|
|
if (trigger) unpackAhd(env, play.filter.trigEnv);
|
|
else unpackAhdsr(env, play.filter.env);
|
|
break;
|
|
case OverlayEnv::kAmp:
|
|
if (trigger) unpackAhd(env, play.trigAhd);
|
|
else unpackAhdsr(env, play.adsr);
|
|
break;
|
|
case OverlayEnv::kNone:
|
|
break; // nothing is overlay-active, so there is nothing a drag could have edited
|
|
}
|
|
}
|
|
|
|
const VelocityCurve& ReaSamplerEditor::splineFor(OverlayEnv which) const {
|
|
switch (which) {
|
|
case OverlayEnv::kPitch: return params_.play.pitchSpline.contour;
|
|
case OverlayEnv::kFilter: return params_.play.filterSpline.contour;
|
|
case OverlayEnv::kAmp:
|
|
case OverlayEnv::kNone:
|
|
break;
|
|
}
|
|
return params_.play.ampSpline.contour;
|
|
}
|
|
|
|
VelocityCurve& ReaSamplerEditor::splineFor(OverlayEnv which) {
|
|
return const_cast<VelocityCurve&>(std::as_const(*this).splineFor(which));
|
|
}
|
|
|
|
bool ReaSamplerEditor::overlayIsSpline() const {
|
|
switch (overlayEnv_) {
|
|
case OverlayEnv::kAmp: return params_.play.ampSpline.mode == EnvMode::Spline;
|
|
case OverlayEnv::kPitch: return params_.play.pitchSpline.mode == EnvMode::Spline;
|
|
case OverlayEnv::kFilter: return params_.play.filterSpline.mode == EnvMode::Spline;
|
|
case OverlayEnv::kNone: return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const VelocityCurve& ReaSamplerEditor::curveFor(CurveTarget target) const {
|
|
switch (target) {
|
|
case CurveTarget::kPitch: return params_.play.pitchVelocityCurve;
|
|
case CurveTarget::kFilter: return params_.play.filter.velocityCurve;
|
|
case CurveTarget::kAmp:
|
|
case CurveTarget::kNone:
|
|
// kNone reads as amp — a valid, harmless choice for a target-agnostic caller (a
|
|
// disabled deck cell still needs SOME curve to draw). editedCurve()'s mutable
|
|
// overload below refuses kNone rather than relying on this fallback to protect amp.
|
|
return params_.velocityCurve;
|
|
}
|
|
return params_.velocityCurve;
|
|
}
|
|
|
|
VelocityCurve& ReaSamplerEditor::curveFor(CurveTarget target) {
|
|
return const_cast<VelocityCurve&>(std::as_const(*this).curveFor(target));
|
|
}
|
|
|
|
const VelocityCurve& ReaSamplerEditor::editedCurve() const { return curveFor(curvePopup_); }
|
|
|
|
VelocityCurve& ReaSamplerEditor::editedCurve() {
|
|
// Refuses kNone rather than aliasing amp (see curvePopup_'s declaration for why); should be
|
|
// unreachable now that every writer of kNone also cancels the drag, but this is the second,
|
|
// independent line of defense.
|
|
if (curvePopup_ == CurveTarget::kNone) {
|
|
static VelocityCurve sink = VelocityCurve::flat();
|
|
return sink;
|
|
}
|
|
return curveFor(curvePopup_);
|
|
}
|
|
|
|
} // namespace reasampler::vst
|