Design patterns
-
What design pattern(s) is everyone using for HISE? I have small individual scripts for modules that need them but for my main script I have one script per set of controls and then one main script that brings them all together. By set of controls I mean a logical grouping of controls, for example I have a mixer and all of the code for it goes in one script.
I was wondering if there may be a more organised design pattern that would be suited to HISE. With KSP I used MVC but I'm not sure that's best for HISE.
-
I have developed a few best practices over time, most of them should be familiar to you, but it should get the ball rolling:
-
Use
namespaces
as much as possible. For example if your interface script shows multiple pages, make anamespace PageHandling
where you put all variables and inline functions that belong to this functionality there. Then put the whole namespace in an external file with the same name as the namespace (in this casePageHandling.js
). The most convenient way is to write the namespace in theonInit
callback, then select the entire namespace, right-click -> Move Selection to external file.
With this approach, you can browse the functionality pretty easy with the drop down menu of the editor and have a tidy autocomplete popup menu. -
Try to use as much arrays (also multi-dimensional arrays) as possible when dealing with UI logic. Not only you can use one inline function as callback for multiple elements using the
indexOf()
method to get the actual component in the callback, you can also change properties with for loops, etc.
Use the right-click feature of theScriptWatchTable
that opens a read-only popup editor with a JSON representation of the array for debugging. Right click on any row that contains an array or object to show it. -
If you're initialising an array containing UI elements, make a init method like this:
inline function initPageButtons() { local p = []; for(i = 0; i < 16; i++) { p.push(Content.getComponent("PageButton" + i)); } return p; }; const var pageButtons = initPageButtons();
- Use the factory method approach for all custom Panels. Use a namespace for this and add an inline function called
make(name)
that takes a String, creates a reference to the Panel, sets all properties and callbacks and returns it. This way you can write stuff likeconst var vuMeter = VuMeter.make("vuMeter");
-
-
Following for workflow inspiration
-
What is the advantage of using an init method to initialise an array of UI elements over something like this directly in the init callback?
for (i = 0; i < 16; i++) { uiArray.push(Content.getComponent("control"+i)); }
Is it just to keep the code tidy?
-
Yes. For this trivial example you don‘t gain much but if your initialisation involves more variables, making them local to the initialisation function keeps the root scope cleaner.
And you can coallescate similar initialisations by parametrizing the function.
-
For my current project I've settled on the following design (which is kind of a modular view-model controller approach).
I have the "main" file which is a thin controller that brings the other files together and handles the callbacks. It also has a project wide namespace that all of the other files can access.
Then I have a bunch of smaller files each with its own namespace. I have a manifest file which is basically a large JSON object that holds various info for all of the different presets (in my case each preset is a different orchestral instrument with its own set of articulations, keyswitches, key ranges, etc.).
The next file is the theme file which holds all of the styling information for the UI. Having this in one file allows me to apply a different look to the whole UI by editing a single file.
Then I have a paintRoutines file which holds all of the various panel paint routines and SVG data - There are no images on my interface, it's all vectors.
The next file is the preset handler. The functions in here take the current preset name, look up the data for the preset in the Manifest file and applies the correct settings, loads sample maps etc.
Then I have the bulk of the scripts which is spread over six files. I have a header with all of its controls at the top of the UI, and a footer with all of its controls at the bottom of the UI. The next 3 files are for the main parts of the UI, an articulation handler, a mixer, and a CC handler. The last file is for a settings window. Each of these six files sets up its own view (GUI) and assigns callbacks for the controls, this is why I call them view-models. Some of them also have functions that can be called in the controller's onNote and onController callbacks.
Now although this seems like a lot of code the majority of those files have less than 100 lines and there is probably less than 1000 lines in total. I've chosen this approach because having a lot of specific files with a small amount of code in each allows me to very easily track down bugs, maintain my code, and reuse the code in other projects.