Parameters mapping explained
When we write the .kuiml file for our script we usually use names like "custom_param0, 1, 2, etc" to access the parameters we've added in the DSP part. However, these "custom" parameters are not the "real" DSP parameters. Let's explain why they exist and why they sometimes cause troubles.
Dynamic script loading
The "real" DSP parameters the PnS engine exposes to the GUI have names like "dsp.input0,1,2, etc". This is true for all Blue Cat Audio plugins. But Plug'n Script is special in a way that it has to load different scripts on the fly, and each script has it's own set of parameters. But the plugin in the DAW cannot change input parameters on the fly, the DAWs usually don't support that.
So Plug'n Script uses a "trick". It exposes to the DAW the list of "generic" parameters with ranges from 0 to 1 and names like "Param 0", "Param 1", etc. and later inside the GUI it links these parameters to "custom_params" which are created inside the skin. These "custom_params" are not exposed to the DAW, and they can change their ranges, names, default values, etc. in according to the currently loaded script.
Let's take a look at this inside "LetiMix Debug" skin (included in LM_Skin) to see it better.
I've loaded a simple "Gain" script that adds a single input parameter called "Gain" with ranges from -20 to 20 dB.
As you can see it's linked to a dsp.input8 parameter, which is a "real" dsp parameter. And as already said "custom_param0" is an "alias" or "proxy" parameter that is created inside the skin.
When we change the value of "custom_param0" it changes the value of dsp.input8 and vice versa. The value of dsp.input8 param changes from 0 to 1, but is displayed as 0..100% by default.
Note for exported plugins
Plug'n Script itself reserves parameters dsp.input1-7 for it's internal purposes, dsp.input0 is by default a "bypass" parameter. That's why our script's first parameter is dsp.input8
But after we export the plugin, PnS removes 7 internal parameters and our first input parameter becomes dsp.input1. Also dsp.input parameters displayed in the DAW get proper names, ranges and units (provided in our script).
This is true if you export without option "Use generic parameters". If you "check" this option upon export, you'll get the same "generic" parameter names and ranges as inside Plug'n Script ("Param 0, 1, 2"). And your first input parameter will be dsp.input8 as well. This can be useful for some compatibility reasons. Still, it's much more convenient to not use this option and get normal parameter names and ranges after export (so don't check "Use generic paramters" if you don't need it specifically).
These two parameters are linked in the skin using PARAM_LINK, something like this (slighly simplified):
- <PARAM_LINK from="dsp.input8" to="custom_param0" normalized="true" />
- <PARAM_LINK from="custom_param0" to="dsp.input8" normalized="true" />
The links are "normalized" meaning the ranges of the parameters are matched. So if one parameter has range from 0 to 1, another from 0 to 10, when we set first parameter to 0.5 another is set to 5.
But how does "custom_param" gets information about it's current name and range from the script? Well, it's also exposed to the GUI using hidden dsp.private_output params and dsp.output_strings.
Here I've loaded another script that exposes 4 input and 2 output parameters. Current number of input/output parameters is exposed using dsp.private_output1/2, and several more hidden parameters and strings are used. They transfer information to the GUI about "custom_param" attributes (min, max and default values, number of "steps", name, units, list of enumerated_values and value format).
So for providing each "custom_param" attributes Plug'n Script actually uses 8 additional parameters and strings.
Later in the skin all this information is linked to every "custom_param" like this:
- <!-- create custom_param0 -->
- <PARAM id="custom_param0" min="0" max="1" default="0" name="Param 0" />
- <!-- link values from DSP to its attributes, they will change dynamically depending on the script -->
- <PARAM_LINK from="dsp.private_output3" to="custom_param0.min"/>
- <PARAM_LINK from="dsp.private_output4" to="custom_param0.max"/>
- <PARAM_LINK from="dsp.private_output5" to="custom_param0.default"/>
- <STRING_LINK from="dsp.output_string8" to="custom_param0.name"/>
- <STRING_LINK from="dsp.output_string9" to="custom_param0.unit"/>
- <STRING_LINK from="dsp.output_string10" to="custom_param0.enum_values"/>
- <STRING_LINK from="dsp.output_string11" to="custom_param0.value_format"/>
- <!-- and finally link the value of the param itself to the dsp.param -->
- <PARAM_LINK from="dsp.input8" to="custom_param0" normalized="true" />
- <PARAM_LINK from="custom_param0" to="dsp.input8" normalized="true" enabled="false" id="backlink0" /> <!-- link back is disabled by default -->
- <PARAM_LINK from="custom_param0.capturing" to="backlink0.enabled"/>
If you want to see how it's done really (not slightly simplified like here), you can open the default skin folder and find files "mapping.inc" and "mapping_utils.inc". The same principles are used in LetiMix skin (mappings are in the file "common.inc")
This "trick" allows Plug'n Script to create individual script parameters on the fly, without the need to reload the plugin or restart the DAW. For the DAW the number of DSP parameters and their properties remain the same. And for the script-writer the parameter name and range ("custom_param0") stays the same after exporting.
Accessing parameters after export
We really don't need "custom_params" trick when our plugin is exported. But it stays there for the compatibility reasons, so that we can have the same simple .kuiml with same parameter names and it would work fine. However "custom_param" becomes a "rudiment", and after export it is wiser to use "dsp.input" params directly.
- <!-- a variable that holds param's name -->
- <VARIABLE id="GAIN" value="custom_param0" />
- <REPEAT count="($SCRIPT_EDIT_MODE$==0)">
- <!-- applied when plugin is exported -->
- <VARIABLE id="GAIN" value="dsp.input1" override="true" />
- </REPEAT>
- <!-- to check what we have -->
- <TEXT value="$GAIN$" />
- <PARAM_TEXT_CONTROL param_id="$GAIN$" />
Something like this would work fine. If you want to make it even easier to manage parameter names in KUIML, you can do it like this (works in LM_Skin):
- <VAR id="N" value="0" />
- <VAR id="PARAM_NAME" value="custom_param" />
- <IF_EXPORTED>
- <!-- when exported we use dsp.input params directly -->
- <VAR id="N" value="1" />
- <VAR id="PARAM_NAME" value="dsp.input" />
- </IF_EXPORTED>
- <VAR id="MODE" value="$PARAM_NAME$$N$"/><NEXT_N />
- <VAR id="GAIN" value="$PARAM_NAME$$N$"/><NEXT_N />
- <VAR id="CHANNELS" value="$PARAM_NAME$$N$"/><NEXT_N />
- ...
- <PARAM_TEXT_CONTROL param_id="$GAIN$" />
This would work fine both in PnS and after export.
Changing custom_param value from the script
Many of us struggle, when we try for the first time to write a GUI script that changes input parameter. Suddenly we find out that though the knob in the GUI turns, the parameter in the DSP is not updated. Don't give up!
Take a look at the last line in the parameter linking example that says:
- <PARAM_LINK from="custom_param0.capturing" to="backlink0.enabled"/>
It enables the "link back" from the "custom_param0" to "dsp.input8" only when custom_param0 is "capturing". Meaning its value is changed by user via mouse or keyboard. Why is this done that way? Why not just always keep custom_param and dsp.input param linked both ways? Blue Cat Audio explains that otherwise there is a possibility of a bad linking "loop" when you load a preset or change parameter via MIDI.
So how to do we do this then? For example, this script would only change the parameter in the GUI (custom_param0), but not the input value in the DSP (dsp.input8).
- <ACTION ... script="
- double new_gain = 2;
- custom_param0 = new_gain;
- " />
To make it work we have to add "BeginCapture" and "EndCapture" calls like this:
- <ACTION id="apply_gain" name="Apply gain" type="Script" script="
- double new_gain = 2;
- custom_param0.BeginCapture();
- custom_param0 = new_gain;
- custom_param0.EndCapture(); "
- requires="custom_param0*"
- />
Now this parameter is marked as "capturing" and its value is updated in the dsp.input parameter as well. These BeginCapture/EndCapture calls are also required for some DAWs to update parameter value or write automation. If you omit them, in some DAWs it may work, in some it will not.
Note: if you have popup errors trying to use "BeginCapture", try to explicitly add them as "REQUIRED_OBJECTS" to the main skin (XML file you're using), like this:
- <REQUIRED_OBJECTS object_ids="custom_param0.BeginCapture;custom_param0.EndCapture" />
Or, if you're modifying dsp.input8 directly from the .kuiml you may need this
- <REQUIRED_OBJECTS object_ids="dsp.input8.BeginCapture;dsp.input8.EndCapture" />
Compare the numbers
Just for fun, here's the same script in Plug'n Script (on the left) and as an exported plugin (on the right).
Compare the number of exposed parameters.
The exported plugin has only the input parameters we've added in the script (4 parameters + default bypass), and their names, ranges and units are specified. Plug'n Script has 48 generic params (by default) + 7 for internal purposes.
The exported plugin parameters have correct ranges, names and units, so mapping them to "custom_params" is done just for backward compatibility.
Conclusion
Now you know that "custom_params" are just aliases created in the skin, so you should use them wisely! The good decision would be to use "dsp.input" params directly after export, as shown in the examples above.
Good luck with your scripting!