mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-12 15:28:11 +00:00
Merge pull request #110 from got-feedBack/reverse-vst-param-sync
Expose sandboxed VST parameter values via kListParameters
This commit is contained in:
@@ -422,6 +422,21 @@ void SandboxedProcessor::setSandboxedParameter(int index, float value)
|
|||||||
control->postNoReply(op::kSetParameter, juce::var(args.get()));
|
control->postNoReply(op::kSetParameter, juce::var(args.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
juce::var SandboxedProcessor::listSandboxedParameters()
|
||||||
|
{
|
||||||
|
if (!control || !isAlive()) return {};
|
||||||
|
juce::String err;
|
||||||
|
auto reply = control->request(op::kListParameters, {},
|
||||||
|
kDefaultReplyTimeoutMs, &err);
|
||||||
|
if (err.isNotEmpty())
|
||||||
|
{
|
||||||
|
VST_TRACE("[sandbox] listSandboxedParameters request failed: %s",
|
||||||
|
err.toRawUTF8());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return reply.getProperty("params", juce::var());
|
||||||
|
}
|
||||||
|
|
||||||
bool SandboxedProcessor::isAlive() const noexcept
|
bool SandboxedProcessor::isAlive() const noexcept
|
||||||
{
|
{
|
||||||
return alive.load(std::memory_order_acquire);
|
return alive.load(std::memory_order_acquire);
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ public:
|
|||||||
// Forward a parameter change to the sandboxed plugin over the control pipe.
|
// Forward a parameter change to the sandboxed plugin over the control pipe.
|
||||||
void setSandboxedParameter(int index, float value);
|
void setSandboxedParameter(int index, float value);
|
||||||
|
|
||||||
|
// Snapshot the sandboxed plugin's CURRENT parameter values (a blocking
|
||||||
|
// control-pipe round-trip). Returns the reply's "params" array var — one
|
||||||
|
// object per param: { index, name, value (normalized 0..1), label, text } —
|
||||||
|
// or an empty var on failure. A sandboxed plugin exposes no host-side JUCE
|
||||||
|
// parameter proxies, so this is how the host reads values the user changed in
|
||||||
|
// the plugin's own editor window. Call OFF the audio lock (blocking IPC).
|
||||||
|
juce::var listSandboxedParameters();
|
||||||
|
|
||||||
// Callback fired when the subprocess unexpectedly exits or its control
|
// Callback fired when the subprocess unexpectedly exits or its control
|
||||||
// pipe breaks. Always invoked from a background thread; mutex-guarded
|
// pipe breaks. Always invoked from a background thread; mutex-guarded
|
||||||
// so concurrent assignment from the owner thread + read from the I/O
|
// so concurrent assignment from the owner thread + read from the I/O
|
||||||
|
|||||||
@@ -730,6 +730,16 @@ std::vector<SignalChain::SlotSummary> SignalChain::getSlotSummaries() const
|
|||||||
juce::Array<SignalChain::ParamInfo> SignalChain::getParameters(int slotId) const
|
juce::Array<SignalChain::ParamInfo> SignalChain::getParameters(int slotId) const
|
||||||
{
|
{
|
||||||
juce::Array<ParamInfo> result;
|
juce::Array<ParamInfo> result;
|
||||||
|
|
||||||
|
// Resolve the processor under the lock, then act on it. For a SANDBOXED
|
||||||
|
// plugin the value read is a blocking control-pipe round-trip, which must
|
||||||
|
// NOT run while holding the audio lock (it would stall processBlock for the
|
||||||
|
// reply timeout), so we drop the lock before the IPC. Slot add/remove and
|
||||||
|
// this getter both run on the host's main thread, so the pointer stays valid
|
||||||
|
// after the lock is released — the same discipline getStateInformation()
|
||||||
|
// relies on for its off-lock kGetState round-trip.
|
||||||
|
slopsmith::sandbox::SandboxedProcessor* sandboxed = nullptr;
|
||||||
|
{
|
||||||
const juce::ScopedLock sl(lock);
|
const juce::ScopedLock sl(lock);
|
||||||
int idx = findSlotIndex(slotId);
|
int idx = findSlotIndex(slotId);
|
||||||
if (idx < 0) return result;
|
if (idx < 0) return result;
|
||||||
@@ -737,6 +747,11 @@ juce::Array<SignalChain::ParamInfo> SignalChain::getParameters(int slotId) const
|
|||||||
auto* proc = slots[idx]->processor.get();
|
auto* proc = slots[idx]->processor.get();
|
||||||
if (!proc) return result;
|
if (!proc) return result;
|
||||||
|
|
||||||
|
sandboxed = dynamic_cast<slopsmith::sandbox::SandboxedProcessor*>(proc);
|
||||||
|
if (sandboxed == nullptr)
|
||||||
|
{
|
||||||
|
// In-process plugin: JUCE parameter proxies are live — read them
|
||||||
|
// directly under the lock (cheap, no IPC).
|
||||||
auto& params = proc->getParameters();
|
auto& params = proc->getParameters();
|
||||||
for (int i = 0; i < params.size(); ++i)
|
for (int i = 0; i < params.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -749,6 +764,27 @@ juce::Array<SignalChain::ParamInfo> SignalChain::getParameters(int slotId) const
|
|||||||
result.add(info);
|
result.add(info);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sandboxed plugin: no host-side JUCE parameter proxies exist, so ask the
|
||||||
|
// subprocess for its live values over the control pipe (off the audio lock).
|
||||||
|
juce::var params = sandboxed->listSandboxedParameters();
|
||||||
|
if (auto* arr = params.getArray())
|
||||||
|
{
|
||||||
|
for (const auto& pv : *arr)
|
||||||
|
{
|
||||||
|
ParamInfo info;
|
||||||
|
info.index = (int) pv.getProperty("index", -1);
|
||||||
|
info.name = pv.getProperty("name", juce::String()).toString();
|
||||||
|
info.value = (float) (double) pv.getProperty("value", 0.0);
|
||||||
|
info.label = pv.getProperty("label", juce::String()).toString();
|
||||||
|
info.text = pv.getProperty("text", juce::String()).toString();
|
||||||
|
if (info.index >= 0)
|
||||||
|
result.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SignalChain::setParameter(int slotId, int paramIndex, float value)
|
void SignalChain::setParameter(int slotId, int paramIndex, float value)
|
||||||
|
|||||||
@@ -1266,6 +1266,37 @@ void dispatchRequest(HostState& st, int requestId, const juce::String& op,
|
|||||||
params[idx]->setValue((float)juce::jlimit(0.0, 1.0, vald));
|
params[idx]->setValue((float)juce::jlimit(0.0, 1.0, vald));
|
||||||
reply(true, {});
|
reply(true, {});
|
||||||
}
|
}
|
||||||
|
else if (op == op::kListParameters)
|
||||||
|
{
|
||||||
|
if (!st.plugin)
|
||||||
|
{
|
||||||
|
reply(false, {}, "no plugin loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Snapshot the plugin's CURRENT parameter values so the host can mirror
|
||||||
|
// edits the user made in the plugin's OWN editor window back into the
|
||||||
|
// in-app UI (a sandboxed plugin exposes no host-side JUCE parameter
|
||||||
|
// proxies, so this pipe round-trip is the only way the host reads them).
|
||||||
|
// Read-only; mirrors the in-process SignalChain::getParameters fields.
|
||||||
|
// getValue() is normalized 0..1 — same range setParameter takes.
|
||||||
|
auto params = st.plugin->getParameters();
|
||||||
|
juce::Array<juce::var> out;
|
||||||
|
out.ensureStorageAllocated(params.size());
|
||||||
|
for (int i = 0; i < params.size(); ++i)
|
||||||
|
{
|
||||||
|
auto* p = params[i];
|
||||||
|
juce::DynamicObject::Ptr po(new juce::DynamicObject());
|
||||||
|
po->setProperty("index", i);
|
||||||
|
po->setProperty("name", p->getName(128));
|
||||||
|
po->setProperty("value", (double) p->getValue());
|
||||||
|
po->setProperty("label", p->getLabel());
|
||||||
|
po->setProperty("text", p->getCurrentValueAsText());
|
||||||
|
out.add(juce::var(po.get()));
|
||||||
|
}
|
||||||
|
juce::DynamicObject::Ptr res(new juce::DynamicObject());
|
||||||
|
res->setProperty("params", juce::var(out));
|
||||||
|
reply(true, juce::var(res.get()));
|
||||||
|
}
|
||||||
else if (op == op::kShutdown)
|
else if (op == op::kShutdown)
|
||||||
{
|
{
|
||||||
reply(true, {});
|
reply(true, {});
|
||||||
|
|||||||
Reference in New Issue
Block a user