From 8a4b430e8389eab2c174ae96ca7b721540ea2707 Mon Sep 17 00:00:00 2001 From: Nick Thompson Date: Sun, 14 Jan 2024 18:19:23 -0500 Subject: [PATCH] Relay graph events to webview --- native/LambdaTimer.h | 28 ++++++++++++++++++++++++++++ native/PluginProcessor.cpp | 34 ++++++++++++++++++++++++++++++++++ native/PluginProcessor.h | 4 ++++ src/main.jsx | 4 ++++ 4 files changed, 70 insertions(+) create mode 100644 native/LambdaTimer.h diff --git a/native/LambdaTimer.h b/native/LambdaTimer.h new file mode 100644 index 0000000..71081cd --- /dev/null +++ b/native/LambdaTimer.h @@ -0,0 +1,28 @@ +#pragma once + +#include + + +//============================================================================== +// A small helper class for invoking a lambda periodically +// via juce::Timer +template +class LambdaTimer : public juce::Timer { +public: + LambdaTimer(int rate, Fn&& f) : callback(std::move(f)) { + startTimerHz(rate); + } + + ~LambdaTimer() { + stopTimer(); + } + + void timerCallback() override { + if (callback) { + callback(); + } + } + +private: + Fn callback; +}; diff --git a/native/PluginProcessor.cpp b/native/PluginProcessor.cpp index 2b7ab85..1739fec 100644 --- a/native/PluginProcessor.cpp +++ b/native/PluginProcessor.cpp @@ -217,6 +217,11 @@ void EffectsPluginProcessor::handleAsyncUpdate() // TODO: This is definitely not thread-safe! It could delete a Runtime instance while // the real-time thread is using it. Depends on when the host will call prepareToPlay. runtime = std::make_unique>(lastKnownSampleRate, lastKnownBlockSize); + + eventsTimer = std::make_unique>>(60, [this]() { + dispatchGraphEvents(); + }); + initJavaScriptEngine(); } @@ -371,6 +376,35 @@ void EffectsPluginProcessor::dispatchStateChange() jsContext.evaluate(expr); } +void EffectsPluginProcessor::dispatchGraphEvents() +{ + const auto* kDispatchScript = R"script( +(function() { + if (typeof globalThis.__receiveGraphEvents__ !== 'function') + return false; + + globalThis.__receiveGraphEvents__(%); + return true; +})(); +)script"; + + if (runtime) { + elem::js::Array batch; + + runtime->processQueuedEvents([this, &batch](std::string const& type, elem::js::Value evt) { + batch.push_back(elem::js::Object({ + {"type", type}, + {"event", evt} + })); + }); + + if (auto* editor = static_cast(getActiveEditor())) { + auto expr = juce::String(kDispatchScript).replace("%", elem::js::serialize(elem::js::serialize(batch))).toStdString(); + editor->getWebViewPtr()->evaluateJavascript(expr); + } + } +} + void EffectsPluginProcessor::dispatchError(std::string const& name, std::string const& message) { const auto* kDispatchScript = R"script( diff --git a/native/PluginProcessor.h b/native/PluginProcessor.h index d828257..91cdee2 100644 --- a/native/PluginProcessor.h +++ b/native/PluginProcessor.h @@ -6,6 +6,8 @@ #include #include +#include "LambdaTimer.h" + //============================================================================== class EffectsPluginProcessor @@ -64,6 +66,7 @@ class EffectsPluginProcessor /** Internal helper for propagating processor state changes. */ void dispatchStateChange(); + void dispatchGraphEvents(); void dispatchError(std::string const& name, std::string const& message); private: @@ -78,6 +81,7 @@ class EffectsPluginProcessor juce::AudioBuffer scratchBuffer; std::unique_ptr> runtime; + std::unique_ptr>> eventsTimer; //============================================================================== // A simple "dirty list" abstraction here for propagating realtime parameter diff --git a/src/main.jsx b/src/main.jsx index 51ee5e6..3152a19 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -39,6 +39,10 @@ globalThis.__receiveStateChange__ = function(state) { store.setState(JSON.parse(state)); }; +globalThis.__receiveGraphEvents__ = function(eventBatch) { + console.log(JSON.parse(eventBatch)); +}; + globalThis.__receiveError__ = (err) => { errorStore.setState({ error: err }); };