Multiple instruments, single project or multiple projects?
-
Ok that's what I thought. Then I'm still confused, thank you for your patience :)
I made a brand new project folder, inserted a script processor, put in one command
Content.makeFrontInterface(600, 250);
compiled it. Then I opened the plugin preview and went to the preset browser, created a preset and then when I try to load that preset by clicking on it I get the version number error. This hasn't happened to me previously. I can make a little video if it helps.Update:
So I opened the preset file in sublime text and the version was set to 0.0. I changed this to 1.0.0. I tried opening it in the preset browser again and I got the same error (it said version 1.0.0 this time though). I click Ok to update it and it resets it back to 0.0. So I think there is a bug here. -
have you set the Project Version at File Setting Project Setting?
-
Aha! mystery solved. Nope I hadn't set that, never have, I will from now on :) thanks again.
-
This is important as it will also show up as version of the compiled app / plugin. I'll add a safe check that requires to set this before exporting.
-
Excellent, that will help newbies like me :p
-
I've got this test project with 2 script processors:
<?xml version="1.0" encoding="UTF-8"?> <Processor Type="SynthChain" ID="Master Chain" Bypassed="0" Gain="1" Balance="0" VoiceLimit="64" KillFadeTime="20" IconColour="0" packageName="" views="32.rk1bzA........JPT.............+Oe7wG+SA...." currentView="-1"> <EditorStates BodyShown="0" Visible="1" Solo="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(600, 200); const var knob = Content.addKnob("knob", 0, 0);function onNoteOn() { 	 } function onNoteOff() { 	 } function onController() { 	 } function onTimer() { 	 } function onControl(number, value) { 	 } "> <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="knob" value="0.46000001"/> </Content> </Processor> <Processor Type="ScriptProcessor" ID="Script Processor2" Bypassed="0" Script=" const var knob = Content.addKnob("knob1", 0, 0); const button = Content.addButton("Save Preset", 150, 10); //CALLBACKS function onNoteOn() { 	 } function onNoteOff() { 	 } function onController() { 	 } function onTimer() { 	 } function onControl(number, value) { 	if (number == button) 	{ 		Content.storeAllControlsAsPreset("Test Bank/Test Category/Another Test.preset", ""); 	} }"> <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="1" onNoteOnOpen="0" onNoteOffOpen="0" onControllerOpen="0" onTimerOpen="0" onControlOpen="0"/> <ChildProcessors/> <Content> <Control type="ScriptSlider" id="knob1" value="0"/> <Control type="ScriptButton" id="Save Preset" value="1"/> </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> </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>
I've got this preset
<?xml version="1.0" encoding="UTF-8"?> <Preset Version="1.0.0"> <Content Processor="Script Processor2"> <Control type="ScriptSlider" id="knob1" value="0.63"/> <Control type="ScriptButton" id="Save Preset" value="1"/> </Content> <MidiAutomation/> </Preset>
But it doesn't do anything when I load it. It seems presets will only affect script 1 which is set to frontInterface.
-
Yeah, as I said, it just supports one front interface.
Check out this example:
0_1488794377602_Preset Example.zip
The second Script Processor is no front interface. Instead it uses a global object that contains the value for its knob which will be restored by the main interfaces' onControl callback.
This is a little bit complicated at first, but once you understand how it's connected, its a pretty easy procedure.
-
Aha I get it now, thank you! It seems a little awkward though if you have a lot of scripts (like I do) and want to save the state of most of the controls in the preset. Can users overwrite or delete factory presets after the plugin has been compiled?
Edit: Just reread the blog post about these being read-only :)
-
I'm struggling to find an efficient way to organise my preset handling script. I can see this being more suited to smaller instruments.
So I have my woowind .hip file and I'd like the user to be able to change between all of the different woodwind instruments. There will be at least 10 instruments and each one will have 10 or more samplers that will need to have their sample maps swapped as the preset is selected. Each will also need it's key range setting, modulator settings adjusted, and various behind-the-scenes knobs adjusted on scripts.
To hardcode all of this seems a very large task. I could add code to each script so that its values are stored in the userPresetData globals object (although it would be better if HISE did this anyway I think). But I will still have to write all of the sample maps into code, and if I want to add new sample maps I will have to edit the code again. Is there a more generic and automated way to populate the userPresetData object?
-
What I'm finding is that by using the global user_preset_array idea I'm essentially building my own preset system for the instrument that works independently of the actual HISE preset system which seems a bit backwards to me. The only purpose of the HISE preset system becomes to change a combo box value. Is there no way the preset system could just take a snapshot of all of the controls, loaded sample maps, and the bypass state of samplers/modulators/etc. and then restore it when the preset is selected? This seems far more practical in the long term than having to construct this functionality individually for each instrument.
-
I'll have to disagree with you here. The idea of the preset system is to store snapshots of the main interface so that it recalls the controls. If your library allows to select different instruments that swap out multiple sample maps and adjust modulators and key ranges, then this is something you have to add to a combobox anyway.
On the other hand, adding support for changing modulators behind the scenes or swapping out sample maps for samplers directly opens up a parallel world which is disconnected from the main interface - do you get what I mean?
You might be able to reduce the complexity by generating the sample map IDs dynamically (which requires a consistent naming, but we'll agree that this is good to have anyway). Let's say you have :
- a trombone
- a trumpet
- a flute
- a clarinet
and each instrument uses sustain, release, staccato and portato samples.
then you could do something like this:
const var instrumentNames = ["Trombone", "Trumpet", "Flute", "Clarinet"]; const var articulations = ["Sustain", "Release", "Staccato", "Portato"]; const var hasPortato = [true, true, false, true]; const var keyRanges = [[40, 80], [43, 80], [56, 90], [50, 93]]; sustainSampler = Synth.getSampler( articulations[0]); releaseSampler = Synth.getSampler( articulations[1]); staccatoSampler = Synth.getSampler(articulations[2]); portatoSampler = Synth.getSampler( articulations[3]); inline function loadInstrument(i) { sustainSampler.loadSampleMap(instrumentNames[i] + "_" + articulations[0]); releaseSampler.loadSampleMap(instrumentNames[i] + "_" + articulations[1]); staccatoSampler.loadSampleMap(instrumentNames[i] + "_" + articulations[2]); portatoSampler.loadSampleMap(instrumentNames[i] + "_" + articulations[3]); portatoSampler.setBypassed(!hasPortato[i]); changeKeyRange(i); }; inline function changeKeyRange(i) { // set the key colours for the given range in keyRanges[i] }
In the
onControl
callback of the combobox you just have to callloadInstrument(value)
. The amount of needed strings is reduced from 16 to 8 here (and in your actual use case it would be 100 Strings vs. 20). Also, this collects all information into nicely readable arrays at the beginning of your script. -
Thanks, this makes it look a bit simpler and gives me an idea. I don't like having to hard code the sample map names, but what I'm thinking is if I gave the sample maps the same names as the containers and or samplers that they are to be used in then that would allow the .hip to be used for more instruments without having to keep editing the code. I could just read from the various module names.
-
It seems like the group XF setup isn't saved with the sample map so switching between a sample map with for example 3 xfade groups and one with 1 causes issues
-
Yes this might be true (I didn't check yet, but on the other hand I didn't add support for this explicitely so it's likely to not work). I'll try to add this soon.
-
I've built a little test script that should allow me to save and load the state of all simple envelopes with the user preset. My plan is now to add all the other envelopes and modulators so this is the only script I'll need to use to save all of my preset data in my projects. Before I embark on this though I want to know if you think this is the most efficient way to do it or if there is a better way?
Also is there a way to iterate over all of the controls of a script? Currently I can go through each one as an attribute but I have to know in advance how many controls the script has, it would be nice if there was a way to loop through them without knowing what number the last control is so I could neatly get every control's value into an array.
<?xml version="1.0" encoding="UTF-8"?> <Processor Type="SynthChain" ID="presetSaveTest" Bypassed="0" Gain="1" Balance="0" VoiceLimit="64" KillFadeTime="20" IconColour="0" packageName="" views="32.rk1bzA.....C.........LDZg4lakwVL.....vp+++O" 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(600, 250); // Create a storage panel, hide it, and save it in the preset const var storagePanel = Content.addPanel("storagePanel", 0, 0); storagePanel.set("visible", false); storagePanel.set("saveInPreset", true); // Create a global object that will hold the values for the other scripts. global userPresetData = {}; // Set the global storage as widget value for the hidden panel. // Important: this will not clone the object, but share a reference! storagePanel.setValue(userPresetData); const var presetHandler = Synth.getMidiProcessor("presetHandler");function onNoteOn() { 	 } function onNoteOff() { 	 } function onController() { 	 } function onTimer() { 	 } function onControl(number, value) { 	if (number == storagePanel) 	{ 		userPresetData = value;	 		presetHandler.setAttribute(0, 1); //Trigger load preset button 	} } "> <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="1" onNoteOnOpen="0" onNoteOffOpen="0" onControllerOpen="0" onTimerOpen="0" onControlOpen="0" Folded="0"/> <ChildProcessors/> <Content> <Control type="ScriptPanel" id="storagePanel" value="JSON{"modName": [4, 27, 0]}"/> </Content> </Processor> <Processor Type="ScriptProcessor" ID="presetHandler" Bypassed="0" Script="//INIT Content.setHeight(50); const var btnLoad = Content.addButton("Load Preset", 150, 10); const var btnSave = Content.addButton("Save Preset", 0, 10); const var modNames = Synth.getIdList("Simple Envelope"); const var simpleEnvelopes = []; for (modName in modNames) { 	simpleEnvelopes[modName] = Synth.getModulator(modName); } //FUNCTIONS inline function savePresetData() { 	reg mod; 	for (modName in simpleEnvelopes) 	{ 		userPresetData.modName = []; 		userPresetData.modName[0] = simpleEnvelopes[modName].getAttribute(0); 		userPresetData.modName[1] = simpleEnvelopes[modName].getAttribute(1); 		userPresetData.modName[2] = simpleEnvelopes[modName].getAttribute(2); 	} } inline function loadPresetData() { 	reg mod; 	for (modName in simpleEnvelopes) 	{ 		simpleEnvelopes[modName].setAttribute(0, userPresetData.modName[0]); 		simpleEnvelopes[modName].setAttribute(1, userPresetData.modName[1]); 		simpleEnvelopes[modName].setAttribute(2, userPresetData.modName[2]); 	} } //CALLBACKS function onNoteOn() { 	 } function onNoteOff() { 	 } function onController() { 	 } function onTimer() { 	 } function onControl(number, value) { 	if (number == btnLoad) 	{ 		loadPresetData(); 		number.setValue(0); 	} 	if (number == btnSave) 	{ 		savePresetData(); 		number.setValue(0); 	} }"> <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="1" onNoteOnOpen="0" onNoteOffOpen="0" onControllerOpen="0" onTimerOpen="0" onControlOpen="0"/> <ChildProcessors/> <Content> <Control type="ScriptButton" id="Load Preset" value="0"/> <Control type="ScriptButton" id="Save Preset" value="0"/> </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="StreamingSampler" ID="Sampler" Bypassed="0" Gain="1" Balance="0" VoiceLimit="128" KillFadeTime="20" IconColour="0" PreloadSize="8192" BufferSize="4096" VoiceAmount="128" SamplerRepeatMode="0" RRGroupAmount="1" PitchTracking="1" OneShot="0" CrossfadeGroups="0" Purged="0" NumChannels="1" SampleMap=""> <EditorStates BodyShown="0" Visible="1" Solo="0" MapPanelShown="1" BigSampleMap="1" GainModulationShown="1" CrossfadeTableShown="0"/> <ChildProcessors> <Processor Type="MidiProcessorChain" ID="Midi Processor" Bypassed="0"> <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/> <ChildProcessors/> </Processor> <Processor Type="ModulatorChain" ID="GainModulation" Bypassed="0" Intensity="1"> <EditorStates BodyShown="1" Visible="1" Solo="0"/> <ChildProcessors> <Processor Type="SimpleEnvelope" ID="DefaultEnvelope" Bypassed="0" Intensity="1" Attack="4" Release="27" LinearMode="0"> <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> <Processor Type="ModulatorChain" ID="Sample Start" Bypassed="0" Intensity="1"> <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/> <ChildProcessors/> </Processor> <Processor Type="ModulatorChain" ID="Group Fade" Bypassed="0" Intensity="1"> <EditorStates BodyShown="1" Visible="0" Solo="0" Folded="1"/> <ChildProcessors/> </Processor> </ChildProcessors> <RoutingMatrix NumSourceChannels="2" Channel0="0" Send0="-1" Channel1="1" Send1="-1"/> <channels> <channelData enabled="1" level="0" suffix=""/> </channels> <samplemap ID="" SaveMode="0" RRGroupAmount="1" MicPositions=";"/> </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>
-
There is also the
exportState
andrestoreState
methods which export the complete state of a Modulator / Effect (including Table values and child processors) into a encoded String that can be used to restore data:This example has an LFO that is resetted using a previously exported String every time you compile it (You can change anything, even add modulators to its chains and if you recompile it goes back to the original state).
HiseSnippet 1201.3oc0VstahaDEdLIVZSZSa218A.EoJkUzZg4ZhVU0.lKAHDHXBIj+rxWFiMwdFmwiMwT0mg9f0Wl9FzNFG.ytrWnp6O57iDNemyY72bNy4Lm9DrFzyCS.be0vPWHf6q4kCQTSISEKDnUM.22v2UwiBIoigpF5p34A0Abb60LBf6f8AKV+0uVUwVAoAWCA.ivVZvKsbrnqQ6edGKa6FJ5vgVNIrtv4szvHIrM1mwm83yBbUzdPYB7JkHyRwCBrfy7.bY4ymSf7fn57JBrEQw8VgJEgBBAsEDxOnFJCCOnYkY0XnUED.Z9DBDQGwbGvwy82rEGeccKJlHSUnP1dxWEqGJahmgh+zir7rTsgQBh.YFmhgafs0iN7QnsPrvhghFLga.ISKa89KCqd..298WGj2KNH+J9tV5VqvWGr+tEJRu1ijgatTaR482fxheZJuU5wkfd6GSuWxKqQrboq0Dwsu+YzO.6hUBR8mo3YYQOZ5.ER5KazqKV22VgQ6z+R5EWsDl.oq.O4XlIoWId7qeyQGdzgq2fmXdkbSDfO4hIzEgfSdWicIv.KruGymiycZIg7XsYWKRFTSRpVSnoW8gkMpVTx31xSMHs0mIORzsdaRGkIilGNylnHM2FKNkN.1t7MYaVQ99wJO11LeKGoIiaX26rdYFn10d1MsIMtnos3nYULcxYFbS+4mJT3hGyMty0ijqbih6c5J0uAm+Ri9plSumNr0bOgBB2hB0yPCt+rNcUGKNcZoRMGLt7EE52HStxPS5C9MdrSob4tqTm7JWVZ781C0GW2dd6vFxU6OnkYQ0IpxtH0AYbJ1bNZ9cEnXoogmROytSFhm1CEFeZ8Q4TjJZV4I8dA0rC6bsbV058UssHhSUKeiksVYZU8gnZ8tMnjzffpyK1UxvP85Jczad1Piw8mYEL2KS9qDUDfUJLbBw5Qm4yxVo58sJ6GJ52VNGbjcn8Eg8IFicb8l52Yb9d37S7ykQtdlJ4UK9HT532jNMK8rQ1i.8X+CFm9VlthSiF9HMpEFkFitBSg8Pm75C+sCO3ve+8zXXrMURXDkfssgjsoMpSC4i31IHeGUH4mX2ir8gKsiUzrYI2gedkbrqjTV+lDFhQsPVzdtvUxwGxkHYAqNbIgVenRht3vrEyVC8904.oXNE0+88J3YscVlhR1KJp+9yJXAqMp1i58g7rngI6++kuA0yORwyytUsfjeK+F8O9zbrAA9nODok.7EmG8VTq0Okc9eb9sJAPCLwI96s18KgSXemjHCgNtXViMsjO6I6fwTSKzjMeg6UmK4yJ.bVt6.tejOWAgMWA8X+wHSzOOSZgL3cCr684EX2RvL0mYx+GVDWWEAihv+ml+y9AdScKWb2IFuJ89+.F+R99VTMysWgkZK7E.9hy2mGP4H95FFPM5ZxtOei692NMxNPkAXeJqroqBkX8DqP+JeGY13fZPFSPHnczjZbohZmEKmMRdQEGDomc4vcOqTLRl6YkhKUBbTzH32pE2yLpn3EKPXbBsXPyCXS7xjSKBV7XPLucXyk8VMsn.wOyX918H2N6Q9c1iB6rGE2YOJsydTdm83zOhGQCAWwm0nLtr..9GTowfBB
-
Oh that looks like a better way to do it. Does this work script processors?
-
Yes. It does the same thing as copying a module with Ctrl+C.
-
This is excellent news, I'm glad I checked in with you before I went any further :D
-
The functions only seem to be available for Modulators and Effects, I get a function not found error when attempting export state on a Midi Processor.
const var script = Synth.getMidiProcessor("Script Processor"); script.exportState();