Retire the zone system: one capture = one parameter set, and re-seam the engine and Sample face into bands
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// editor_session.cpp — the ReaSamplerEditor's session/bridge state: construction, the
|
||||
// live-bank snapshot (refreshFromBank / rebuildVisible), the sync tick, the
|
||||
// commit-and-reload seam, selection loading, the picked-capture marker resolution/upsert
|
||||
// helpers, and the decoded-PCM + peak thumbnail caches. UI thread only; every edit commits
|
||||
// off the audio thread via the processor's reloadInstrument.
|
||||
// commit-and-reload seam, selection loading, the loaded capture's marker resolution, and
|
||||
// the decoded-PCM + peak thumbnail caches. UI thread only; every edit commits off the audio
|
||||
// thread via the processor's reloadInstrument.
|
||||
|
||||
#include "shell/instrument/reasampler_editor.h"
|
||||
|
||||
@@ -38,8 +38,8 @@ using util::readFileBytes;
|
||||
|
||||
ReaSamplerEditor::ReaSamplerEditor(ReaSamplerProcessor* processor)
|
||||
: CPluginView(nullptr), processor_(processor) {
|
||||
// Default view size, tuned to the Sample-face band heights: title + hero waveform +
|
||||
// cluster + control strip. 840x620 clears the full face without scroll on 1080p.
|
||||
// Default view size, tuned to the three band heights: chrome + two-lane waveform +
|
||||
// deck row. 840x620 clears the full face without scroll on 1080p.
|
||||
ViewRect r(0, 0, 840, 620);
|
||||
setRect(r);
|
||||
}
|
||||
@@ -53,30 +53,21 @@ void ReaSamplerEditor::refreshFromBank() {
|
||||
banks_.clear();
|
||||
visible_.clear();
|
||||
selectedId_.clear();
|
||||
map_.zones.clear();
|
||||
selectedZone_ = -1;
|
||||
params_ = InstrumentParams{};
|
||||
return;
|
||||
}
|
||||
auto banksJson = processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
||||
samples_ = banksJson ? listSamples(*banksJson) : std::vector<SampleChoice>{};
|
||||
banks_ = banksJson ? listBanks(*banksJson) : std::vector<BankChoice>{};
|
||||
selectedId_ = processor_->selectedSampleId();
|
||||
const auto prevZoneCount = static_cast<int>(map_.zones.size());
|
||||
map_ = processor_->performanceMap();
|
||||
params_ = processor_->instrumentParams();
|
||||
channelMode_ = processor_->channelMode();
|
||||
voiceCount_ = processor_->voiceCount();
|
||||
voiceMode_ = processor_->voiceMode();
|
||||
monoTrigger_ = processor_->monoTrigger();
|
||||
if (selectedZone_ >= static_cast<int>(map_.zones.size())) selectedZone_ = -1;
|
||||
// A refresh that emptied the selection closes the curve popup — an open-but-invisible
|
||||
// modal would otherwise swallow clicks on the empty state.
|
||||
if (selectedId_.empty() && map_.zones.empty()) curvePopupOpen_ = false;
|
||||
// On the Zone surface, close the popup if the zone count changed at all — a mid-list
|
||||
// deletion can leave selectedZone_ in range but silently naming a different zone.
|
||||
if (view_ == View::kZone && curvePopupOpen_) {
|
||||
const auto newZoneCount = static_cast<int>(map_.zones.size());
|
||||
if (selectedZone_ < 0 || newZoneCount != prevZoneCount) curvePopupOpen_ = false;
|
||||
}
|
||||
if (selectedId_.empty()) curvePopupOpen_ = false;
|
||||
// Drop a filter that names a bank no longer present.
|
||||
if (!activeFilterBankId_.empty()) {
|
||||
bool found = false;
|
||||
@@ -127,13 +118,13 @@ void ReaSamplerEditor::onSyncTimer() {
|
||||
#endif // _WIN32
|
||||
|
||||
void ReaSamplerEditor::commitAndReload() {
|
||||
// UI thread only. Publishes the edited selection + zones, then rebuilds off the audio
|
||||
// thread. The reload also copies the picked capture's file ref + intrinsics into the
|
||||
// instance-owned refs table — a browser load is the moment the instance becomes
|
||||
// UI thread only. Publishes the edited selection + parameter set, then rebuilds off the
|
||||
// audio thread. The reload also copies the loaded capture's file ref + intrinsics into
|
||||
// the instance-owned refs table — a browser load is the moment the instance becomes
|
||||
// self-contained for that sample.
|
||||
if (!processor_) return;
|
||||
processor_->setSelectedSampleId(selectedId_);
|
||||
processor_->setPerformanceMap(map_);
|
||||
processor_->setInstrumentParams(params_);
|
||||
processor_->reloadInstrument();
|
||||
// The reload may have auto-defaulted the channel mode (implicit only) — re-read so the
|
||||
// toggle draws what the engine actually decoded with.
|
||||
@@ -144,23 +135,25 @@ void ReaSamplerEditor::commitAndReload() {
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::loadSelection(const std::string& id) {
|
||||
// A Sample-face load REPLACES the loaded sound: the previous sample's materialized
|
||||
// full-range zone must not linger, or first-match resolve would keep playing it.
|
||||
// Authored Zone-view maps (narrow key ranges) are left untouched.
|
||||
// A load REPLACES the loaded sound. The shaping parameters (play mode, envelopes, pitch
|
||||
// engine, key-track, velocity curve) are NOT reset — the one set governs whatever is
|
||||
// loaded, so a load swaps the sound and keeps the settings. The three CAPTURE-ANCHORED
|
||||
// overrides are: a root, a loop span and a start frame all name positions in the
|
||||
// OUTGOING capture and mean nothing in the new one, so they clear and the new capture
|
||||
// plays from its own bank intrinsics.
|
||||
selectedId_ = id;
|
||||
if (reconcileSingleCaptureZones(map_, selectedId_)) {
|
||||
selectedZone_ = map_.zones.empty() ? -1 : 0;
|
||||
}
|
||||
params_.rootOverride.reset();
|
||||
params_.loopOverride.reset();
|
||||
params_.startPoint.reset();
|
||||
commitAndReload();
|
||||
}
|
||||
|
||||
ReaSamplerEditor::SetupMarkers ReaSamplerEditor::pickedMarkers(std::int64_t frames) const {
|
||||
SetupMarkers m;
|
||||
// Seed from the bank's intrinsic loop (fact about the file), then let a per-zone override
|
||||
// for the picked id win (the instrument's performance choice). Read the loop intrinsic from
|
||||
// the live bank blob (the same path selectSample uses); when that is not readable (extension
|
||||
// absent / not yet parsed) the instance-owned ref carries the same intrinsics. The override
|
||||
// lives in map_.
|
||||
// Seed from the bank's intrinsic loop (fact about the file), then let the parameter set's
|
||||
// override win (the instrument's performance choice). Read the loop intrinsic from the
|
||||
// live bank blob (the same path selectSample uses); when that is not readable (extension
|
||||
// absent / not yet parsed) the instance-owned ref carries the same intrinsics.
|
||||
if (processor_) {
|
||||
std::optional<SelectedSample> sel;
|
||||
auto banksJson =
|
||||
@@ -176,17 +169,13 @@ ReaSamplerEditor::SetupMarkers ReaSamplerEditor::pickedMarkers(std::int64_t fram
|
||||
m.loopEnd = sel->loop.end;
|
||||
}
|
||||
}
|
||||
// The override (loop + start) on a zone for the picked id supersedes the intrinsic.
|
||||
for (const PerformanceZone& z : map_.zones) {
|
||||
if (z.sampleId != selectedId_) continue;
|
||||
if (z.loopOverride) {
|
||||
m.hasLoop = z.loopOverride->hasLoop;
|
||||
m.loopStart = z.loopOverride->start;
|
||||
m.loopEnd = z.loopOverride->end;
|
||||
}
|
||||
if (z.startPoint) m.start = *z.startPoint;
|
||||
break;
|
||||
// The parameter set's override (loop + start) supersedes the intrinsic.
|
||||
if (params_.loopOverride) {
|
||||
m.hasLoop = params_.loopOverride->hasLoop;
|
||||
m.loopStart = params_.loopOverride->start;
|
||||
m.loopEnd = params_.loopOverride->end;
|
||||
}
|
||||
if (params_.startPoint) m.start = *params_.startPoint;
|
||||
// Default an unset loop's end to the sample length so the loop markers have somewhere sane
|
||||
// to sit before the user drags (loopStart stays 0). The "no loop" state is m.hasLoop==false;
|
||||
// the markers are still drawn (drag one to CREATE a loop).
|
||||
@@ -194,79 +183,23 @@ ReaSamplerEditor::SetupMarkers ReaSamplerEditor::pickedMarkers(std::int64_t fram
|
||||
return m;
|
||||
}
|
||||
|
||||
int ReaSamplerEditor::upsertPickedOverride(const SetupMarkers& m) {
|
||||
// Find-or-append the zone for selectedId_ and write the loop/start override fields. The
|
||||
// bank intrinsic is never written (read-only bank consumer). selectedId_ must be
|
||||
// non-empty; callers are responsible for that guard. Returns the zone index (0-based) so
|
||||
// callers can update selectedZone_.
|
||||
void ReaSamplerEditor::applyMarkers(const SetupMarkers& m) {
|
||||
// Write the edited markers into the parameter set as the loop/start override. The bank
|
||||
// intrinsic is never written (read-only bank consumer).
|
||||
SampleLoop loop;
|
||||
loop.hasLoop = m.hasLoop;
|
||||
loop.start = m.loopStart;
|
||||
loop.end = m.loopEnd;
|
||||
for (int i = 0; i < static_cast<int>(map_.zones.size()); ++i) {
|
||||
PerformanceZone& z = map_.zones[static_cast<std::size_t>(i)];
|
||||
if (z.sampleId == selectedId_) {
|
||||
z.loopOverride = loop;
|
||||
z.startPoint = m.start;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
PerformanceZone z;
|
||||
z.sampleId = selectedId_;
|
||||
z.lowNote = 0;
|
||||
z.highNote = 127;
|
||||
z.loopOverride = loop;
|
||||
z.startPoint = m.start;
|
||||
map_.zones.push_back(z);
|
||||
return static_cast<int>(map_.zones.size()) - 1;
|
||||
}
|
||||
|
||||
PerformanceZone ReaSamplerEditor::effectiveSampleZone() const {
|
||||
// The picked id's one-zone override, if the map already carries one; else a product-default
|
||||
// zone bound to the picked id (not appended — a read-only resolve; a control edit
|
||||
// materializes it via ensureSampleZone).
|
||||
for (const PerformanceZone& z : map_.zones) {
|
||||
if (z.sampleId == selectedId_) return z;
|
||||
}
|
||||
PerformanceZone z;
|
||||
z.sampleId = selectedId_;
|
||||
z.lowNote = 0;
|
||||
z.highNote = 127;
|
||||
return z;
|
||||
params_.loopOverride = loop;
|
||||
params_.startPoint = m.start;
|
||||
}
|
||||
|
||||
int ReaSamplerEditor::effectiveRoot() const {
|
||||
int root = 60;
|
||||
if (params_.rootOverride) return *params_.rootOverride;
|
||||
for (const SampleChoice& s : samples_) {
|
||||
if (s.id == selectedId_ && s.rootNote) root = *s.rootNote;
|
||||
if (s.id == selectedId_ && s.rootNote) return *s.rootNote;
|
||||
}
|
||||
for (const PerformanceZone& z : map_.zones) {
|
||||
if (z.sampleId == selectedId_ && z.rootOverride) root = *z.rootOverride;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
int ReaSamplerEditor::ensureSampleZone() {
|
||||
if (selectedId_.empty()) return -1;
|
||||
for (int i = 0; i < static_cast<int>(map_.zones.size()); ++i) {
|
||||
if (map_.zones[static_cast<std::size_t>(i)].sampleId == selectedId_) return i;
|
||||
}
|
||||
PerformanceZone z;
|
||||
z.sampleId = selectedId_;
|
||||
z.lowNote = 0;
|
||||
z.highNote = 127;
|
||||
map_.zones.push_back(z);
|
||||
return static_cast<int>(map_.zones.size()) - 1;
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::commitPickedMarkers(const SetupMarkers& m) {
|
||||
// Materialize the edited markers as a per-zone loop/start override on the picked id (upsert):
|
||||
// a full-keyboard zone carrying the override. This plays identically to the un-zoned single
|
||||
// capture (one chromatic zone) and round-trips through the component state; the zone becomes
|
||||
// visible if the user opens the Zones panel. The bank intrinsic is never written.
|
||||
if (selectedId_.empty()) return;
|
||||
upsertPickedOverride(m);
|
||||
commitAndReload();
|
||||
return 60;
|
||||
}
|
||||
|
||||
const std::vector<AudioSample>& ReaSamplerEditor::monoPcmFor(const std::string& sampleId) {
|
||||
|
||||
Reference in New Issue
Block a user