Factory Preset Creator
-
I've finally finished the first version of my factory preset handler. This script should probably be the first script in the project and loaded into the master container, but it might work in other places too. The idea is it will save the state of all of your project's samplers, midi processors, effects, and modulators, and then reload them when a preset is selected from the preset browser. You can enter the name of the Bank and Category you want to store the preset it and also enter the name of the preset - the Bank and Category must already have been created in the standard preset browser before you enter them in the script (maybe we can get the script to provide a list of the available banks and categories, Christoph?).
/** * Title: Factory Preset Creator v1.0.1.js * Author: David Healey * Date: 02/07/2017 * Modified: 19/07/2017 * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html */ //INIT Content.setHeight(100); // 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 object that will hold the preset values all modules. 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 samplerIds = Synth.getIdList("Sampler"); const var effectIds = Synth.getIdList("Effect"); const var modulatorIds = Synth.getIdList("Modulator"); const var midiProcessorIds = Synth.getIdList("Script Processor"); const var samplers = []; const var effects = []; const var modulators = []; const var midiProcessors = []; for (s in samplerIds) { samplers.push(Synth.getChildSynth(s)); } for (e in effectIds) { effects.push(Synth.getEffect(e)); } for (m in modulatorIds) { modulators.push(Synth.getModulator(m)); } for (i = 1; i < midiProcessorIds.length; i++) //Skips first processor which should be this script { midiProcessors.push(Synth.getMidiProcessor(midiProcessorIds[i])); } //GUI const var headings = [Content.addLabel("Label1", 0, 5), Content.addLabel("Label2", 150, 5), Content.addLabel("Label3", 300, 5)]; const var inputs = [Content.addLabel("Bank", 0, 25), Content.addLabel("Category", 150, 25), Content.addLabel("Name", 300, 25)]; for (i = 0; i < 3; i++) { headings[i].set("textColour", 4278190080); headings[i].set("saveInPreset", false); headings[i].set("editable", false); inputs[i].set("bgColour", 4285953654); inputs[i].set("textColour", 4278190080); inputs[i].set("editable", true); } headings[0].setValue("Bank"); headings[1].setValue("Category"); headings[2].setValue("Name"); const var save = Content.addButton("Save", 450, 10); save.set("saveInPreset", false); //FUNCTIONS function savePreset() { for (i = 0; i < samplers.length; i++) { if (userPresetData.samplers == void) userPresetData.samplers = []; userPresetData.samplers[i] = samplers[i].exportState(); } for (i = 0; i < effects.length; i++) { if (userPresetData.effects == void) userPresetData.effects = []; userPresetData.effects[i] = effects[i].exportState(); } for (i = 0; i < modulators.length; i++) { if (userPresetData.modulators == void) userPresetData.modulators = []; userPresetData.modulators[i] = modulators[i].exportState(); } for (i = 0; i < midiProcessors.length; i++) { if (userPresetData.midiProcessors == void) userPresetData.midiProcessors = []; userPresetData.midiProcessors[i] = midiProcessors[i].exportState(); } Content.storeAllControlsAsPreset(inputs[0].getValue() + "/" + inputs[1].getValue() + "/" + inputs[2].getValue() + ".preset"); } function restorePreset() { userPresetData = storagePanel.getValue(); //Restore from panel if (userPresetData.samplers.length > 0) { for (i = 0; i < samplers.length; i++) { samplers[i].restoreState(userPresetData.samplers[i]); } } if (userPresetData.effects.length > 0) { for (i = 0; i < effects.length; i++) { effects[i].restoreState(userPresetData.effects[i]); } } if (userPresetData.modulators.length > 0) { for (i = 0; i < modulators.length; i++) { modulators[i].restoreState(userPresetData.modulators[i]); } } if (userPresetData.midiProcessors.length > 0) { for (i = 0; i < midiProcessors.length; i++) { midiProcessors[i].restoreState(userPresetData.midiProcessors[i]); } } } //CALLBACKS function onNoteOn() { } function onNoteOff() { } function onController() { } function onTimer() { } function onControl(number, value) { switch(number) { case storagePanel: restorePreset(); break; case save: savePreset(); save.setValue(0); break; } }
HiseSnippet 3088.3oc6ZktbiabDFb2kNqnOJ6TtRkehnJUEsq1kDf2RabxxSQJdHHBRpCWa4LDX.HHwEAFPHRW6uyqSdDxiTdCRlA.jD7RGthcb4X8ic0zGy708Lc2C5QbVFBPaaCKpHeZ2YlPpHeVT9Y5ngkFBTzopWlJxKixYAsgHphyLA11PQpHQd9YDtQN3ETd+7u9qEAp.cA3JRTT8MTDfMUzTPqnx89FJppUAhvtJZgjN86qKXnWxP0vAijmGkgxDHLFHCaCHh8rnTSUft1TQXhlJYbqwrClWHN4mhwW7Syx2JmVEL1sp+XIOAnDbrrf5n9X0ohDMx+F+SjnUDUPFV7H.BhmynEMDmwOzvU2eo6qXqLPERFvRwiwjO455HnkDP.FRzpFphDGB42oJMTQUjagC0lBOybqbuO22890QaoHprj9J27W5wfdkFgc2Qd15P9EqAY18A4G.dQBAuW3CuuJJufkhIZEGB19cQw1Md4m4eRnFPWTEtNB8Ui5Y+ouLZhW+5XzultqBREdJcUeMo8UklnKzhdJabl3rwGYSjrfCZng0ozkASUDoqAApvYD5kwF6ozLISvjKQRF1bDZsLDUjTfhmRydRX5MwG1zswheFWyooneK8PDxz9zDIbcciKq6D2vRNgpuP1IjMUeaJLBf5wGhzTi85DwhkHQ8106FqjAdeVGEWCLFV0BOX499QYYXdCMKCyqdGQZ5RVPL.oAz1XCDeVk1DnCUeC8PEQHsB5MzXSk1FLkLfVQmFMDKhmWHF9vtMhdJvZgtbDUo+F5EqNPTzizQGFVfCeCMFAj0OL033Y7nCm5eD.KhDP0FtSYHfott+NAVPjkCbcawXvHn.BiT.h1EGoRODeBJDvwPV0AZSCvrzLDcTg1wiIqZL.nR6XCs7mZ79F.aKe+G8madHxaJBjag2BXiWBQ4ESJsjgkmXX2mHT22YFmnecMSCKDPGcJluhsOvzMPzBpF5POc7w8anG3fnsGBrH6JVPIHNzW.9G1xSzmrfGsNfINhPaK.MS7Q75h1XCwKgXbLRqK1TwF6G484dHVmUp.kjvXXOZTwi45J34.AXnsGcZsf+FpEN+w9.nWz3pjIGtSqin529gssgsouDp6fUX3rfcLxl4Q1jS8q7juJ12G6fEKcbSG6gGsD3dIm7Fcj8qvf8iASAjLEK8rdyP.F2XB7cvGACqrFQ4vdYO8WYKaLEK82GoEdVTv1D66nUn+ya45iqB0kQCwLO93WQmHA+XESaZIEKruwbgbztCUDFhOVZ3fikF.8ODa6sC4gm0bfahovLOZy0+aU9P.RSj3rd0CsqLDBDUzk81OBkSoIX.Imh2+w5mMIyqdC8djHIVB1L2uLovxjhwSl0NVnna5f1yxWDnO1ewSt6YtDNWjLtnwh0eOhQtbvhkOo25ubCiweCKk+VCwMuvif8Y9ICQv6P923.OGoSlKO6ILL4IIW2V1MRbtHC61BBwkoAqmFN1A99hkxLPNzxlOyIYRkMSZxrsgb2C.2PxPqZPN8OFaIzX9vpTd9tdL+kbYCycoeOrDICKgmKeyjI3JbqU4pnCBYnSRSNk.ozY7JaRpHgIbuNT743p8ZWpa8KZyGSxQW.oXn6sB9hdj2V4l6xKyoDNbL1AXIOPQhdi77wWk76anmZnH9J58JfWtrCNXO7wterHgFEGdGoRk2kzNhrMgCL2BrKRe8nv5x7w6ApqmudKjFv1GnqF7nvYnzjOJnFtDwdP6VUQ1BvqjvGyqM9wA60yl93f9Fkv1G72UktsMg0jJvL1j1NMkEQPjaq.KnpRFaYnZWvN3vePTONfVdQ.4qnOl9vDGh+2.lr2GyjaxLt+05HQzjk21PEhIon+i7ZQJstH3FSjXugiu25ljqc+sUy56vkb63qNsjkgl+cFIIb2eTevIB5+B9Zz9GHdjYS7j8fvQ6AH2eSb+4HH9V79q2V79CweXbs6DG9vJTr88gpUh8.fZqf+6AW6MQgOzVOD99P2ZR9P.bWg42GHuuzBA.cqfz6EraJ8J.6ccrREZ1rXgRMBUFyPusABdgt+gbrXaxQRZWrBRCfOMsKtj9nXcOpcjti1.n0a7+HK+qf6pfDFFvHveI.rgqElcJwkrQrIwBGf+PwwjK0DnBtz7o9AFSWStCVTq2OVkIrxDeDUjmudKMh83ZogfeRxPBZnWWWAcgILnmLKbyaQQRJLoUt0vT8bm6PrUj1tOJTAItonh7h.wwhh75mxmuneJDeJkhHUjuHZX2Lk21B0y96OO547Wz96ObQhiCOk9aOLalrwSYHLqGikBeoRkYMrUgihWtg4Yot9hok3lTNYp32.xx1qmUkg2Z128xl04A1ScaeGqAWZigmUPbTpVwqpMmMk0sWoWq7TUd2YMEykLWZ2YWMp+33UU5vWLC5ZPathhEawZVne0qPmqNdbi3stIy0cll2H6UsF.ZO6Zotc33xN6lA0yLojQtr.mA03mNpNmc8ho6T4hBHIMWal9vqRMIUQ2x8O2RRKck7yJoopORoawdEJpyN+jQ0ZUqq07qOQKewrSjK0nOp77V1mLd5cJYl2rmrk6M1kRMSdfzMZFc.wcRO6hqlb9cNYP0jub.3tLUts+cmwoN0oJTo7vqljSYbyNSZpN95zcUan3LQuX9yO6pzBCmVkCb6sYYpbiR+Yi5vgutY0xRplb4a1ucc8JUZI1+7jlyc6qVY50njb8xzubUX2LoLJLpQ5pmjsBGyvqGEeh9ECxdYylSNan0E2TCN9JSA4rnxWdqA2TqAim2v3rymToYyjhoqU0p+ndMEc6T0VsQGE1jsuo1Hork6kb.CxLsYCgpWLFNsc+YWerxj7YbG2NSOmRGirj52pwIsSNvMYxIozzKafJOH+TkTxn18zKU3xJslW9lVo0pVCXNuXmRIQLNERaN0NuagjSjKTJ2zTBIUbuFkkMayzUyMtvstxt75JxisJJmWhAVu1EL251O6sMDJoUpnzM4DGjyZZiTcjL.mLJcRySpNHGSxKx1Nkw7ySwNqEROI7NA2im2WiWeboKaKlaX26ffbnrxoJYnIyywawHYWR3NfS9blYuL6HPFYMDqXiiEOov76ReR9AEu8XGoLC5ICFkqoZuYGW97qjN+bW8yusXpISlemQud1HGfcINWol1XefqiP6yFvUtnP6qc36b2IckYXQGe1Lqlk4klavkxLc+hcE4SkrCfy91yFw0q4PibcZMyH2bW2A51WyVU+1h7UDQWVH8M4FUqRG0TYqkj6rLMpMBF+5oYqL97C+vG8Cl2UDr22b5EA+IQIeEUPjaPNgGToXQW7wUOQE+jnd8.OrRa211uL5xlWDtixjtzGv.WTXs90R5potsBZV3t3+DZyLyNayL6tRO9Hg6WEkiTWZ238Y6.u37s+Xi2fl1+4Q8auzJv9hnUu9GZG5+gAkWtmmm4SiRNCgGsQC4+A9BM+sG8KzrYA7W73dFkeF9jI+2+r7uF58KoPuu1GJ+1n7H7EX0TzkCd..O7bPzEi9IM7CeYaUCfHux7Ph8OeeQGr6xZcp+i26sTEzLbzWasB.dGnIDPZ+8ZvqSmyrLbL2TKp26cVoqEPXL1SD1FuPm71ngkkpjkgssD1b7lK6vr3brjI9pPqHbJ9luqSqsiFdqWGeUYxK0FIBljOpaAL8dX3MeM2W939NBr5dW.OTBqhJxglZh3qGDGZRWZVcIscMzbvCQHRWSCI6NRx8I+ZRNO397GGbefmt92r.i7JjcuJ5SwQFXJ9Y1JCk.NpnkTePPV.gvGsWQ4qeeGnJD+wtqH8GeeSb8Vf05gLOI6i8wae22dvuOpOboIoKn+kQAmm++8EbtOW2WDTtgFu5VneluO+YQ8x6SSJn8+Jn1wvgjRtE.YobGtbAthBOtJp.LbckmQpU4OlgLl.HdntHyh+ngBXxtrHDlI6BlTBKlJrsE76jtDhwDTmTgv6dTp3xapgORaiqUSfDtJFkeeezHUddAwygoQd9L+LL93YuEjaoHvYfcl33HRRmnu6mFi9mh0PCHXY7cBAuDB189ROJXqT26uLrCh1hLllM7GHS5M62IHPhbeK9Hwt0H4SViTOYMR+j0HySVirOYMx8j0H+8nA41IEbPFZ94won9O.0A13HC
-
Nice work. It must be quite difficult considering the moving target type of the preset system these days :)
However I still disagree with you on what's a user preset - in my definition it's just a collection of frontend interface values (and this is what the in built preset system of HISE offers). With your script a user preset becomes much more powerful because it can change the entire structure of a HISE preset, which might lead to unwanted side effects (maybe even a bit like the dead lock issues Hasan was struggling the last days).
BTW, not sure what the labels are doing, but I started to merge the layout system with the frontend interface system so you can add a FloatingTile widget to a script which can be populated by almost any component (preset browser, virtual keyboard, settings dialogue). This example shows how to embed the actual preset browser directly in the interface:
Content.setHeight(400); const var FloatingTile = Content.addFloatingTile("FloatingTile", 20, 20); // [JSON FloatingTile] Content.setPropertiesFromJSON("FloatingTile", { "width": 694, "height": 366 }); // [/JSON FloatingTile] const var presetBrowserData = { "Type": "PresetBrowser", "StyleData": { "highlightColour": "0xFFFF0000", "backgroundColour": "0xFF333333" } }; FloatingTile.setContentData(presetBrowserData);
This system is currently completely undocumented and highly experimental (and I expect that there will be some breaking changes until it gets to a stable point), but this will be the way to go for building interfaces with HISE in the future.
-
Well I agree with you fully that user presets should be for the user to save their front end values, I feel though that we need a system for developers to create different setups of their instruments that go beyond what the user preset system is intended for.
Other than hacking the preset system I can't see a way to allow the user to switch instruments from a single VSTi. With instruments than only require sample maps to be swapped it's not such a big deal but when working on a varied collection of instruments, where modulators, effects, scripts, etc. also change from instrument to instrument, incorporating all the various parameter settings into the front end script isn't practical. With Kontakt I'd just create a separate NKI for each instrument but using the preset browser seems so much better than providing lots of individual VSTis.
So I think this little preset hack is the simplest way and can be easily dropped in to any project to allow a developer to create a detailed variation of their instrument that is easy for the user to load.
The labels are for entering the Bank, Category, and Name of the preset. So if I want to save a piccolo preset I might enter "Woodwind", "Flutes", "Piccolo". The only problem I found was that the Bank and Category had to have already been created as the save preset function doesn't create the folder structure.
The new floating tile system seems very interesting. Does its introduction mean you'll be removing the standard header with the preset browser? And the default on-screen keyboard?
-
I admit this might be useful in certain scenarios. But be prepared for some hiccups as there are a lot of things happening when creating modules and its impossible to predict all combinations.
I think adding / removing or changing states of modulators and effects should come without problems, but as soon as you change a sound generator, it will trigger a global recompile of all scripts and this might cause some very nasty issues with multithreading.
Can't you use the preset browser instead of the labels? The preset browser just assumes a two level folder hierarchy for Banks and Categories and would be perfectly capable to do these things without having to write script code for this. I am offering these functions for user preset handling on the script side:
- load next / previous user preset
- save user preset (with a given name)
- open the preset browser
- get the name of the currently loaded preset.
The floating tile system will indeed replace both the virtual keyboard as well as the top bar (in fact it already has, because I removed these components from the compiled interface a few days ago).
However everything that can be found there can be put into a floating tile, so if you've fallen in love with the top bar - which would be a little bit awkward :), you can still recreate it, but customise it better.
The reason for this decision was that almost everybody wants to fully customise the whole appearance of the plugin's interface. I'd rather provide a collection of ready made C++ components that can be shuffled together using the JSON data from the HISE layout.
-
Oh I just got what you mean about using the preset browser instead, that makes so much more sense :)
When you mention changing sound generators does that mean restoring their state or adding/removing them from the instrument? I'm thinking I might just be able to live with swapping out the sample maps and not worry about saving their state - although might be an issue if two presets need a different number of RR groups.
-
RR Groups should actually be saved within the SampleMap - if that is not the case, it's a bug and will be fixed :)
-
Aha that should make things easier
-
It's just occurred to me why I'm not using the built in preset browser, that will only work with the front end script so won't work with this script. I've just been trying to implement this script in a project and already I've run into a problem with it not restoring MIDI processors. One element that will likely vary between different sample sets for many instruments is the playable key range and the key-switch range and I'm currently adding that functionality with a separate script that keeps everything nice and modular but to do it with the built in preset system I'd lose the modularity. I'm again thinking it will just be easier to use a separate VSTi for each instrument although as we both know that won't be as convenient for the user.
-
Why not wrap it in a namespace and offer this in an external file that can be used from the frontend interface directly?
And changing the key colours dynamically when swapping sample maps should be no problem (just store the data in an array that correlates to the sample map list).
And if you want to change attributes of a MIDI processor, you could also directly set its attributes instead of restoring the whole thing.
-
The namespace idea is good. The problem with storing the attributes of the MIDI processors is there are a lot of MIDI processors in some of my instruments, if there was a way to iterate through all of the attributes of each MIDI processor then that would be practical as it could then easily be automated in a loop but currently I'd have to do it manually for each processor and it would vary between projects.
-
Well, you can just create arrays with values that have the same length as the MIDI processor has attributes and go crazy with for loops:
HiseSnippet 1137.3oc0X0kaaaDDdojXSDSUPSQO.D5IYDWYRQEmebMrMciMbS8OnJvn.FFoTjKsVDpcEVtzotA9fj25Qnu1iPetWjdCZmkjxjThRPxHFnl.ZA2YluY+1YmYAGcBm4hCCYbjxid6UivHkuTs2UTwfcG3PnnC9djxiUOzITf45IhruZjSXH1ConTceo.k50PwO+yV1NANTWblHD5TFwE+ijgDQlzeY62PBB1ywC+VxvbV2c6CbYzcYArHfOUUMPibbeuyE3ibjlUQEcIA+gPjhgpUm172a1+21o8DO6GM.F8A4WdgUWlmEFlhbi3bLUbJ.Gonp7uvih5q8HBFumvQfAeV0l4cUuArOPSV5SIgj9AX4DSTOfSIhQ6NfD3cx3vVHBoT6jrfX0jf32ndHwibi7rf4WEqPOCQ9voRkhTpVAJYNKJsGKvS5fYPup4nWsD58D0dtbxHQlFI295ToyfcIJQU9iZpvoTnP+RGttOgGJ1QeS8yLZarptQaS4Pmy2ng1DFYmXzKk5egb34RixaVHFd2aZmIGrJ5wDSm1kxg0mzu6IWdvz3751WfEENaZ0LVeyUJr.8hWf4.JwfXT4fYGIDLpbKrKiJfLt1NddIBa0LUYyU06.6NSiU1Pas0zO6G5c7QiAdt1XbgX4oyHLWPvg6wYCklk2GergltdyPmKwGPOgiA6a9JcemfP7pwZ3NdD19bVzHPdSylMztNcAWqvJVB+smG+sg01b8YrArWvMf8msMfMrA7intBBipynGwD3iosVQ6iZ00tdJM99koRRYNKH.yKSq7NJ9bf0hFMrOluJD.Chvw1Q7SEpu4liCzqzPqNrkqW2GReHPD1XCch92kVA0N.SuPL.D8zmJsLwz5wolxP4NBAmzORfaQVMExYjyg3Q85WCVVheSqllgiSRemxyofx6Z4OLbnLO5au7z2dgnu8sg9YtFXObjAWAV7dUsE6dU2jL4bFxnGPIhiGguYdR91XIFnaxyxKJK+Juz37pRLKSzzWliRqtf2Vza0efZ7oPoWkapnlq3+MTV+hU9RIsZJGg5U4MxPXcB.clEhNw2SzoTPVyBjE.xpS5J8+tx5xxkdzmmbIi65bo4+QBY4UJUSgBuIhSqdbZZUu.hGliHfS9BU4gEJNxj+i8PK.1GDisSAv+9m9zet0hC1ZRv+0VKb4vCUSt6nz5A+655gmYdKpGLJuHp6r.0E.0c86SEQZ2SJhlagSs6mENKL3tE.usk0eWVUGzfCyKJvQTrqGYmhoJfDjBEeGHCfgDwU46jbIZExnzVgLK6vZAo6STOgHbGTNeqTBegS+6Z9l1XYC0W66icEYjsl5d+7ssKxkfJ+DKRPnWbnC74V+JzC8QQC6Aso6hAlPovGHJ6qthLSNYtgbtLxzCS8LF2zcpRS4bkTkliUhF53xYuyMIgT155Cik.bhF+G.TW8P4bcywYhRdOD5K6cttx.w2BLubDcVZDVKMhtKMhmszHVeoQ77kFwKlCBYSv6DIXCSJKPn+C..59Q1
-
Thanks for the code example. I think though that this still requires that I know in advance how many attributes each script processor has and I need to keep track of these manually. If we could do a for loop type thing to iterate over an arbitrary number of attributes then a few lines of code could handle any number of scripts with any number of attributes and the values of the attributes could be saved automatically into the user preset. Maybe I'm looking at this the wrong way though, I'll play around some more with your script example.
-
You need to know the number of attributes anyway when you create the preset data, or am I missing the point?
-
Yes currently I would need to know the number of attributes, but if we had a function that could give us the number of attributes for a script then I could do something like this.
for (s in scripts) { for (i = 0; i < s.numAttributes; i++) { presetData[i] = s.getAttribute(i); } }
-
Ah yes, that makes sense (I thought you're creating those by hand, but for this approach, you'll need a getNumAttributes() method). I'll add this tomorrow.
-
Thanks Christoph, should be very useful :)
-
Could this
getNumAttributes()
function also work for other modules such as effects, modulators, and envelopes too? -
Yes of course...
-
Alright, the changes are pushed to the repository.
-
Excellent, building now!