// 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 #include // 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(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((std::max)(std::int64_t{0}, frames - startFrame)) / rate : 0.0; const double playSpan = rate > 0.0 ? static_cast(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(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(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