feat: run the per-voice filter between the pitch and amp stages, with its own deck

Params ride the one parameter set; payload v8 -> v9, off by default.
Deck composition moves to a pure deck_groups module in pitch -> filter -> amp order.
This commit is contained in:
2026-07-30 15:26:14 -04:00
parent c9c708a338
commit 67215509cb
25 changed files with 1354 additions and 191 deletions
+100
View File
@@ -0,0 +1,100 @@
// deck_groups.cpp — see deck_groups.h. Pure data; no host types.
#include "core/instrument/ui/deck_groups.h"
#include <utility>
namespace reasampler::instrument::ui {
namespace {
int id(DeckParam p) { return static_cast<int>(p); }
double clamp(double v, double lo, double hi) { return v < lo ? lo : (v > hi ? hi : v); }
} // namespace
double deckBipolarFromNorm(double norm) { return clamp(norm, 0.0, 1.0) * 2.0 - 1.0; }
double deckNormFromBipolar(double value) { return clamp(value, -1.0, 1.0) * 0.5 + 0.5; }
std::vector<DeckGroupDesc> sampleDeckGroups(PlayMode playMode) {
std::vector<DeckGroupDesc> out;
{
DeckGroupDesc pitch;
pitch.id = kGroupPitch;
pitch.captionWidth = 38;
pitch.captionToggle = {id(DeckParam::kPitchEngine), 48};
pitch.cellIds = {id(DeckParam::kKeyTrack)};
out.push_back(std::move(pitch));
}
{
DeckGroupDesc penv;
penv.id = kGroupPitchEnv;
penv.captionWidth = 58;
penv.captionToggle = {id(DeckParam::kPitchEnvEnable), 32};
penv.cellIds = {id(DeckParam::kPitchEnvAttack),
id(DeckParam::kPitchEnvDecay),
id(DeckParam::kPitchEnvDepth)};
out.push_back(std::move(penv));
}
{
// Tone shaping left-to-right, then the three modulation depths that all target cutoff.
DeckGroupDesc filter;
filter.id = kGroupFilter;
filter.captionWidth = 46;
filter.captionToggle = {id(DeckParam::kFilterEnable), 32};
filter.cellIds = {id(DeckParam::kFilterMorph),
id(DeckParam::kFilterCutoff),
id(DeckParam::kFilterQ),
id(DeckParam::kFilterDrive),
id(DeckParam::kFilterModAmt),
id(DeckParam::kFilterVel),
id(DeckParam::kFilterKeyTrack)};
filter.rowToggle = {id(DeckParam::kFilterLaw), 44};
out.push_back(std::move(filter));
}
{
DeckGroupDesc fenv;
fenv.id = kGroupFilterEnv;
fenv.captionWidth = 66;
fenv.cellIds = {id(DeckParam::kFilterEnvAttack),
id(DeckParam::kFilterEnvHold),
id(DeckParam::kFilterEnvDecay),
id(DeckParam::kFilterEnvSustain),
id(DeckParam::kFilterEnvRelease)};
out.push_back(std::move(fenv));
}
{
DeckGroupDesc amp;
amp.id = kGroupAmpEnv;
amp.captionWidth = 78;
amp.captionToggle = {id(DeckParam::kPlayMode), 44};
if (playMode == PlayMode::Gate) {
amp.cellIds = {id(DeckParam::kAttack), id(DeckParam::kHold),
id(DeckParam::kDecay), id(DeckParam::kSustain),
id(DeckParam::kRelease)};
} else {
// Trigger, time-ordered left-to-right (Fade In / Length % / Fade Out — matches
// the drawn envelope), plus the two reserved blanks that hold the Gate width.
amp.cellIds = {id(DeckParam::kTrigFadeIn), id(DeckParam::kTrigLength),
id(DeckParam::kTrigFadeOut), -1, -1};
}
out.push_back(std::move(amp));
}
{
DeckGroupDesc voice;
voice.id = kGroupVoice;
voice.captionWidth = 38;
voice.captionToggle = {id(DeckParam::kVoiceMode), 40};
voice.cellIds = {id(DeckParam::kVoiceCount)};
voice.rowToggle = {id(DeckParam::kMonoTrigger), 44};
out.push_back(std::move(voice));
}
{
DeckGroupDesc master;
master.id = kGroupMaster;
master.captionWidth = 46;
master.cellIds = {id(DeckParam::kMasterGain)};
out.push_back(std::move(master));
}
return out;
}
} // namespace reasampler::instrument::ui