Writing Scalable Projects
-
I think it's a good idea to be writing scalable code more often than not. Unless it's a true one-off project, scalable projects can save a lot of time down the road and help making expansion decisions business-wise.
Adhering to HISE's "one script per job" mantra, here's my current setup of scripts and paradigm. Please correct me if I got anything wrong.
Interface Processor
The main interface script.
Configuration
Contains a global object, and I store the parameters inside its properties. Like globals.x, but I declared it as global config = {}, just for clarity when referring to it elsewhere (e.g. config.lowkey).
These properties are then accessed in the on init function of other realtime scripts and the relevant values are pulled into the namespaced reg variables, as accessing reg variables is faster than accesing object properties (by a factor of 10 if I recall correctly).A Bunch of Smaller Scripts for Various Tasks
They pull the values from the config object (which is global), declaring the relevant values in reg variables in init (faster than declaring reg in on note).
If/when I need to use the code for another similar project, i just tweak the Configuration script and the rest adapts automatically.
What other ways are there to approach this in HISE? What approach do you use?
-
@aaronventure That's pretty much what I do except I very very rarely use global variables. I have a config namespace within my interface script.
Almost all my subscript modules are independent. All their properties can be set from my interface script if needed. For example I might have a note range filter, this has two knobs, low note and high note. All notes outside the range will be ignored. I can set these two knobs from my interface script dynamically, for example when changing presets.
So far the only time I did need global variables was for my guitar project where I wanted an array to keep track of notes on different strings and I needed access to that from multiple scripts. I'll be working on a new guitar project soon and I'm hoping to achieve the same thing without globals.
All my reusable modules live in a git submodule inside my scripts folder. Whenever I make a new project I can just pull down this submodule. I used to use the global scripts folder but I ended up making changes for one project that would break another project. So the submodule approach, while it requires a bit more care to manage, doesn't break old projects as each project has the correct version of the code it needs at all times.
-
@d-healey Thanks for your insight, David.
I've been pondering the cross-script communication thing for a bit, so I got to do some testing today. I wasn't fond of the idea of linking processors together manually in the interface or even hardcoding it somewhere as that adds to the maintenance to be done any time I want to re-use the script elsewhere.
I wanted to test how fast are different way of accessing information.
Accessing a reg is fastest, as expected.
reg lastNote = 0; Console.startBenchmark(); for (i=0;i<1000000;i++) lastNote; Console.stopBenchmark(); Benchmark Result: 55.896 ms
Accessing a global object's property is only 20% slower.
global config = {}; config.lowkey = 22; //...in some other MIDI Processor Console.startBenchmark(); for (i=0;i<1000000;i++) config.lowkey; Console.stopBenchmark(); Benchmark Result: 66.685 ms
Accessing the built-in Globals object property is slower than accessing the one you declare on your own.
Globals.y = 5; Console.startBenchmark(); for (i=0;i<1000000;i++) Globals.y; Console.stopBenchmark(); Benchmark Result: 77.564 ms
Accessing a value via .getAttribute is over 250% slower.
const var testproc= Synth.getMidiProcessor("testproc"); Console.startBenchmark(); for (i=0;i<1000000;i++) testproc.getAttribute(testproc.slidertest); Console.stopBenchmark(); Benchmark Result: 188.314 ms
Accessing a control's value (that I would set using .setAttribute or through linking from another script) is nearly 50% slower than accessing a custom defined global object property.
const var localSlider = Content.addKnob("localSlider", 0, 0); Console.startBenchmark(); for (i=0;i<1000000;i++) localSlider.getValue(); Console.stopBenchmark(); Benchmark Result: 90.953 ms
So, why even bother with linking, which requires control setup in target processors? It's faster to just store the temp value into a global object property and just refer to that from anywhere, and it's even faster in execution. It's near reg access speed.
Is something wrong with my benchmarks here? I declared
reg i = 0
before doing the benchmarks. -
@aaronventure Speed isn't really an issue in my case. Almost all the set attribute stuff is done either at init or when changing presets.
I think the only time I'm doing it real time is when changing articulations and then I have dedicated, non-deferred articulation handler for that and I haven't had any issues.
-
@aaronventure this is probably going to scare you a bit - almost everything is in a script, each script uses its own namespace. It is a massive project:
// THE SOUND SOURCES include("VoiceModels.js"); // LAF // include("LAF.js"); include("PatternPreLoader.js"); // THE HEADER CONTROLS include("HeaderControls.js"); // MASTER CONTROLS include("MasterControls.js"); // EDITOR CONTROLS include("EditorControls.js"); // REALISM CONTROLS include("RealismControls.js"); // THE STD CONTROLS // include("StdControls.js"); // SAMPLER AREA include("Samplers.js"); // SYNTH OSCILATOR AREA include("Synths.js"); // ENVELOPES AREA // include("GainEnvelopes.js"); // FILTER AREA // include("Filters.js"); // FILTER ENVELOPES include("FilterEnvelopes.js"); // TREM VIB AREA include("VibTremSpace2.js"); // UNISON AREA include("UnisonSpace.js"); // CONTROL AREA include("ControlSpace2.js"); // FM AREA include("FMSpace.js"); // LOOP PLAYER include("LoopPlayerAndBrowser.js"); // THE FX AREA // include("SendEffects2.js"); include("MasterEffects2.js"); include("update.js"); // the ARP include("Arp.js"); // the CHORD PLAYER include("chordplayer.js"); /// the RANGE SETTERS include("RangeSetters.js"); // SETTINGS // include("settingsspace.js"); // SOUND BROWSER // include("SoundDataLoader.js"); include("SoundBrowser.js"); // SOUND randomisation include("SoundRandomiser.js"); // Authorisation & Demo include("Authorisation.js"); // THE GLOBAL MODULATORS // include("GlobalModulators.js"); //PRESET BROWSER include("PresetBrowser.js"); //LOCKING include("UnitLock.js"); //PATHS include("Paths.js");
-
@Lindon Same sort of thing I do