Any easy way to communicate from/to C++ and Javascript
-
I have a passable knowledge with JUCE having shipped one commercial product. I'm working with the ExternalFloatingTileTest tutorial from the hise_tutorial on github as my starting point as I need to have access to sysex send and receive. Thats all well and done, but now I need to pass information from the UI JS code to my C++ and vice versa.
Does anybody have any hints. Does the UI code use ValueTree node change listeners (which I used as my main source of component message passing in JUCE) or are there some other clever Hise compoents I could inherit from where I could access ScriptComponents to set control callbacks when they change, or at least read/write ScriptComponent states.
( ps. I asked this on another thread I necroed the other day, but that has not gotten any replys)
-
@jonhallur This depends a bit on the context - there are multiple ways to communicate with scripted controls.
ValueTrees are used inside scriptnode as data model, but for the main HISE project architecture it's just used for saving and restoring.
Although theoretically possible I would refrain from trying to attach any listeners to the script components themselves as there might be lifetime issues that might lead to subtle bugs. So I'd rather recommend a more black-box approach which gets the internals out of the way.
If you just need to communicate float numbers, you can use the
hise::Processor::setAttribute()
function with your Interface script processor - they will be forwarded to the script component callbacks, but you need to know the index of your controls.If you want to have a global data storage that you need to access from both the script and your C++ code, you might be interested in
hise::MainController::getGlobalVariableObject()
, which returns ajuce::DynamicObject
that you can populate / query in C++ and access through theglobal
keyword in HiseScript, but then you don't get any callbacks so you need to resort to polling. -
@christoph-hart Excellent, and thank you for taking the time to humor me.
How do I get
hise::Processor
The only thing that had a
setAttribute
I could find wasgetMainController()->getMainSynthChain()
but that only got me the synth module which I'm not trying to communicate with. Is there any object that would allow me to setAttribute on the ScriptButton, or some other UI component. Even just invoking the callback of the FloatingTile would get me to where I want to be.But the
Globals
object is pretty great. I can just create a lambda in C++_global->setMethod({ "sysEx" }, [](const var::NativeFunctionArgs& args) -> var { DBG("Callback from UI code"); if (args.arguments->isArray()) { DBG("Got an array back"); } return var(0); });
and invoke it from the JS code
inline function onButton1Control(component, value) { if(value) if(Globals.sysEx) Globals.sysEx([5,4,3,2,1]); };
Which is almost everything I want to do.
I could do the rest by having the UI timer pull the
Globals
object for changes, but I think that is the last solution I'd want to do.
If I try to make a function on theGlobals
object on the JavaScripts side, it is not recognized as a method on the C++ side.
I've tried functions, inline functions and anonymous functions, and C++ returnstrue
onhasProperty()
but alwaysfalse
onhasMethod()
andinvokeMethod()
just fails quietly as there is a quiet little test that will just return an empty object whenvar
decides that this is an object, but not a function.If you have some further insight I'd love to hear it as I am very close to where I would like to be, if this is just one of the limitations of the interop then I'll implement the timer polling of the
Globals
object.Thanks again for the help.
-
Cool, didn't knew you could do this across C++/HiseScript boundaries (but watch out for data races, the scripting code is executed on its own thread which might interfere with your SysEx handling).
If you have access to the MainController object, you can get the Interface processor pretty easy:
// Fetch the first scriptprocessor that is called Interface (probably the one you need). raw::Reference<JavascriptMidiProcessor> ref(getMainController(), "Interface", false); // Don't make it crash if there isn't an interface if(auto p = ref.getProcessor()) p->setAttribute(0, 0.6f, sendNotification);
Ideally you'll cache the reference to the processor as the lookup is rather slow. Oh and
BTW, the
raw
namespace contains a lot of functions that tuck away the internals of the full HISE codebase so I recommend trying to use it as much as possible. The docs are here:https://docs.hise.audio/cpp_api/raw/index.html
(They weren't included in the last documentation builds).
-
@christoph-hart Super, that solves everything I need.
-
@christoph-hart I spoke a little too soon.
1>d:\sources\hise\juce\modules\juce_core\memory\juce_weakreference.h(207): error C2385: ambiguous access of 'masterReference' 1>d:\sources\hise\juce\modules\juce_core\memory\juce_weakreference.h(207): note: could be the 'masterReference' in base 'hise::ScriptBaseMidiProcessor' 1>d:\sources\hise\juce\modules\juce_core\memory\juce_weakreference.h(207): note: or could be the 'masterReference' in base 'hise::JavascriptProcessor' 1>d:\sources\hise\juce\modules\juce_core\memory\juce_weakreference.h(205): note: while compiling class template member function 'juce::ReferenceCountedObjectPtr<juce::WeakReference<ProcessorType,juce::ReferenceCountedObject>::SharedPointer> juce::WeakReference<ProcessorType,juce::ReferenceCountedObject>::getRef(ObjectType *)' ... 1>d:\sources\hise\juce\modules\juce_core\memory\juce_weakreference.h(207): error C2248: 'hise::ScriptBaseMidiProcessor::masterReference': cannot access protected member declared in class 'hise::ScriptBaseMidiProcessor'
-
@jonhallur Should be fixed now.
-
@christoph-hart Can this also be used to send an LFO as CC ?
-
@christoph-hart said in Any easy way to communicate from/to C++ and Javascript:
@jonhallur Should be fixed now.
Confirmed fixed in lasted pull from develop
-
@lalalandsynth yes if you want midi out I‘d recommend writing a custom c++ floating tile.
-
Just for future documentation and forum searches, this here code works in the other direction, that is calling into the Javascript from C++ from an inline function handed to C++, I image it would work for inline functions added to the Globals object as well.
_global->setMethod({ "set_callback" }, [&](const var::NativeFunctionArgs& args) -> var { auto *obj = args.arguments->getDynamicObject(); if (obj) { if (auto p = _ref.getProcessor()) { juce::Result* res; var fVar(obj); var args[2] = { 42, 13 }; LockHelpers::SafeLock sl(getMainController(), LockHelpers::ScriptLock); p->getScriptEngine()->maximumExecutionTime = RelativeTime(3.0); p->getScriptEngine()->executeInlineFunction(fVar, args, res); } } return { 0 }; });
Obviously this is not safe, and if you don't use an inline function, it will crash your C++ code.
This is rougly the code from
void ProcessorWithScriptingContent::customControlCallbackIdle(ScriptingApi::Content::ScriptComponent *component, const var& controllerValue, Result& r)