Multiple instruments, single project or multiple projects?



  • I'm currently working on a collection of woodwind instruments, they will all use the same instrument setup and scripts with a few differences like key range, articulations, key switches etc. Is there anything wrong with having just one project for all of the instruments (with a different preset for each instrument) or would it be better to create an individual project for each instrument? How will having all the instruments in one project affect compiling to a VST/AU? and will the user presets for one instrument show up in the preset browser of another?


  • administrators

    Actually, I would try to make this collection a single VST and swap the samples via sample maps. This prevents cluttering the users VST folder with a single plugin for each instrument and from the development perspective heavily reduces the redundancy: you only have one patch which contains all scripts and modulators and swap out the samples and change the key range etc. according to the loaded sample map.

    In this case, user presets will also store the sample map so you can build user presets which then load different sample sets.

    I think this system is one of the biggest advantages of HISE (I am using this exact method for the library that will come out shortly).



  • This is a good idea. So would I need to add controls to the interface for the user to switch between the instruments (sample maps) or is this something I'd be able to make accessible from the preset browser?


  • administrators

    Both. If you store the select control of the sample maps in presets (using set("saveInPreset", true), it will recall the samples.

    If you only want to select the instruments from the preset browser, just hide the select control - but I can't think of any reason why this would be useful 🙂

    I'll setup a example project for you.


  • administrators

    Alright:

    0_1488138289222_SwitchTest.zip

    This is a example project. You have two samples (a sine and a triangle), two sample maps (with each sample) and two user presets that recall the combobox.

    Let me know if something is unclear.



  • Oh this looks awesome, thanks! I'll have to play with it a bit 🙂



  • If I understand correctly the user presets only saves the state of the front end script, all other values need to be set by the script based on the state of one or more saved widgets?



  • Yep, all plugins works this way...



  • Is there a reason why the same presets we use as developers couldn't also be "factory" user presets when the instrument is exported to a plugin?


  • administrators

    Ah, you mean the actual .hip file? This would mean a heavy rewrite of the architecture of HISE, because the .hip file serves as root data structure for the plugin (and, again, is poorly named because it has nothing to do with actual "presets"). Making this hot swappable is pretty complex, because it might involve window rescaling (if the interface size changes), wiping all samples and globals, recompiling all scripts, etc.

    Also changing the .hip file can't be called from a script (because it would be destroying itself with this action). I could add a drop down in the global toolbar that allows selecting different .hip files, but I can't say when I find the time to implement this.

    I recommend sticking to the current system - hot swapping the sample maps and adjusting the rest (background image, modulator settings etc...) via script callbacks. This really reduces the redundancy if done right.



  • Yep that makes sense, the preset name threw me off. Is there a way to make a hidden interface in a separate script (one that won't show on the front end) that will be saved with the user preset where I can do all my sample map swapping and other setup?


  • administrators

    You can just hide the control that swaps the sample map. The properties saveInPreset and visible are independent...



  • My problem is the preset is going to be more complex than just hiding a single control because I'll need knobs to set up lots of other things like key range, round robin, legato, as well as changing the sample map. All the things that will vary between different presets.

    Having all this in the same script as the user's GUI makes it a bit complex to handle. Would it be possible to add another script with Synth.addToFront() or Content.makeFrontInterface() but have the tab switching button hidden so that the user can never access it? Or add a function, to do the same thing, something like Content.makePresetInterface()?



  • Ive just noticed also that the preset system only seems to remember the first script with makeFrontInterface() ignoring the controls on other front interface scripts.

    <?xml version="1.0" encoding="UTF-8"?>
    
    <Processor Type="SynthChain" ID="Master Chain" Bypassed="0" Gain="1" Balance="0"
               VoiceLimit="128" KillFadeTime="20" IconColour="0" packageName=""
               views="32.rk1bzA.....C.........LDZg4lakwVL..........." currentView="-1">
      <EditorStates BodyShown="0" Visible="1" Solo="0" Folded="0" InterfaceShown="0"/>
      <ChildProcessors>
        <Processor Type="MidiProcessorChain" ID="Midi Processor" Bypassed="0">
          <EditorStates BodyShown="1" Visible="1" Solo="0" Folded="0"/>
          <ChildProcessors>
            <Processor Type="ScriptProcessor" ID="Script Processor" Bypassed="0" Script="Content.makeFrontInterface(632, 250);&#13;&#10;&#13;&#10;Content.addKnob(&quot;A Knob&quot;, 0, 0);function onNoteOn()&#10;{&#10;&#9;&#10;}&#10;function onNoteOff()&#10;{&#10;&#9;&#10;}&#10;function onController()&#10;{&#10;&#9;&#10;}&#10;function onTimer()&#10;{&#10;&#9;&#10;}&#10;function onControl(number, value)&#10;{&#10;&#9;&#10;}&#10;">
              <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="0"
                            onNoteOnOpen="0" onNoteOffOpen="0" onControllerOpen="0" onTimerOpen="0"
                            onControlOpen="0"/>
              <ChildProcessors/>
              <Content>
                <Control type="ScriptSlider" id="A Knob" value="0.47"/>
              </Content>
            </Processor>
            <Processor Type="ScriptProcessor" ID="Script Processor22" Bypassed="0" Script="Content.makeFrontInterface(632, 250);&#13;&#10;&#13;&#10;Content.addKnob(&quot;AnotherKnob&quot;, 0, 0);function onNoteOn()&#10;{&#10;&#9;&#10;}&#10;function onNoteOff()&#10;{&#10;&#9;&#10;}&#10;function onController()&#10;{&#10;&#9;&#10;}&#10;function onTimer()&#10;{&#10;&#9;&#10;}&#10;function onControl(number, value)&#10;{&#10;&#9;&#10;}&#10;">
              <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="0"
                            onNoteOnOpen="0" onNoteOffOpen="0" onControllerOpen="0" onTimerOpen="0"
                            onControlOpen="0"/>
              <ChildProcessors/>
              <Content>
                <Control type="ScriptSlider" id="AnotherKnob" value="0.88999999"/>
              </Content>
            </Processor>
          </ChildProcessors>
        </Processor>
        <Processor Type="ModulatorChain" ID="GainModulation" Bypassed="0" Intensity="1">
          <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/>
          <ChildProcessors/>
        </Processor>
        <Processor Type="ModulatorChain" ID="PitchModulation" Bypassed="1" Intensity="0">
          <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/>
          <ChildProcessors/>
        </Processor>
        <Processor Type="EffectChain" ID="FX" Bypassed="0">
          <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/>
          <ChildProcessors/>
        </Processor>
        <Processor Type="SineSynth" ID="Sine Wave Generator" Bypassed="0" Gain="0.25"
                   Balance="0" VoiceLimit="128" KillFadeTime="20" IconColour="0"
                   OctaveTranspose="0" SemiTones="0" UseFreqRatio="0" CoarseFreqRatio="1"
                   FineFreqRatio="0" SaturationAmount="0">
          <EditorStates BodyShown="1" Visible="1" Solo="0" GainModulationShown="1" Folded="1"/>
          <ChildProcessors>
            <Processor Type="MidiProcessorChain" ID="Midi Processor" Bypassed="0">
              <EditorStates BodyShown="1" Visible="1" Solo="0" Folded="1"/>
              <ChildProcessors/>
            </Processor>
            <Processor Type="ModulatorChain" ID="GainModulation" Bypassed="0" Intensity="1">
              <EditorStates BodyShown="1" Visible="0" Solo="0"/>
              <ChildProcessors>
                <Processor Type="SimpleEnvelope" ID="DefaultEnvelope" Bypassed="0" Intensity="1"
                           Attack="5" Release="10" LinearMode="1">
                  <EditorStates BodyShown="1" Visible="1" Solo="0"/>
                  <ChildProcessors>
                    <Processor Type="ModulatorChain" ID="Attack Time Modulation" Bypassed="0"
                               Intensity="1">
                      <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/>
                      <ChildProcessors/>
                    </Processor>
                  </ChildProcessors>
                </Processor>
              </ChildProcessors>
            </Processor>
            <Processor Type="ModulatorChain" ID="PitchModulation" Bypassed="0" Intensity="0">
              <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/>
              <ChildProcessors/>
            </Processor>
            <Processor Type="EffectChain" ID="FX" Bypassed="0">
              <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/>
              <ChildProcessors/>
            </Processor>
          </ChildProcessors>
          <RoutingMatrix NumSourceChannels="2" Channel0="0" Send0="-1" Channel1="1" Send1="-1"/>
        </Processor>
      </ChildProcessors>
      <RoutingMatrix NumSourceChannels="2" Channel0="0" Send0="-1" Channel1="1" Send1="-1"/>
      <macro_controls>
        <macro name="Macro 1" value="0" midi_cc="-1"/>
        <macro name="Macro 2" value="0" midi_cc="-1"/>
        <macro name="Macro 3" value="0" midi_cc="-1"/>
        <macro name="Macro 4" value="0" midi_cc="-1"/>
        <macro name="Macro 5" value="0" midi_cc="-1"/>
        <macro name="Macro 6" value="0" midi_cc="-1"/>
        <macro name="Macro 7" value="0" midi_cc="-1"/>
        <macro name="Macro 8" value="0" midi_cc="-1"/>
      </macro_controls>
      <MidiAutomation/>
    </Processor>
    
    

  • administrators

    Yeah, I pretty much abandoned the possibility to have more than two front scripts (I think we discussed this a while back) and I wouldn't rely on that design approach.



  • Oh yeah I'm sticking to the panel method for multiple tabs, works great. I was just testing the multiple front-end to come up with another way to do presets. So it seems everything that I want in a preset has to be part of the main GUI script?


  • administrators

    Basically, yeah, but you can do a lot of stuff eg, store Objects as JSON data and copy them to a global variable in the onControl callback or set an attribute of another script that fires its onControl callback.



  • Ah that sounds clever a way to do it, good suggestion!



  • After more thinking, I think my needs are going to be too complex for the preset system to handle because for the different instruments I'm going to have a different number of samplers, and I'll need to be able to change the sampler IDs on the fly (because my UI makes use of these for the various articulations), there is some other stuff too that will make it a bit messy to handle. So I'm thinking I'll use the preset system for "like instruments" that share the same articulations, number and order of samplers etc, and use a separate VST for those instruments that require more than the preset system is intended for. But I'll stick to using a single HISE project for all of my woodwind instruments since they are closely related and rely on the same code and most of the same instrument setup and I'll just create separate .hip files for each.


  • administrators

    For what do you need the Sampler IDs besides setting the label on the interface?

    If its only for displaying the name on the label, it should be doable with JSON:

    
    // onInit
    
    reg instrumentData;
    
    // in onControl
    
    // This will come in from the preset recall
    value = {
       "name": "Trombones",
       "releaseSamplerEnabled": false,
       // ...
    };
    
    instrumentData = value;
    
    // Activate an additional sampler if needed
    ReleaseSampler.setBypassed(!instrumentData.releaseSamplerEnabled);
    
    // Set the label name
    mainLabel.set("text", instrumentData.name);
    

    I still think it'll be more cleaner using just one plugin and you can't underestimate the workflow benefits of just having one preset file.

    If we're at it: I don't know if you knew this, but you can save the presets as .XML file instead of the binary .hip file format. While it increases loading times a bit (only in HISE, not in the compiled plugin), you'll get a ton of benefits by using a version control system like Git for the XML file and the scripts. It even extracts the scripts from the preset when saving and stores them in local .js files and reassembles them if you reload the plugin.

    If you do this right, you'll end up with nice clean commit history that looks like this:
    0_1488716408854_exampleCommit.png

    Especially if you are planning to release the library on GitHub as GPL, it would make a hell of a impression if you could actually reconstruct the building process in a nice clean way.


Log in to reply
 

3
Online

339
Users

1.0k
Topics

7.1k
Posts

Looks like your connection to Forum was lost, please wait while we try to reconnect.