Forum
    • Categories
    • Register
    • Login

    Custom browser - custom preset file format???

    Scheduled Pinned Locked Moved General Questions
    14 Posts 6 Posters 652 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • OrvillainO
      Orvillain
      last edited by

      Right so it looks like there's quite a few ways to potentially address this. But probably the most flexible is using the UserPresetHandler API:
      https://docs.hise.dev/scripting/scripting-api/userpresethandler/index.html

      This function seems extremely relevant:

      UserPresetHandler.setUseCustomUserPresetModel(var loadCallback, var saveCallback, bool usePersistentObject)
      

      It takes two functions - onPresetLoad and onPresetSave.

      So in theory, I write a custom namespace for handling my custom preset format. It needs to be able to this kind of stuff:

      • Gather all loaded samplemap names and any relevant identifiers
      • Gather all parameters and their values
      • Construct a sensible JSON array of all of the above
      • Save it to a file on disk, using the FileSystem API coupled with the UserPresetHandler API.
      • Use the onPresetLoad() method to reconstruct a selected preset in full.
      • It also needs to be able to flag when sample content cannot be found.
      • It needs to be able to also save and restore modulator states
      • Any internal values for midi processors also need to be stored
      • Arpeggiator patterns also
      • Any routing matrix changes
      • Maybe sample map details beyond names - root notes, velocity splits, etc?
      • Add a version number to the file for future use
      • UI states for selected tabs
      • Provide some kind of sample relink method in situations where a samplemap or individual sample cannot be found
      • For saving a preset, I should plan to allow custom meta-data to be added to a preset; things like author, style, type, etc...

      Need to do some heavy thinking about this. Because the clients requirements are definitely quite a bit more expanded in scope than I initially thought.

      Musician - Instrument Designer - Sonic Architect - Creative Product Owner
      Crafting sound at every level. From strings to signal paths, samples to systems.

      HISEnbergH 1 Reply Last reply Reply Quote 1
      • HISEnbergH
        HISEnberg @Orvillain
        last edited by

        @Orvillain It's definitley a big task. Out of curiousity have you considered just "hijacking" HISE's preset system using the UserPresetHandler API? This is what I am testing out currently in a project that demands more from the preset browser than is currently available.

        My idea is to let HISE's preset system to handle all the heavy lifting while I just define custom save/load callbacks that embed extra metadata as necessary. So instead of creating a completely separate file format, I'm using UserPresetHandler.setUseCustomUserPresetModel() to replace HISE's default data structure with my own while keeping the standard .preset file format.

        As an example I am trying to define my own Tagging system and adding that information into the .xmls found in the UserPreset folder.

        It might breakdown in your use case given the client requirements here, but may be worth investigating.

        Sonic Architect && Software Mercenary

        Christoph HartC 1 Reply Last reply Reply Quote 1
        • Christoph HartC
          Christoph Hart @HISEnberg
          last edited by

          That's the right direction, but I would also try to stay within the HISE user preset system so you get the intended loading prodecure (kill voices, load the preset on a background thread, then unsuspend the audio processing).

          With the custom data model you can define the layout of the user preset file as you wish. Note that it will be converted from JSON to XML at some point though (which is a bit weird).

          They also want to be able to export a preset, and include any sample content with the preset - ie; bundle the custom loaded .wav file, or the samplemap, with the preset. For personal sharing purposes.

          I would detach this from the user preset system. A preset with sample mapping data and samples should be

          1. The preset file
          2. The JSON (or SFZ or whatever) mapping file
          3. The audio samples

          all in one folder.

          1 Reply Last reply Reply Quote 4
          • OrvillainO
            Orvillain
            last edited by

            @Christoph-Hart I'm looking at this again. Before I begin, is there a decent snippet anywhere that demonstrates how to do this for a simple plugin?

            Also, what are the gotcha's to watch out for? You mentioned killing voices, loading on the background thread, etc...

            Musician - Instrument Designer - Sonic Architect - Creative Product Owner
            Crafting sound at every level. From strings to signal paths, samples to systems.

            Christoph HartC 1 Reply Last reply Reply Quote 0
            • Christoph HartC
              Christoph Hart @Orvillain
              last edited by Christoph Hart

              @Orvillain nope, I'm using it in Triaz, but I haven't made a public example yet, but it's a good idea to do so. The problem is that it cannot be encompassed into a nice simple snippet as it requires additional data (preset files) to be useful.

              Just from thinking about it there are a few things to watch out for:

              • initialisation order. Usually you can decide what you restore first by rearringing your script function, but be aware that restoring module states for whole HISE modules will always come after this method.
              • the function is being executed synchronously in the loading thread (but it should take a lock to prevent simultaneous execution of scripting tasks in the scripting thread). You can safely assume that there are no voices playing during this function call (because it kills all voices when loading a preset).
              • The XML -> JSON conversion is a bit weird because the data structures are not 100% convertible, so there are a few edge cases to consider here. You'll notice when you run into them :)
              OrvillainO 1 Reply Last reply Reply Quote 2
              • OrvillainO
                Orvillain @Christoph Hart
                last edited by

                @Christoph-Hart

                Hey Christoph - I've not really got my head around this yet, and my client is requesting multiple types of preset. They want effect chain presets as well as full state presets. They also want to be able to add meta-data and copy samples along with the preset.

                Does that mean I'm going to need custom data models for each type ??

                I could really do with a simple tutorial that shows some of this stuff 😁

                Musician - Instrument Designer - Sonic Architect - Creative Product Owner
                Crafting sound at every level. From strings to signal paths, samples to systems.

                LindonL 1 Reply Last reply Reply Quote 0
                • OrvillainO
                  Orvillain
                  last edited by

                  Here's what I've figured out so far:

                  namespace PluginUserPresetHandling {
                  
                      const UserPresetHandler = Engine.createUserPresetHandler();
                  
                      
                      inline function onPresetLoad(obj) {
                          Console.print("we just loaded a preset!");
                          if (obj.presetData.ui)
                              UserPresetHandler.updateSaveInPresetComponents(obj.presetData.ui);
                      }
                      
                      inline function onPresetSave() {
                          Console.print("we just saved a preset!");
                          return {
                              "version": "1.0.0",
                              "presetName": "This is a magic string for the preset name",
                              "presetAuthor": "Drew",
                              "presetDescription": "This is a saucy preset",
                              "presetTags": ["Fat, Warm, Round"],
                              "presetData": {
                                  "ui": UserPresetHandler.createObjectForSaveInPresetComponents()
                              }
                          };
                      }
                  
                      inline function init() {
                          UserPresetHandler.setUseCustomUserPresetModel(onPresetLoad, onPresetSave, false);
                      }
                  }
                  

                  And this will actually produce a file that looks like this:
                  ecc5224f-b00a-47c4-8a66-36b607908657-image.png

                  So that's quite cool.... but something about my preset did sound different, versus how it sounded when I saved it. So I guess there's more data across the modules that I need to grab too.

                  Musician - Instrument Designer - Sonic Architect - Creative Product Owner
                  Crafting sound at every level. From strings to signal paths, samples to systems.

                  DanHD 1 Reply Last reply Reply Quote 1
                  • LindonL
                    Lindon @Orvillain
                    last edited by

                    @Orvillain said in Custom browser - custom preset file format???:

                    @Christoph-Hart

                    Hey Christoph - I've not really got my head around this yet, and my client is requesting multiple types of preset. They want effect chain presets as well as full state presets. They also want to be able to add meta-data and copy samples along with the preset.

                    FX Chain presets should be pretty simple to encode in a json data model - what meta-data and samples?

                    Does that mean I'm going to need custom data models for each type ??

                    yes

                    HISE Development for hire.
                    www.channelrobot.com

                    1 Reply Last reply Reply Quote 0
                    • DanHD
                      DanH @Orvillain
                      last edited by

                      @Orvillain there’s a mini preset system posted on here a while back by the lunacy guys / Casey Kolb. I’m about to take off otherwise would post.

                      DHPlugins / DC Breaks | Artist / Producer / DJ / Developer
                      https://dhplugins.com/ | https://dcbreaks.com/
                      London, UK

                      1 Reply Last reply Reply Quote 0
                      • OrvillainO
                        Orvillain
                        last edited by Orvillain

                        Okay... starting to get a feel for this system now....

                        One thing I ran into today was trying to decide whether my menus should have their saveInPreset bool enabled or not. My menus have a callback associated with them that controls the loading of an effect and the binding of parameter knobs.

                        But because I am eventually going to be aiming at an effect chain preset format.... I figured saveInPreset should be disabled, and I should track the effect data manually.

                        I got there in there, but it did require a bit of thought. Because loading the effect networks seems to be asynchronous, so you cannot rely on the parameter knobs to be properly flushed at the right time when calling updateSaveInPresetComponents().

                        So after a lot of trial and error, I just decided to call it twice!!

                            inline function onPresetLoad(obj) {
                                local data = obj.presetData;
                                if (!isDefined(data)) return;
                        
                                if (isDefined(data.ui)) {
                                    UserPresetHandler.updateSaveInPresetComponents(data.ui);
                                }
                        
                                local engIds = ["Engine1", "Engine2", "Engine3"];
                                for (i = 0; i < engIds.length; i++) {
                                    local engineIdx = i + 1;
                                    local smKey = "engine" + engineIdx + "SampleMap";
                                    local wtKey = "engine" + engineIdx + "Wavetable";
                        
                                    if (isDefined(data[smKey]))
                                        UISoundSelector.syncSamplerMenu(engineIdx, data[smKey]);
                        
                                    if (isDefined(data[wtKey]))
                                        UISoundSelector.syncSynthMenu(engineIdx, data[wtKey]);
                                }
                        
                                if (isDefined(data.fxSelections)) {
                                    for (k in data.fxSelections) {
                                        local fxName = data.fxSelections[k];
                                        UIEffectDropDownMenu.syncEffectMenu(k, fxName);
                                    }
                                }
                        
                                if (isDefined(data.ui)) {
                                    UserPresetHandler.updateSaveInPresetComponents(data.ui);
                                }
                            }
                            
                            inline function onPresetSave() {
                                return {
                                    "version": "1.0.0",
                                    "presetAuthor": "",
                                    "presetDescription": "User Preset",
                                    "presetTags": [],
                                    "presetData": {
                                        "ui": UserPresetHandler.createObjectForSaveInPresetComponents(),
                                        "engine1SampleMap": PluginSharedData.engineSounds["Engine1"].sampler,
                                        "engine2SampleMap": PluginSharedData.engineSounds["Engine2"].sampler,
                                        "engine3SampleMap": PluginSharedData.engineSounds["Engine3"].sampler,
                                        "engine1Wavetable": PluginSharedData.engineSounds["Engine1"].synth,
                                        "engine2Wavetable": PluginSharedData.engineSounds["Engine2"].synth,
                                        "engine3Wavetable": PluginSharedData.engineSounds["Engine3"].synth,
                                        "fxSelections": captureFXSelections()
                                    }
                                };
                            }
                        

                        This works well, if a little wasteful perhaps.

                        Part of the reason I wanted to do this is because the menu values are obviously floats, not the text value of the menu. And I knew that in the future if I wanted to add an effect type, and change the order of effects in the menu list, that this would break backwards compatibility. So I basically have a shim that will take a string and convert it to the right integer for the menu as it is in that moment, and then set the menu to the appropriate value .... which then triggers the callback to load the right effect network...

                        and then that last updateSaveInPresetComponents() call makes sure the parameters match what the preset has stored.

                        Musician - Instrument Designer - Sonic Architect - Creative Product Owner
                        Crafting sound at every level. From strings to signal paths, samples to systems.

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post

                        11

                        Online

                        2.2k

                        Users

                        13.3k

                        Topics

                        116.1k

                        Posts