Global Cable + Third Party Node
-
Got a bit of a weird one. I'm trying to import a pitch-detection algorithm that works in JUCE, but I can't get it to send the data to a global cable.
process()
andsetParameter()
update it just fineprepare()
andtimerCallback()
(JUCE override), only change the cable value when I add the node (scriptnode) or load the binary (hardcoded), then never again.// ==================================| Third Party Node Template |================================== #pragma once #include <JuceHeader.h> namespace project { using namespace juce; using namespace hise; using namespace scriptnode; using cable_manager_t = routing::global_cable_cpp_manager<SN_GLOBAL_CABLE(106677056)>; enum class GlobalCables { pitch = 0 }; // ==========================| The node class with all required callbacks |========================== template <int NV> struct tuner: public data::base, public cable_manager_t, private juce::Timer { // Metadata Definitions ------------------------------------------------------------------------ SNEX_NODE(tuner); tuner() { startTimerHz(30); // nope } ~tuner() { stopTimer(); // nope } struct MetadataClass { SN_NODE_ID("tuner"); }; static constexpr bool isModNode() { return false; }; static constexpr bool isPolyphonic() { return NV > 1; }; static constexpr bool hasTail() { return false; }; static constexpr bool isSuspendedOnSilence() { return false; }; static constexpr int getFixChannelAmount() { return 2; }; static constexpr int NumTables = 0; static constexpr int NumSliderPacks = 0; static constexpr int NumAudioFiles = 0; static constexpr int NumFilters = 0; static constexpr int NumDisplayBuffers = 0; int handleModulation(double& value) { return 0; } template <typename T> void processFrame(T& data) {} void handleHiseEvent(HiseEvent& e) {} void setExternalData(const ExternalData& data, int index) {} // Scriptnode Callbacks ------------------------------------------------------------------------ void prepare(PrepareSpecs specs) { randomGenerator.setSeedRandomly(); float randomValue = randomGenerator.nextFloat(); setGlobalCableValue<GlobalCables::pitch>(randomValue); // doesn't work here startTimerHz(30); // either doesn't start the timer, or it does, but the timer callback doesn't update the cable } void reset() { stopTimer(); } template <typename T> void process(T& data) { randomGenerator.setSeedRandomly(); float randomValue = randomGenerator.nextFloat(); setGlobalCableValue<GlobalCables::pitch>(randomValue); // works (too many calls per second) } juce::Random randomGenerator; void timerCallback() override { randomGenerator.setSeedRandomly(); float randomValue = randomGenerator.nextFloat(); setGlobalCableValue<GlobalCables::pitch>(randomValue); // nope } // Parameter Functions ------------------------------------------------------------------------- template <int P> void setParameter(double v) { if (P == 0) { if (v >= 0.5) { monitorOutput.store(true); } else { monitorOutput.store(false); } } } void createParameters(ParameterDataList& data) { { parameter::data monitor("Monitor", { 0.0, 1.0 }); registerCallback<0>(monitor); monitor.setDefaultValue(0.0); data.add(std::move(monitor)); } } private: std::atomic<bool> monitorOutput{ false }; }; }
-
@iamlamprey ah yes actually that is a non-trivial issue: the
juce::Timer
(orjuce::AsyncUpdater
) class require the message thread to run and be available and that is not the case in the DLL build, because the code in there runs in a different process and cannot access the UI thread of the main application.I didn't notice that because I was always running a separate thread for communication, but I guess we need a better solution.
So I've added a small helper class that runs a thread inside the DLL process and basically replicates the functionality of the
juce::Timer
andjuce::AsyncUpdater
class. All you need to do is to subclass from these classes instead of the JUCE ones and it will take care of everything:template <int NV> my_node: public juce::Timer // nope { }; template <int NV> my_node: public hise::DllTimer // this will work { my_node() { startTimer(30); } void timerCallback() override { ... } }; template <int NV> my_node: public juce::AsyncUpdater // nope { }; template <int NV> my_node: public hise::DllAsyncUpdater // this will work { my_node() { } void process(ProcessData<2>& data) { triggerAsyncUpdate(); } void handleAsyncUpdate() override {} };
Once you compile the plugin, it will automatically resort to using the default JUCE classes with the proper UI thread model.
-
@Christoph-Hart Nice, thanks for the quick fix!