Report the instrument's automatable parameters to the host under a frozen id table, in signal-flow order, with real units
42 of 44 ids issued: pitch key-track and Trigger length stay reserved pending a live path. Master gain reclassified Live — it never reloaded.
This commit is contained in:
@@ -13,11 +13,12 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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/param/param_format.h" // THE formatter every value label reads through
|
||||
#include "core/instrument/param/param_id.h" // whether a control has a parameter row at all
|
||||
#include "core/instrument/ui/bake_hold.h" // the Hold knob's ladder map
|
||||
#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/deck_values.h" // the parameter-set binding
|
||||
#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
|
||||
@@ -33,18 +34,13 @@ using instrument::ui::deckHeight;
|
||||
using instrument::ui::kDeckKnobSize;
|
||||
using instrument::ui::kPad;
|
||||
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::filterCutoffHzFromNorm;
|
||||
using instrument::engine::filter::filterDriveDepthFromNorm;
|
||||
using instrument::engine::filter::filterQFromNorm;
|
||||
using util::clamp01;
|
||||
|
||||
namespace {
|
||||
@@ -220,117 +216,36 @@ void ReaSamplerEditor::applyDeckKnob(int id, double norm) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
char buf[24];
|
||||
buf[0] = '\0';
|
||||
const PlaySeconds& play = params_.play;
|
||||
switch (id < 0 ? ParamControl::kCount : static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kAttack:
|
||||
formatEnvTimeMs(play.adsr.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kHold:
|
||||
formatEnvTimeMs(play.adsr.holdSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kDecay:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
formatEnvTimeMs(play.trigAhd.decaySeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kPitchEnvAttack:
|
||||
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:
|
||||
formatEnvTimeMs(play.pitchEnv.shape.decaySeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
snprintf(buf, sizeof(buf), "%+.1fst", play.pitchEnv.peakSemitones); break;
|
||||
case ParamControl::kKeyTrack:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", params_.keyTrack * 100.0); break;
|
||||
case ParamControl::kRate: {
|
||||
// One decimal below 100 % only: the taper is linear in semitones, so the lower half
|
||||
// spends 50 percentage points on the same twelve semitones the upper half spends
|
||||
// 100 on — a whole percent is twice as coarse a step down there.
|
||||
const double pct = play.playRate * 100.0;
|
||||
snprintf(buf, sizeof(buf), pct < 100.0 ? "%.1f%%" : "%.0f%%", pct);
|
||||
break;
|
||||
}
|
||||
case ParamControl::kPitch:
|
||||
snprintf(buf, sizeof(buf), "%+.1fst", play.pitchOffsetSemitones); break;
|
||||
case ParamControl::kVoiceCount:
|
||||
snprintf(buf, sizeof(buf), "%d", voiceCount_); break;
|
||||
case ParamControl::kMasterGain:
|
||||
formatMasterGainLabel(deckControlNorm(id), buf, sizeof(buf)); break;
|
||||
// Filter readouts run the stored normalized positions back through the module's OWN
|
||||
// laws, so what the label says is what the kernel is solved for.
|
||||
case ParamControl::kFilterMorph: {
|
||||
const double m = play.filter.settings.morphNorm;
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", m * 100.0);
|
||||
break;
|
||||
}
|
||||
case ParamControl::kFilterCutoff: {
|
||||
const float hz = filterCutoffHzFromNorm(play.filter.settings.cutoffNorm);
|
||||
if (hz >= 1000.0f) snprintf(buf, sizeof(buf), "%.2fk", hz / 1000.0f);
|
||||
else snprintf(buf, sizeof(buf), "%.0fHz", hz);
|
||||
break;
|
||||
}
|
||||
case ParamControl::kFilterQ:
|
||||
snprintf(buf, sizeof(buf), "%.2f",
|
||||
static_cast<double>(filterQFromNorm(play.filter.settings.resonanceNorm)));
|
||||
break;
|
||||
case ParamControl::kFilterDrive:
|
||||
snprintf(buf, sizeof(buf), "%.2f",
|
||||
static_cast<double>(filterDriveDepthFromNorm(play.filter.settings.driveNorm)));
|
||||
break;
|
||||
case ParamControl::kFilterModAmt:
|
||||
snprintf(buf, sizeof(buf), "%+.0f%%", play.filter.modAmount * 100.0); break;
|
||||
case ParamControl::kFilterVel:
|
||||
snprintf(buf, sizeof(buf), "%+.0f%%", play.filter.velAmount * 100.0); break;
|
||||
case ParamControl::kFilterKeyTrack:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.filter.keyTrack * 100.0); break;
|
||||
case ParamControl::kFilterEnvAttack:
|
||||
formatEnvTimeMs(play.filter.env.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterEnvHold:
|
||||
formatEnvTimeMs(play.filter.env.holdSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterEnvDecay:
|
||||
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:
|
||||
formatEnvTimeMs(play.filter.env.releaseSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kFilterTrigAttack:
|
||||
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:
|
||||
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:
|
||||
case ParamControl::kReleaseCurve:
|
||||
case ParamControl::kTrigAttackCurve:
|
||||
case ParamControl::kTrigDecayCurve:
|
||||
case ParamControl::kPitchEnvAttackCurve:
|
||||
case ParamControl::kPitchEnvDecayCurve:
|
||||
case ParamControl::kFilterEnvAttackCurve:
|
||||
case ParamControl::kFilterEnvDecayCurve:
|
||||
case ParamControl::kFilterEnvReleaseCurve:
|
||||
case ParamControl::kFilterTrigAttackCurve:
|
||||
case ParamControl::kFilterTrigDecayCurve:
|
||||
snprintf(buf, sizeof(buf), "^%.2f", curveExponentFor(id, play));
|
||||
break;
|
||||
default:
|
||||
// The chrome knobs (preview velocity, bake Hold) are labeled at their own call
|
||||
// site; nothing else here.
|
||||
break;
|
||||
double ReaSamplerEditor::deckPlainValue(int id) const {
|
||||
const auto deck = static_cast<instrument::ui::DeckParam>(id);
|
||||
// A curve exponent is read off its stored field, never round-tripped through the knob law:
|
||||
// that law's centre detent snaps anything near-neutral back to exactly 1.0, so a round trip
|
||||
// would misreport a stored exponent that isn't neutral as 1.00. The host has only the norm
|
||||
// and therefore cannot make this distinction — param/CLAUDE.md records the divergence.
|
||||
if (instrument::ui::deckParamUnit(deck) == instrument::ui::UnitCategory::Exponent) {
|
||||
return curveExponentFor(id, params_.play);
|
||||
}
|
||||
return std::string(buf);
|
||||
return instrument::param::toPlain(deck, deckControlNorm(id));
|
||||
}
|
||||
|
||||
std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
if (id < 0 || id >= static_cast<int>(ParamControl::kCount)) return {};
|
||||
const auto deck = static_cast<instrument::ui::DeckParam>(id);
|
||||
// The one deck knob with no plain-value layer at all: an already-integer count.
|
||||
if (deck == ParamControl::kVoiceCount) {
|
||||
char buf[24];
|
||||
snprintf(buf, sizeof(buf), "%d", voiceCount_);
|
||||
return std::string(buf);
|
||||
}
|
||||
if (instrument::param::paramIdFor(deck) == 0) return {};
|
||||
|
||||
// The digits come from the ONE formatter; everything the editor adds around them is static
|
||||
// chrome — a constant prefix or suffix cannot diverge from what the host shows.
|
||||
char digits[24];
|
||||
instrument::param::formatPlainFor(deck, deckPlainValue(id), digits, sizeof(digits));
|
||||
const char* caret =
|
||||
instrument::ui::deckParamUnit(deck) == instrument::ui::UnitCategory::Exponent ? "^" : "";
|
||||
return caret + std::string(digits) + instrument::param::unitStringFor(deck);
|
||||
}
|
||||
|
||||
EnvClampBounds ReaSamplerEditor::envClampBounds() const {
|
||||
|
||||
Reference in New Issue
Block a user