C: realtime VST3 setParam live — Vst3Fx::setParam (title/shortTitle->ParamID, setParamNormalized + inputParameterChanges queue), RealtimeFxChain dispatch vst3, test_60 delay 1s->0.25s giua stream khong restart
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "public.sdk/source/vst/hosting/module.h"
|
||||
#include "public.sdk/source/vst/hosting/hostclasses.h"
|
||||
#include "public.sdk/source/vst/hosting/processdata.h"
|
||||
#include "public.sdk/source/vst/hosting/parameterchanges.h"
|
||||
#include "public.sdk/source/vst/vstpresetfile.h"
|
||||
#include "public.sdk/source/common/memorystream.h"
|
||||
#include "pluginterfaces/vst/ivstaudioprocessor.h"
|
||||
@@ -54,6 +55,7 @@
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -388,6 +390,9 @@ struct Vst3FxState {
|
||||
bool controllerIsComponent = false;
|
||||
Steinberg::IPlugFrame* plugFrame = nullptr; // owned; freed in closeEditor
|
||||
IPtr<IPlugView> view; // editor view while GUI open
|
||||
Steinberg::Vst::ParameterChanges paramChanges; // live setParam queue -> inputParameterChanges
|
||||
std::mutex paramMutex; // setParam (loop/worker) vs processAudio
|
||||
std::map<std::string, Steinberg::Vst::ParamID> paramByName; // lower(title/shortTitle) -> id
|
||||
};
|
||||
|
||||
// Class selection for FX: any kVstAudioEffectClass whose subcategory does NOT
|
||||
@@ -534,6 +539,25 @@ bool vst3FxLoadInner(Vst3FxState* s, const std::string& path, double sampleRate,
|
||||
if (component->getBusInfo(kAudio, kOutput, 0, bi) == kResultOk && bi.channelCount > 0)
|
||||
s->outputChannels = bi.channelCount;
|
||||
|
||||
// Live setParam: map lowercased title/shortTitle -> ParamID so the
|
||||
// realtime chain can address VST3 params by name (no UI-side tags).
|
||||
// Titles are UTF-16 (String128); keep the ASCII subset lowercased.
|
||||
for (int32 pi = 0; pi < controller->getParameterCount(); ++pi) {
|
||||
Steinberg::Vst::ParameterInfo info = {};
|
||||
if (controller->getParameterInfo(pi, info) != kResultOk) continue;
|
||||
auto addName = [&](const Steinberg::Vst::TChar* nm) {
|
||||
if (!nm) return;
|
||||
std::string k;
|
||||
for (const Steinberg::Vst::TChar* p = nm; p && *p && k.size() < 64; ++p) {
|
||||
if (*p < 0x80) k.push_back((char)std::tolower((unsigned char)*p));
|
||||
}
|
||||
if (k.empty()) return;
|
||||
s->paramByName[k] = info.id;
|
||||
};
|
||||
addName(info.title);
|
||||
addName(info.shortTitle);
|
||||
}
|
||||
|
||||
s->module = std::move(module);
|
||||
s->component = std::move(component);
|
||||
s->controller = std::move(controller);
|
||||
@@ -599,7 +623,10 @@ public:
|
||||
s->processData.processMode = kRealtime;
|
||||
s->processData.numSamples = (int32)n;
|
||||
s->processData.inputEvents = nullptr;
|
||||
s->processData.inputParameterChanges = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(s->paramMutex);
|
||||
s->processData.inputParameterChanges = &s->paramChanges;
|
||||
}
|
||||
s->processData.processContext = &s->processContext;
|
||||
s->processContext.projectTimeSamples += n;
|
||||
s->processContext.projectTimeMusic =
|
||||
@@ -640,6 +667,12 @@ public:
|
||||
std::memset(outL, 0, n * sizeof(float));
|
||||
std::memset(outR, 0, n * sizeof(float));
|
||||
}
|
||||
{
|
||||
// Each block consumes the queue once; clear so the next setParam
|
||||
// writes fresh points (adelay reads the LAST point).
|
||||
std::lock_guard<std::mutex> lk(s->paramMutex);
|
||||
s->paramChanges.clearQueue();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Apply preset: base64 of a raw .vstpreset FILE (VST3 magic + chunk list)
|
||||
@@ -745,6 +778,52 @@ public:
|
||||
append32((uint32_t)ctrl.size());
|
||||
blob.insert(blob.end(), ctrl.begin(), ctrl.end());
|
||||
base64EncodeTo(blob.data(), blob.size(), out);
|
||||
#endif
|
||||
}
|
||||
// Live param change (realtime chain, NO restart): address by lowercase
|
||||
// title/shortTitle or numeric ParamID; value normalized 0..1. Delivered
|
||||
// via setParamNormalized (controller/UI state) + inputParameterChanges
|
||||
// queue (split plugins read the queue in process() — same rule as
|
||||
// Vst3Instrument). ponytail: beginEdit/endEdit automation gestures when a
|
||||
// plugin ignores setParamNormalized (fallback: restart with new preset).
|
||||
bool setParam(const std::string& key, double value) {
|
||||
#ifndef HAVE_VST3SDK
|
||||
(void)key; (void)value;
|
||||
return false;
|
||||
#else
|
||||
auto* s = static_cast<Vst3FxState*>(state_);
|
||||
if (!s || !s->controller) return false;
|
||||
Steinberg::Vst::ParamID tag = 0;
|
||||
bool found = false;
|
||||
try { // numeric ParamID passes through
|
||||
size_t consumed = 0;
|
||||
unsigned long num = std::stoul(key, &consumed);
|
||||
if (consumed == key.size()) { tag = (Steinberg::Vst::ParamID)num; found = true; }
|
||||
} catch (...) {}
|
||||
if (!found) {
|
||||
std::string k = key;
|
||||
std::transform(k.begin(), k.end(), k.begin(),
|
||||
[](unsigned char c) { return (char)std::tolower(c); });
|
||||
auto it = s->paramByName.find(k);
|
||||
if (it == s->paramByName.end()) {
|
||||
std::cerr << "[RealtimeFx] vst3 setParam unknown key: " << key << std::endl;
|
||||
return false;
|
||||
}
|
||||
tag = it->second;
|
||||
}
|
||||
Steinberg::Vst::ParamValue v =
|
||||
(Steinberg::Vst::ParamValue)std::max(0.0, std::min(1.0, value));
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(s->paramMutex);
|
||||
s->controller->setParamNormalized(tag, v);
|
||||
int32 idx = 0;
|
||||
if (Steinberg::Vst::IParamValueQueue* q =
|
||||
s->paramChanges.addParameterData(tag, idx))
|
||||
q->addPoint(0, v, idx);
|
||||
}
|
||||
std::cout << "[RealtimeFx] vst3 setParam '" << key << "' -> tag " << tag
|
||||
<< " = " << v << std::endl;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
// Native editor: attach the plugin's IPlugView to a host window (must run
|
||||
@@ -1233,6 +1312,7 @@ void RealtimeFxChain::setParam(int slot, const std::string& key, double value) {
|
||||
if (c && slot >= 0 && (size_t)slot < c->entries.size()) {
|
||||
auto& e = c->entries[(size_t)slot];
|
||||
if (e.bfx) e.bfx->setParam(key, value);
|
||||
else if (e.fx) e.fx->setParam(key, value);
|
||||
return;
|
||||
}
|
||||
// Chain chưa swap (SET_PARAM tới trước setChain) — stash, worker áp sau swap.
|
||||
@@ -1343,6 +1423,7 @@ void RealtimeFxChain::Impl::workerLoop() {
|
||||
if (p.slot < 0 || (size_t)p.slot >= chain_->entries.size()) continue;
|
||||
auto& e = chain_->entries[(size_t)p.slot];
|
||||
if (e.bfx) e.bfx->setParam(p.key, p.value);
|
||||
else if (e.fx) e.fx->setParam(p.key, p.value);
|
||||
}
|
||||
pending_.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user