Stack L/R waveform lanes in stereo mode, with overlays drawn once at full band height
This commit is contained in:
@@ -48,6 +48,8 @@ void ReaSamplerEditor::refreshFromBank() {
|
||||
// Main/UI thread only — reads the live bank over the bridge (allocates, calls REAPER).
|
||||
thumbCache_.clear(); // a bank edit may have re-captured/removed a sample; drop stale peaks
|
||||
pcmCache_.clear(); // and its decoded PCM (the waveform + snap source)
|
||||
channelPcmId_.clear();
|
||||
channelPcm_ = ChannelPcm{};
|
||||
if (!processor_) {
|
||||
samples_.clear();
|
||||
banks_.clear();
|
||||
@@ -202,42 +204,62 @@ int ReaSamplerEditor::effectiveRoot() const {
|
||||
return 60;
|
||||
}
|
||||
|
||||
std::string ReaSamplerEditor::samplePathFor(const std::string& sampleId) const {
|
||||
// SampleChoice is the browser's metadata projection and does not carry the WAV path, so
|
||||
// resolve it from the live bank blob (selectSample).
|
||||
if (!processor_) return {};
|
||||
auto banksJson = processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
||||
if (banksJson) {
|
||||
if (auto sel = selectSample(*banksJson, sampleId)) return sel->relativePath;
|
||||
}
|
||||
// Fallback: the bank blob is not readable (extension absent / not yet parsed) or the id
|
||||
// went stale there — the instance-owned ref still carries the path, so a self-contained
|
||||
// instance draws its loaded sound's waveform regardless.
|
||||
const SampleRefs refs = processor_->sampleRefs();
|
||||
if (const SelectedSample* r = findRef(refs, sampleId)) return r->relativePath;
|
||||
return {};
|
||||
}
|
||||
|
||||
const ReaSamplerEditor::ChannelPcm& ReaSamplerEditor::channelPcmFor(
|
||||
const std::string& sampleId) {
|
||||
if (channelPcmId_ == sampleId && !sampleId.empty()) return channelPcm_;
|
||||
|
||||
// A failed decode is still cached (channelCount stays 0) so a broken/missing file is not
|
||||
// re-read on every paint.
|
||||
channelPcmId_ = sampleId;
|
||||
channelPcm_ = ChannelPcm{};
|
||||
const std::string relativePath = samplePathFor(sampleId);
|
||||
if (relativePath.empty()) return channelPcm_;
|
||||
|
||||
const std::string projectDir = processor_->bridge().activeProjectDir();
|
||||
const std::vector<std::uint8_t> bytes =
|
||||
readFileBytes(resolveBankFile(projectDir, relativePath)); // empty on any failure
|
||||
const WavLayout layout = parseWavLayout(bytes);
|
||||
if (layout.valid) {
|
||||
channelPcm_.interleaved = extractFloatFrames(bytes, layout, 0, layout.frameCount());
|
||||
channelPcm_.channelCount = static_cast<int>(layout.channelCount);
|
||||
}
|
||||
return channelPcm_;
|
||||
}
|
||||
|
||||
const std::vector<AudioSample>& ReaSamplerEditor::monoPcmFor(const std::string& sampleId) {
|
||||
auto it = pcmCache_.find(sampleId);
|
||||
if (it != pcmCache_.end()) return it->second;
|
||||
|
||||
// SampleChoice is the browser's metadata projection and does not carry the WAV path, so
|
||||
// resolve the path from the live bank blob (selectSample) and decode via the shared WAV
|
||||
// parse. Every failure path caches an empty vector so a broken/missing file is not
|
||||
// re-decoded on every paint. Keyed by id (width-independent) — the thumbnail bins this at
|
||||
// whatever width, the snap scans it directly.
|
||||
std::string relativePath;
|
||||
// Every failure path caches an empty vector so a broken/missing file is not re-decoded on
|
||||
// every paint. Keyed by id (width-independent) — the thumbnail bins this at whatever
|
||||
// width, the snap scans it directly.
|
||||
std::vector<AudioSample> mono;
|
||||
if (processor_) {
|
||||
auto banksJson =
|
||||
processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
||||
if (banksJson) {
|
||||
if (auto sel = selectSample(*banksJson, sampleId)) relativePath = sel->relativePath;
|
||||
}
|
||||
if (relativePath.empty()) {
|
||||
// Fallback: the bank blob is not readable (extension absent / not yet parsed) or
|
||||
// the id went stale there — the instance-owned ref still carries the path, so a
|
||||
// self-contained instance draws its loaded sound's waveform regardless.
|
||||
const SampleRefs refs = processor_->sampleRefs();
|
||||
if (const SelectedSample* r = findRef(refs, sampleId)) {
|
||||
relativePath = r->relativePath;
|
||||
}
|
||||
}
|
||||
if (!relativePath.empty()) {
|
||||
const std::string projectDir = processor_->bridge().activeProjectDir();
|
||||
const std::string abs = resolveBankFile(projectDir, relativePath);
|
||||
const std::vector<std::uint8_t> bytes = readFileBytes(abs); // empty on any failure
|
||||
const WavLayout layout = parseWavLayout(bytes);
|
||||
if (layout.valid) {
|
||||
std::vector<AudioSample> interleaved =
|
||||
extractFloatFrames(bytes, layout, 0, layout.frameCount());
|
||||
mono = downmixToMono(interleaved, layout.channelCount);
|
||||
}
|
||||
const std::string relativePath = samplePathFor(sampleId);
|
||||
if (!relativePath.empty()) {
|
||||
const std::string projectDir = processor_->bridge().activeProjectDir();
|
||||
const std::string abs = resolveBankFile(projectDir, relativePath);
|
||||
const std::vector<std::uint8_t> bytes = readFileBytes(abs); // empty on any failure
|
||||
const WavLayout layout = parseWavLayout(bytes);
|
||||
if (layout.valid) {
|
||||
std::vector<AudioSample> interleaved =
|
||||
extractFloatFrames(bytes, layout, 0, layout.frameCount());
|
||||
mono = downmixToMono(interleaved, layout.channelCount);
|
||||
}
|
||||
}
|
||||
auto ins = pcmCache_.emplace(sampleId, std::move(mono));
|
||||
|
||||
Reference in New Issue
Block a user