FR: Global variables scripting API
-
It would be nice to be able to create global variables. The more simple the declaration the better. One idea I had for global variables is something like this, using a specific keyword/namespace/etc.:
var GLOBAL.myGlobal = some_value_here;
Another implementation might be this, having a built-in array in HISE (although I prefer the above syntax):
var myGlobal = 3; GLOBAL[myGlobal] = some_value_here
*To Kontakt devs: HISE knob variables are already global, so you wouldn't need to, for example, use PGS to get a knob value from one script into another as you do in Kontakt.
-
This would be not difficult to implement. I would prefer a global "dictionary" with Identifier access and the API calls
Engine.setGlobal("varName", 1.7); var x = Engine.getGlobal("varName");
-
Would that allow for arrays?:
var a = 10; var b = 20; var c = 30; Engine.setGlobal("varName", [a,b,c]); var x = Engine.getGlobal("varName"); var Ten = x[1]; var Twenty = x[2]; var Thirty = x[3];
or this?
var Ten = Engine.getGlobal("varName")[1]; var Twenty = Engine.getGlobal("varName")[2]; var Thirty = Engine.getGlobal("varName")[3];
-
Yes, the first syntax example would work. Altough I would recommend to name the array for clarity:
var array = []; array[0] = 10; array[1] = 20; array[2] = 30; Engine.setGlobal("varName", array); var x = Engine.getGlobal("varName"); Console.print(x[0]); // 10 Console.print(x[321]); // undefined
BTW the JUCE Javascript Engine has some quirks when defining arrays and this style of definition is the most working one.
Also there will be a difference between handling arrays and primitive values as globals. Long story short: arrays will be referenced and primitive values (integers, doubles and even strings) will be copied:
var x = Engine.getGlobal("varName"); // "varName" is an array x[4] = 7; // operates on the actual array, so the global array will be changed. var x = Engine.getGlobal("varName"); // "varName" is an integer x = 7; // will not change the global variable Engine.setGlobal("varName", 7); // this is still needed
This would be the least intrusive implementation (for different behaviour it will get messy)
And I have not decided whether the access to the global variables will be by integer index or named Identifier. This is a performance question and I will do some benchmarking to check if the latter is too heavy.
-
As far as I know that shouldn't be a problem. I don't know when you'd need to copy a global array. If you need to then, this would work?
var reference = Engine.getGlobal("myglobal"); for (var i = 0; i < array.size(); i++) { var copy[i] = reference[i] }; You could make a specific command API just in case, but not important as it would be easy to make your own function to do it. [code] var copy = Engine.copyGlobal("myglobal") [/code][/i][/i]
-
Doesn't Javascript already have "global" and "local" variable scopes? I guess by global, you mean in the sense that the variables are available outside the single script itself, correct?
I like the "Engine" object approach, since that looks like a pretty natural way to work. I assume the data type is conveyed when setting the global variable, too? For example:
var exampleArray = [1, 1, 2, 3, 5, 7, 13]; Engine.setGlobal("fibonacci", exampleArray);
Would you be able to have global functions as well?
-
@1g2fhobs:
Would you be able to have global functions as well?
Well, anything that can be saved in a var on the script side will be saved as global variable engine-wide, so theoretically functions will work too. But I can imagine it will get messy quite quickly (What if you call a function that references a variable from another script and the script was deleted?).
-
If a function references a variable from a script that was deleted, then surely you just throw an error "variable is undefined"
What would the code look like to create a global function, is it like this?
function myGlobalFunction (a,b) { return a * b; } Engine.setGlobal("myGlobalFunction", myGlobalFunction); myLocalFunction = Engine.getGlobal("myGlobalFunction"); var multiplied = myLocalFunction(3,2);
Seems like includes would be better for this.
-
So, I implemented this and how I expected, the function storage comes with some quirks. Consider this example:
Script 1:
var faultyVariable = 25; var x = function() { Console.print(faultyVariable); }; Engine.setGlobal(0, x);
Script 2:
var x = Engine.getGlobal(0); x();
The console output is
undefined
. This is because the function uses a variable defined in Script 1, but not in Script 2. If I however, addvar faultyVariable = 27;
in the second script (before the function call of course), it will print this value (so the global function looks in the local scope). This can lead to pretty unpredictable behaviour where functions do work in one script but not in the next, so I would suggest removing function storage from the global variable container.
Also, the variables are clones before they are saved into the global container. This is needed because if you create an array, save it as global and then delete the script processor, the array contains dangling pointers to deleted elements and this will result in a crash.
There are a few drawbacks because of this:- interface objects can't be stored (and controlled from another script, but there is the API call Synth.setParameter() for this functionality.
- saving an array is slower because it has to be duplicated - but I strongly recommend not saving an array in one of the time critical callbacks anyway…
-
referring to globals by number is going to be difficult for large projects.
the coder will have to create include variables to refer to global numbers
include:
mynamespace.ColorEnum = { myGlobal : 0, myOtherGlobal : 1, anotherGlobal: 2 }
first script processor:
include("theAbove.js"); Engine.setGlobal(mynamespace.myGlobal, a * b + c / e - f );
another script processor:
include("theAbove.js"); var output = Engine.getGlobal(mynamespace.myGlobal, a * b + c / e - f );
Seems like a lot to do a simple thing. I think a use-case for global variables would be when you want to take a knob value and do a calculation ONCE, and send that calculation to other knobs or values/parameters in other scripts.
-
@3kbojkje:
If a function references a variable from a script that was deleted, then surely you just throw an error "variable is undefined"
This is the default behaviour - a crash only occures when storing an interface widget as global variable and then deleting the script.
However this really leads to unpredictable mess behaviour and would be a cause of many irritations and bugs. Consider this example:var velocity = 0; var calculateStuff = function() { Synth.setParameter(12, velocity); } function onNoteOn() { velocity = Message.getVelocity(); }
You can't use this function in another script without knowing that it needs a variable called 'velocity' and the variable needs to be updated every noteOn. This is a heavy violation of the encapsulation principle that is the main reason for functions.
@3kbojkje:
Seems like includes would be better for this.
Yes indeed. I think limiting global variables to values and arrays only is the least complicated thing to do.
About the index vs named identifier stuff for global access, I will make some performance tests, but the snippet you wrote does not look too complicated to me (especially in combination with the include functionality). But I think you have the syntax wrong. This example works:
// This stuff can be outsourced to an external enum file var globals = {}; globals.BUTTON_VALUE = 4; Engine.setGlobal(globals.BUTTON_VALUE, 55); Engine.getGlobal(globals.BUTTON_VALUE);
-
the include file:
Edit: So this would all be built into the API. In this case, the developer only has to do the globals.BUTTON_VALUE part. –>function global(arguments) { if (arguments.length == 1) Engine.getGlobal(arguments[0]); else Engine.setGlobal(arguments[0],arguments[1]); }; var globals = {}; globals.BUTTON_VALUE = 4;
// get var local_button_value = global(BUTTON_VALUE); // set global(BUTTON_VALUE, some_formula);
Is there any (feasible) way you can make Engine.SetGlobal() and Engine.GetGlobal() something that happens in the background so the developer can just focus on referring to global variables by their variable name? The above code is my attempt to do that.
-
OK I added another way of handling global variables:
Globals.x = 5; Console.print(Globals.x); // 5 in every script
It can't be simpler than that. 'Globals' is a object that is hold by the engine and their properties ( = every variable that is stored in it) are reference counted. You can use this system like normal variables (so everything from functions to objects and script interface widgets can be stored here. However because it uses reference counting, deleting a script processor with widget saved into the global storage does not delete the widget (not until the global property is overwritten or cleared).
But as always: with great power comes great responsibility (it is a little bit like the getter/setter paradigm vs. direct access). I think I keep both styles for now (the overhead is negible) and everybody can use the system he prefers.
-
sweet, perfect!
-
And I added Autocomplete support for this style. Pressing Escape shows "Globals" under the API classes and if you press Escape again, you'll see all global properties with their current value (just like the local properties).
Also the variables are shown in the variable watch window (with the prefix 'Globals.')
Kind of starting to like this too
-