How to add params conveniently
Download param.as and use it like that:
- #include "library/params.as"
- // create placeholders for params indexes
- int ENABLE, MODE, THRESHOLD, MIX, GR, OUT_MODE, OUT_LEVEL, FILENAME, MYDATA1;
- // add input params
- ENABLE = ip("Enable", "Off;On");
- MODE = ip("Mode", "Simple;Classic;1176;Opto", "Classic");
- THRESHOLD = ip("Threshold", "dB", -40, 0, -18, ".1");
- MIX = ip("Wet/Dry");
- // add output params
- GR = op("Gain Reduction", "db", 0, 10);
- OUT_MODE = op("Out mode", "Simple;Classic;1176;Opto");
- OUT_LEVEL = op("Out level");
- // add input strings
- FILENAME = ips("Filename");
- // add output strings (with max length)
- MYDATA1 = ops("MyData1", 256);
- }
- double threshold = IP[THRESHOLD]; // IP is alias for inputParameters
- string mystring = IPS[FILENAME]; // IPS is alias for inputStrings
- }
- OP[GR] = rand(0,10); // OP is alias for outputParameters
- OPS[MYDATA1] = someString; // OPS is alias for outputStrings
- }
Short explanation
Using params library we can add params inside initialize function in a very compact and easy to read and maintain manner.
The format for adding params is:
- "name", ["units", min, max, default_value, "format", [steps]]
- "name", "value1;value2;value3", ["default_value"] // for enumerated params
We can also use shortcuts IP, OP, IPS, OPS instead of long names inputParameters, outputParameters, inputStrings, outputStrings to make our code more compact.
Long explanation
The default way of adding input and output parameters in Plug'n Script is using arrays:
- // adding input parameters
- array<string> inputParametersNames={"Gain", "Mix", "Mode"};
- array<string> inputParametersUnits={"dB", "%", ""};
- array<double> inputParametersMin={-20, 0, 0};
- array<double> inputParametersMax={20, 100, 2};
- array<double> inputParametersDefault={0, 50, 1};
- array<string> inputParametersFormats={"+.2",".0", ""}; // value formatting like C "printf"
- array<string> inputParametersEnums={"", "", "Vintage;Neutral;Modern"};
- array<int> inputParametersSteps={-1, 11, 3}; // positions count for auto-layout knobs
- array<double> inputParameters(inputParametersNames.length); // number of input params
To access these parameters later we can write
- double gaindb = inputParameters[0];
- double mix = inputParameters[1];
- int mode = int(inputParameters[2] + 0.5); // rounding positive double to int
It would be easier to read and write code if we write names instead of indexes (0,1,2...). We can assign them to constants like this:
- const int GAIN = 0;
- const int MIX = 1;
- const int MODE = 2;
Or better use Enums, which will do the same, but are easier to setup and maintain (especially when you want to add, re-order or remove values).
- enum inputParamsIndexes{
- GAIN,
- MIX,
- MODE
- }
Now we can access inputParameters like this:
- double gaindb = inputParameters[GAIN];
- double mix = inputParameters[MIX];
- int mode = int(inputParameters[MODE] + 0.5); // rounding positive double to int
Cool! If we have a simple script with a few params, we can use it like that.
Disadvantages of default method
However, using this array-based way of adding params has some disadvantages.
- it can be hard to remember all these array names and syntax (so more likely you'll copy-paste from script to script)
- when you need just a couple of input/output params, you have to add many lines of code
- it's difficult to insert a new param in the middle, or change the order of params, or temporarily remove a param
- it all grows into a big mess when you have a lot of params
For example, this is a beginning of one of my first projects (tone-generator script) using less than 30 input params. The section of adding params takes >130 lines of code and is very hard to read and manage.
- // Input Parameters Shortcuts
- const int IP_ONOFF = 0;
- const int IP_GEN_MODE = 1;
- const int IP_STATIC_LEVEL = 2;
- const int IP_FREQ_MODE = 3;
- const int IP_FREQ_PRESETS = 4;
- const int IP_FREQ_FREE = 5;
- const int IP_FREQ_SAMPLES = 6;
- const int IP_AR_HIGH_LEVEL = 7;
- const int IP_AR_HIGH_DUR = 8;
- const int IP_AR_HIGH_DUR_MS = 9;
- const int IP_AR_LOW_LEVEL = 10;
- const int IP_AR_LOW_DUR = 11;
- const int IP_TR_BOTTOM_LEVEL = 12;
- const int IP_TR_TOP_LEVEL = 13;
- const int IP_TR_STEP_DUR = 14;
- const int IP_TR_PAUSE_DUR = 15;
- const int IP_FR_START_FREQ = 16;
- const int IP_FR_END_FREQ = 17;
- const int IP_FR_STEP_DUR = 18;
- const int IP_FR_PAUSE_DUR = 19;
- const int IP_ST_INPUT_TRIM = 20;
- const int IP_ST_FORM = 21;
- const int IP_ST_PHASE = 22;
- const int IP_SW_START_FREQ = 23;
- const int IP_SW_END_FREQ = 24;
- const int IP_SW_LENGTH = 25;
- const int IP_SW_PAUSE = 26;
- const int IP_SW_MODE = 27;
- // input parameters
- // NAMES
- array<string> inputParametersNames={"Enable", "Gen Mode", "Static Level",
- "Stat Freq Mode", "Stat Freq Presets", "Stat Frequency", "Stat Freq Samples",
- "AR High Level", "AR High Time", "AR High Time ms", "AR Low Level", "AR Low Time",
- "TR Min Level", "TR Max Level", "TR Step Duration", "TR Pause Steps",
- "FR Start freq", "FR End freq", "FR Step Duration", "FR Pause Steps",
- "ST Input Gain", "ST Form", "ST Phase",
- "SW Start Freq", "SW End Freq", "SW Length", "SW Pause", "SW Mode"
- };
- array<double> inputParameters(inputParametersNames.length);
- // UNITS
- array<string> inputParametersUnits={"", "", "dB",
- "", "Hz", "Hz", "smp",
- "dB", "", "ms", "dB", "s",
- "dB", "dB", "s", "steps",
- "Hz", "Hz", "s", "steps",
- "dB", "", "deg",
- "Hz", "Hz", "smp", "swps", ""
- };
- // ENUMS
- array<string> inputParametersEnums={"Off;On", "Static;Att / Rel;Thr / Ratio;Freq steps;Freq sweep", "",
- "Freq set;Frequency", join(freq_presets, ";"), "", "",
- "", join(allowed_durs_high, ";"), "", "", join(allowed_durs_low, ";"),
- "", "", join(allowed_durs_thrrat_steps, ";"), "",
- join(fr_freqs_list, ";"), join(fr_freqs_list, ";"), join(allowed_durs_freq_steps, ";"), "",
- "", "Sinus;Square;Saw;Rev Saw;Triangle;Brown noise;Pink noise;White noise", "",
- "", "", "" /* join(allowed_sweep_lengths, ";")*/, "", "linear;log"
- };
- // FORMAT
- array<string> inputParametersFormats={".0", ".0", "+.1",
- ".0", ".0", ".1", ".0",
- ".1", ".0", ".2", ".1", ".0",
- ".0", ".0", ".0", ".0",
- ".0", ".0", ".0", ".0",
- ".1", ".0", ".0",
- ".5", ".0", ".0", ".0", ".0"
- };
- // MIN
- array<double> inputParametersMin={0, MODE_STATIC, ST_FLOOR_DB,
- 0, 0, 0, 4,
- AR_FLOOR_DB, 0, 0, AR_FLOOR_DB, 0,
- TR_FLOOR_DB, TR_FLOOR_DB, 0, 1,
- 0, 0, 0, 0,
- MIN_input_trim_LEVEL, FORM_SINUS, -360,
- SWEEP_MIN_FREQ, SWEEP_MIN_FREQ, 1, 0, SWEEP_MODE_LINEAR
- };
- // MAX
- array<double> inputParametersMax={1, MODE_FREQ_SWEEP, ST_CEIL_DB,
- 1, freq_presets.length-1, 384000, 40000,
- AR_CEIL_DB, allowed_durs_high.length-1, 100, AR_CEIL_DB, allowed_durs_low.length-1,
- TR_CEIL_DB, TR_CEIL_DB, allowed_durs_thrrat_steps.length-1, TR_MAX_PAUSE_DURATION,
- fr_freqs_list.length-1, fr_freqs_list.length-1, allowed_durs_freq_steps.length-1, FR_MAX_PAUSE_DURATION,
- MAX_input_trim_LEVEL, FORM_WHITENOISE, 360,
- MAX_FREQUENCY, MAX_FREQUENCY, 131072 /* allowed_sweep_lengths.length-1 */, 16, SWEEP_MODE_LOG
- };
- // STEPS
- array<int> inputParametersSteps={2, roundDoubleToInt(inputParametersMax[IP_GEN_MODE]-inputParametersMin[IP_GEN_MODE]) + 1, ST_CEIL_DB-ST_FLOOR_DB+1,
- 2, freq_presets.length, 10, roundDoubleToInt(inputParametersMax[IP_FREQ_SAMPLES])/4,
- AR_CEIL_DB-AR_FLOOR_DB+1, allowed_durs_high.length, roundDoubleToInt(inputParametersMax[IP_AR_HIGH_DUR_MS])+1, AR_CEIL_DB-AR_FLOOR_DB+1, allowed_durs_low.length,
- TR_CEIL_DB-TR_FLOOR_DB+1, TR_CEIL_DB-TR_FLOOR_DB+1, allowed_durs_thrrat_steps.length, roundDoubleToInt(inputParametersMax[IP_TR_PAUSE_DUR])+1,
- fr_freqs_list.length, fr_freqs_list.length, allowed_durs_freq_steps.length, roundDoubleToInt(inputParametersMax[IP_FR_PAUSE_DUR])+1,
- roundDoubleToInt(inputParametersMax[IP_ST_INPUT_TRIM] - inputParametersMin[IP_ST_INPUT_TRIM]+1), roundDoubleToInt(inputParametersMax[IP_ST_FORM] - inputParametersMin[IP_ST_FORM]+1), 360*2+1,
- 0, 0, 0/* allowed_sweep_lengths.length*/ , roundDoubleToInt(inputParametersMax[IP_SW_PAUSE]+1), 2
- };
- // DEFAULT
- array<double> inputParametersDefault={1, MODE_DEFAULT, -12,
- 0, roundDoubleToInt(freq_presets.length-3), 3300, 4,
- -5, floor(allowed_durs_high.length / 2 ), 50, -25, floor(allowed_durs_low.length/2-1),
- -60, TR_CEIL_DB, floor(allowed_durs_thrrat_steps.length / 4), 4,
- 0, fr_freqs_list.length-1, floor(allowed_durs_freq_steps.length / 4), 4,
- 0, FORM_SINUS, 0,
- SWEEP_MIN_FREQ, MAX_FREQUENCY, 2048, 0, SWEEP_MODE_LOG
- };
- // output parameters
- const int OP_MODE = 0;
- const int OP_SAMPLERATE = 1;
- const int OP_FREQ = 2;
- const int OP_LEVEL = 3;
- const int OP_PERIOD_LENGTH_SAMPLES = 4;
- array<string> outputParametersNames={"Mode", "SampleRate",
- "Frequency", "Level", "Period length"};
- array<double> outputParameters(outputParametersNames.length);
- array<string> outputParametersUnits={"", "",
- "Hz", "dB", "smp"};
- array<string> outputParametersEnums={"STATIC;ATT-RELEASE;THR-RATIO;FREQ RANGE;FREQ SWEEP", "",
- "", "", ""};
- array<string> outputParametersFormats={"", ".0",
- ".1", ".1", ".0"};
- array<double> outputParametersMin={MODE_STATIC, 1,
- 0, -300, 0};
- array<double> outputParametersMax={MODE_FREQ_SWEEP, 384000,
- 192000, MAX_LEVEL, 384000};
- array<double> outputParametersDefault={MODE_ATT_REL, 1,
- 0, 0, 0};
If there's a mistake somewhere (say, a missed comma), the script may even continue to work, but array values will be shifted, which is sometimes hard to notice.
So, wouldn't it be nice to make things clean and simple?
Let's use initialize
Inside initialize we can still add and update parameters. So let's write a function that will get a bunch of param attributes and put it into arrays, something like that:
- // add (or update) input parameter
- void ip(int index, string name, string units, double min, double max = 0, double def_no = 0, string vf = ".1", int steps = 0, string enums = "") {
- ...
- inputParametersNames[index] = name;
- inputParametersUnits[index] = units;
- ...
- }
We can also add another function with the same name but other parameters to add "enumerated" parameters.
- // add or update enumerated input parameter
- void ip(int index, string name, string enums = "", string def_value = ""){
- // do some parsing to find number of enums, default enum number
- ...
- // call the main ip function to add param
- ip(index, name, "", 0, max, default_value, "", max+1, enums);
- }
Before using inputParameters arrays we have to create them globally. And there will be more functions to add output params and strings, so let's save it all into "params.as" file and include it on top of our script.
- #include "library/params.as"
- enum inputParamsIndexes{
- GAIN,
- MIX,
- MODE
- }
- ip(GAIN, "Gain", "dB", -20, 20, 0, "+.2");
- ip(MIX, "Mix", "%", 0, 100, 50, ".0", 11);
- ip(MODE, "Vintage;Neutral;Modern", "Neutral"); // enum_list, default_value
- }
This is already very cool — light and easy to write and read.
Though using constants for index names might be slightly inconvenient, because:
- Order of function calls in initialize may not match the order of params, which is visually not clear.
- If you want to disable or remove params you have to update them both in enums list and in initialize, which is twice as much work.
So, let's improve it further: index of added param can be generated and returned from function, like this:
- // placeholders for params indexes
- int GAIN, MIX, MODE;
- // name, [ units, min, max, default, format, steps ]
- GAIN = ip("Gain", "dB", -20, 20, 0, "+.2");
- MIX = ip("Mix", "%", 0, 100, 50, ".0", 11);
- MODE = ip("Vintage;Neutral;Modern", "Neutral"); // enum_list, default_value
- }
Now it's easy to re-order, remove or temporarily disable params. All param info is kept in a single line. Beautiful!
Here are examples on how to add output params, input and output strings. Also if you like to make things shorter, with this library included you can use aliases, like IP[GAIN] instead of inputParameters[GAIN].
What about the GUI?
We can make our life easier in the KUIML as well.
By default, when we add params in the Plug'n Script DSP they are exposed to the GUI with generic names like dsp.input8 (for exported plugin they start from dsp.input1), dsp.output0, etc. Generic params have generic ranges from 0 to 1. And inside skin they are mapped into custom_param0, custom_out_param0 with proper param names and ranges.
So when writing kuiml file for our script we usually use these aliased params, like this:
- <PARAM_TEXT_CONTROL param_id="custom_param0" />
- <PARAM_TEXT param_id="custom_out_param0" />
It would also be nice to use names instead of these numbered params.
We can do it so by keeping param names in KUIML VARIABLEs.
and later use it like that:
- <PARAM_TEXT_CONTROL param_id="$GAIN$" />
- <CUS_BLUE_KNOB param_id="$MIX$" />
- <PARAM_TEXT param_id="$MODE$">
- <INVISIBLE_PARAM_MENU_BUTTON param_id="$MODE$" width="100%" height="100%" cursor="system::hand" />
- </PARAM_TEXT>
So, it's much more readable already!
But what if we reorder params in DSP? Do we have to rewrite every VARIABLE in KUIML?
We can get away from it by using the incremented variable index, like this:
Good, but let's make things more visually appealing. If you're not using Letimix Skin yet, first add this to your kuiml (in Letimix skin these elements are added already):
Now we can write it shorter:
- <VAR id="N" value="0" />
- <VAR id="GAIN" value="custom_param$N$" /><NEXT_N />
- <VAR id="MIX" value="custom_param$N$" /><NEXT_N />
- <VAR id="MODE" value="custom_param$N$" /><NEXT_N />
Now if we re-order params in DSP we just move the line of code in KUIML and that's it! Beautiful!
For output params we can use the same principle, just use custom_out_param$N$ instead.
Input and output strings
Custom input strings are exposed as dsp.input_string1, dsp_input_string2, etc. (String number 0 is holding the path to current script). In the skin these string names are kept in VARIABLEs named script_input_string0, script_input_string1, etc.
- <TEXT_EDIT_BOX string_id="$script_input_string0$" />
- <TEXT_EDIT_BOX string_id="$script_input_string1$" />
Custom output strings are exposed as dsp.output_string360 and further, but when exported this name would change to something like dsp.output_stringX. Where X is 8 + ($PLUGIN_INPUT_PARAMS_COUNT$-1)*4 + $PLUGIN_OUTPUT_PARAMS_COUNT$*4 + 16 + 16 (if you wonder what it all means: the number of params + 16 input string names, 16 output string names, then goes output string data that we need).
It makes using dsp.output_stringX more complicated. However, proper output string names are also kept in VARIABLEs like script_output_string0, script_output_string1, that we can safely use.
It's probably easier to keep things like that, however, if you really want to give your input and output string names, you can try the similar approach.
- <!-- custom input strings start index -->
- <VAR id="N" value="1" />
- <VAR id="MY_IN_STRING_A" value="dsp.input_string$N$" /><NEXT_N />
- <VAR id="MY_IN_STRING_B" value="dsp.input_string$N$" /><NEXT_N />
- <TEXT_EDIT_BOX string_id="$MY_IN_STRING_A$" />
- <TEXT_EDIT_BOX string_id="$MY_IN_STRING_B$" />
- <!-- custom output strings start index -->
- <VAR id="N" formula="if ($SCRIPT_EDIT_MODE$, 360, 8 + ($PLUGIN_INPUT_PARAMS_COUNT$-1)*4 + $PLUGIN_OUTPUT_PARAMS_COUNT$*4 + 16 + 16)" />
- <VAR id="MY_OUT_STRING_A" value="dsp.output_string$N$" /><NEXT_N />
- <VAR id="MY_OUT_STRING_B" value="dsp.output_string$N$" /><NEXT_N />
- <TEXT string_id="$MY_OUT_STRING_A$" />
- <TEXT string_id="$MY_OUT_STRING_B$" />
That's all for now. You can write your ideas on adding params in PnS in the comment below.