Merging scripts
-
Got any tips on merging several UI scripts into one without variable names clashing? I'm thinking namespaces might play a role and includes but haven't yet worked out the best way to combine them when some variable names will be the same and they are all making use of the same basic callbacks.
-
Yeah, namespaces are the obvious solution if simple variable renaming doesn't do the job (eg. add a short prefix with an underscore, something like
main_volumeSlider
andadv_volumeSlider
or whatever).If you want to merge the code for the callbacks, the best way would be to extract the callback codes into
inline functions
and call these in the callback:namespace FirstScript { inline function onNoteOnCallback() {}; }; namespace SecondScript { inline function onNoteOnCallback() {}; }; function onNoteOn() { FirstScript.onNoteOnCallback(); SecondScript.onNoteOnCallback(); }
Be aware that the standard
var
type does not support namespaces, so if you don't useconst vars
orreg
s, you'll definitely need to rename your variables.BTW, you can select a variable and press Ctrl+D to select the next occurences just like in Sublime Text 3. I added this feature for quick variable renaming:
And you can edit the whole script in a popup editor by right clicking on the script processor header and choose (Open Script in Popup Window).
-
Thanks Christoph! Yes I'm doing all my main editing in Sublime but I find the built in editor really useful for debugging and the CTRL+D has been very handy.
I was thinking I could make a "tab" class and for each tab create an object that contains all the script code in inline functions - haven't quite worked out exactly how I'll do this yet - and then call the functions in each callback as you suggested. I'm assuming though that by placing all the code for each tab in an object it will create a fair amount of inefficiency since it has to resolve the object first, is my thinking on this correct?
-
The resolving is not the problem (it's a little bit slower, but shouldn't be the bottleneck here), but you can't use inline functions as member objects. And you really shouldn't use standard functions inside the audio thread.
I'll see if I can at least use inline functions as variables. Then you can do something like this:
inline function f1() { Console.print(1); }; inline function f2() { Console.print(2); }; const var functionList = [f1, f2]; functionList[0](); // 1
-
What about some kind of pre-processor addition that resolves the content of a "pseudo" variable that holds the inline function?
-
Can you give an example how this could be used to solve the problem?
This would definitely be the most easiest solution and I wanted to add more preprocessor stuff anyway...
-
I was inspired by the way the macro system works with the KSP extended language, basically doing a text substitution at compile time. Since my post I've been playing around with some ideas and actually I don't think such a setup would solve this issue.
Am I right in thinking that when an inline function is called HISE just replaces the call with the contents of the function and handles all the parameters using external variables? If so would it be possible to have HISE recognize when an inline function is stored in a variable and treat the use of that variable as if it was just a call to the inline function?
-
It's a little more complicated than just a replacement of the function body (because this wouldn't allow return values). Basically its a stripped down version of the standard function call with preallocated parameter slots (currently 5).
I'll have to dig around to check how to store inline functions in variables, but it is pretty easy to break stuff there so I have to be careful...
-
Cool, I'll play around some more with merging scripts
-
Alright, you can now do this:
inline function get1() { return 1; }; inline function get2() { return 2; }; inline function get3() { return 3; }; const var obj = { "f1": get1, "f2": get2, "f3": get3 }; const var array = [get1, get2, get3]; // Access via object property for(f in obj) obj[f](); // Access via array for(f in array) f();
You still need to define inline functions before you store them into a variable, but now it should work with iterators, arrays etc. Also it is remarkably slower (ca. 30%) than the "native" inline function call (however still faster and more predictable than the standard function call), so I'd advice to just use it when you absolutely need it.
-
Great, this will definitely come in handy!
-
I found a little issue while merging my scripts, if two GUI controls on different tabs have the same ID they are not namespaced.
As I understand it we only need to know the ID of a control when using the JSON notation to set attributes, the rest of the time it doesn't matter what the ID is because we can always use
.get("id")
on the variable to retrieve it if it's needed. Since I'm creating all of my controls and setting their attributes using helper functions I have no need to know their IDs, so I was thinking is there a way we could call a function to give controls a unique control ID when they are created. Something like this:const var myButton = Content.addButton(Content.getUniqueControlId(), 0, 0);
This would guarantee that every control had a unique ID and would prevent collisions when trying to merge multiple scripts.
-
The IDs are also used to store data and resolve the widgets on countless situations so it's pretty mandatory to have them unique.
You could write your random ID generating function directly in Javascript:
inline function createUniqueString() { local s = String.fromCharCode(Math.randInt(65, 90)); s += String.fromCharCode(Math.randInt(97, 122)); s += String.fromCharCode(Math.randInt(97, 122)); s += String.fromCharCode(Math.randInt(97, 122)); s += String.fromCharCode(Math.randInt(97, 122)); s += String.fromCharCode(Math.randInt(97, 122)); s += String.fromCharCode(Math.randInt(97, 122)); s += String.fromCharCode(Math.randInt(97, 122)); return s; }
Although it assigns different IDs to the widget each time the script is compiled, and this prevents the widget values to be persistent (the IDs are also used to store the values in the preset).
What you need to do is to generate a deterministic string in your UI factory class. The most simple example would be a internal counter in your UIFactory that appends the number to the ID:
namespace UIFactory { reg _counter = 1; inline function createKnob(x, y) { local knob = Content.addKnob("Knob" + _counter++, x, y); return knob; }; }; const var k1 = UIFactory.createKnob(0, 0); const var k2 = UIFactory.createKnob(130, 0);
-
Oh now why didn't I think of that! That's the perfect solution, thanks again!