We're covering a lot of this in this thread:
https://forum.hise.audio/topic/13701/custom-browser-custom-preset-file-format/29
Basically, you've got this:
namespace PluginUserPresetHandling {
const UserPresetHandler = Engine.createUserPresetHandler();
inline function onPresetSave() // this is your main preset save method
{
Console.print("onPresetSave triggered");
}
inline function onPresetLoad(obj) // this is your main preset load method
{
Console.print("onPresetLoad triggered");
}
inline function preLoadCallback() // things you want to happen before loading a preset happen here - looking for samples, looking for graphical assets, etc.
{
Console.print("preLoadCallback triggered");
}
inline function postLoadCallback() // things you want to happen after loading a preset happen here - updating preset name labels in your UI, triggering other UI updates, etc.
{
Console.print("postLoadCallback triggered");
}
inline function postSaveCallback() // things you want to happen after saving a preset happen here - copying samples to an external location, removing any dirty flags you might've setup in the UI layer, etc.
{
Console.print("postSaveCallback triggered");
}
inline function init() {
UserPresetHandler.setUseCustomUserPresetModel(onPresetLoad, onPresetSave, false); // this line is essential
UserPresetHandler.setPreCallback(preLoadCallback);
UserPresetHandler.setPostCallback(postLoadCallback);
UserPresetHandler.setPostSaveCallback(postSaveCallback);
}
}
You can achieve a hell of a lot with this, without discarding the HISE preset system.
For example, here is my onPresetSave method in my current project:
inline function onPresetSave() {
Console.print("onPresetSave triggered");
PluginSharedHelpers.forceAllOuterSlotsEnabled();
if (PluginSharedData.presetMode == "Global") {
return saveGlobalPreset();
}
if (PluginSharedData.presetMode == "FXChain") {
return saveFXChain();
}
}
In this way, I'm able to gate different save functions based on a master type, which means I can either write the HISE .preset file, or I can write a custom file using my own data model.
My load one is this:
inline function onPresetLoad(obj) {
Console.print("onPresetLoad triggered");
PluginSharedData.isRestoringPreset = true;
if (PluginSharedData.presetMode == "Global") {
loadGlobalPreset(obj);
}
if (PluginSharedData.presetMode == "FXChain") {
loadFXChain(obj);
}
PluginSharedData.isRestoringPreset = false;
}
The loadFXchain method is this:
inline function loadFXChain(obj) {
if (!isDefined(obj)) return;
Console.print("we are now attempting to load an fx chain");
PluginSharedHelpers.forceAllOuterSlotsEnabled();
local fxSelections = obj.fxSelections;
local fxChainOrder = obj.fxChainOrder;
local params = obj.parameters;
// Set effect menus by stable id (this loads the networks)
if (isDefined(fxSelections)) {
for (i = 0; i < fxSelections.length; i++) {
local sel = fxSelections[i];
if (!isDefined(sel) || !isDefined(sel.id)) continue;
local idName = (isDefined(sel.idName) && sel.idName != "") ? sel.idName : "empty";
UIEffectDropDownMenu.setMenuToId(sel.id, idName, true); // fire callback
}
}
// Restore FX parameters for the current fxchainScope, skipping selectors (already set)
if (isDefined(params)) {
for (i = 0; i < params.length; i++) {
local p = params[i];
if (!isDefined(p) || !isDefined(p.id)) continue;
if (!_isFxParam(p.id)) continue;
if (p.id.contains("EffectSelector")) continue; // handled above
local c = Content.getComponent(p.id);
if (!isDefined(c)) continue;
c.setValue(p.value);
c.changed();
}
}
And you can hopefully see, that what that is doing is skipping userPresetLoad altogether - and instead is manually iterating around whatever data exists in the file, finding the right UI component, and setting the value. This allows me to circumvent some of the HISE assumptions; namely that if a UI component is not specified when loading a user preset, it gets reset to the default value. Which is super no bueno.
Bear in mind some of this stuff can be asynchronous. So I don't think you can always rely on the order of things.