Managing plugin parameters in AudioPluginInstance

Thanks t0m for your prompt answer, the GenericAudioProcessorEditor has indeed some inspirational snippets.
I have been doing some trials with no luck so far. I feel my problem is a bit upstream because I don’t have a way (and I am wondering if there is one) to read the actual available values (e.g. {“sine”, “square”, “noise”} for a waveform discrete parameter) from the AudioPluginInstance. After I load a 3rd party plugin into an AudioPluginInstance, I can only read the 0 - 1 value of each parameter via AudioPluginInstance::getParams().
Also, I would really love to understand where/when the actual conversion from 0 -1 to the available discrete parameter value exactly takes place in the case of an AudioPluginInstance (loaded with a 3rd party VST).

any suggestions?

Can you not call the getText (float normValue, int) function on each of the parameters returned from getParameters ()?. You’ll need to pass in the normalised value you already have.

Get labels for the values a parameter can take:

Handling a new parameter value and mapping it to a label:

Thanks a lot @alibarker1 for your suggestion.
I have tried that, but in my case returns only the value back as a string. I guess it happens when the getParameters () is called from an AudioPluginInstance loaded with a 3rd party plugin? (see previous answer from t0m below).

If you load a VST there is no way (unless you parse an accompanying VSTXML file, which is rare and, as of recently, undocumented) to determine if a parameter is discrete or not. AUs and VST3 do provide this information.

Did you mean I should somehow implement my own version of getText (float normValue, int)?

The GenericAudioProcessorEditor attempts to extract the maximum amount of information about the parameters possible. Since VST2s cannot provide information about the number of steps they have (it’s not available via the VST2 API) the best you can do is to look at the text value provided by the plug-in for each parameter value. You can see this text displayed in the Slider boxes in the GUI. If, however, the plug-in chooses not to provide any meaningful text there is nothing you can do.

Thanks for your answer t0m, but I am still struggling with this issue.

I think if I post a bit of code it can help you helping me. I am doing some trials in a command line application. As you can see, I am not using any editor and indeed the flag I tried to put in the GenericAudioProcessorEditor constructor does not fire. Though I’ve noticed that some parameters’ attributes are initialized by the VSTPluginInstance constructor. To play around a bit I hardcoded a parameter to be boolean and have 2 steps .I commented the outputs I get form cout.

#include "../JuceLibraryCode/JuceHeader.h"
#include <array>
using namespace std;
typedef std::vector<std::pair<int, float>>  PluginPatch;

int main ()
{
	MessageManager::getInstance(); //to avoid leaks

AudioPluginInstance*  plugin;
PluginPatch   params;
PluginPatch          overridenParameters;
OwnedArray<PluginDescription> pluginDescriptions;
KnownPluginList pluginList;
AudioPluginFormatManager pluginFormatManager;
String path = "/path/to/my/VST/TAL-NoiseMaker.vst";

pluginFormatManager.addDefaultFormats();
for (int i = pluginFormatManager.getNumFormats(); --i >= 0;)
{
	pluginList.scanAndAddFile (String (path),
							   true,
							   pluginDescriptions,
							   *pluginFormatManager.getFormat(i)); //fill in plugin description
}
String errorMessage;
plugin = pluginFormatManager.createPluginInstance (*pluginDescriptions[0],
												   44100,
												   512,
												   errorMessage); //load TAL-NoiseMaker.vst into my AudioPluginInstance

jassert (pluginDescriptions.size() > 0);

//At this point I only have the AudioPluginInstance of TAL-NoiseMaker.vst.
//I have no editor, as I want to process the audio and save each sound offline (by calling the prepareToPlay and processBlock functions later with a given MIDI information).

//some flags and printing trials
if (plugin != nullptr)
{
	const OwnedArray<AudioProcessorParameter>& processorParams = plugin->getParameters();//get the parameters as an AudioProcessorParameter array
	for (int i=0 ; i< plugin->juce::AudioProcessor::getNumParameters() ; i++){

		juce::StringArray pstring = processorParams[i]->getAllValueStrings();
		
		//i = 45 correspond to lfosync1, a boolean parameter of Tal-Noisemaker.vst.
		//I modified the constructor of VSTPluginInstance in juce_VSTPluginFormat.cpp hardcoding the isBoolean and numSteps attributes to 1 for i = 45
		if (i ==45) {
			processorParams[i]->setValue(0.51);
			cout << processorParams[i]->getName(100) << " = " << processorParams[i]->getText(processorParams[i]->getValue(), 100) << endl; //prints "lfo1sync = On"
		}
		if (i ==45) {
			processorParams[i]->setValue(0.49);
			cout << processorParams[i]->getName(100) << " = " << processorParams[i]->getText(processorParams[i]->getValue(), 100) << endl; //prints "lfo1sync = Off"
		} //without modifying the constructor of VSTPluginInstance it would print the float value.
		if (i ==1) {
			processorParams[i]->setValue(0.51);
			cout << processorParams[i]->getName(100) << " = " << processorParams[i]->getText(processorParams[i]->getValue(), 100) << endl; //prints "volume = 0.51"
		}
		if (i ==45) {
			cout << processorParams[i]->getName(100) << " getAllValueString() k = " << pstring.size()<< endl; //prints 0
		}
	}
}

delete plugin;
DeletedAtShutdown::deleteAll();
MessageManager::deleteInstance();


return 0;

}

Maybe I could hardcode the parameters’ attributes I know to be discrete/boolean and their number of steps in the VSTPluginInstance constructor, it does not seem a very neat solution. In addition, I am not sure whether those hardcoded parameter will be correctly interpreted/mapped by the AudioProcessor of the TAL-Noisemaker.vst plugin (loaded as AudioPluginInstance).

Hopes this helps in helping me, I am stuck on these since 3 days already :confused:

If you get values like “lfo1sync = 0.49” then I’m afraid there is no alternative approach you can use to determine the number of steps; the only mechanism available to the plug-in to communicate labels for specific parameter values is via getText. As for how the plug-in interprets different floating point values - it’s up to the plug-in!

The plug-in’s own GUI will obviously feed in the right numbers when you interact with it. You could try playing with the GUI and recording the parameter changes to get a feel for what values are appropriate.

It’s a long time ago when i wrote the plugin. All values are in the range from 0…1 and divided to the number of discrete steps. Booleans or switches maybe are true when > 0. You maybe find out more if you switch to the generic parameter view in your favourite host.

Thanks t0m for your help.
Your answer clarifies a lot. So the interpretation of the floating point values (mapping them to a discrete set of choice) happens inside the processor of the VST and I cannot do anything about it (not even with the AudioProcessor I get with the AudioPluginInstance after loading), correct?

Then I guess there is nothing I can do other than “measure” when the change happens via the GUI. I thought maybe JUCE was taking care of this “mapping” so I could edit the mapping function but it’s not the case. It happens internally in the 3rd party VST processor (correct?).

I tested in logic for the values of the discrete parameter “osc2waveform” (5 values in the GUI) and indeed it changes at non-equally spaced intervals of the float value (see image below).

It looks like I’d better switch to a VST3 plug-in, which indeed provides a much richer rose of information when using getText() (see image below for the output of getText() after loading a VST3 plug-in in the AudioPluginInstance)
image

I will check whether there is a VST3 version of the plugin available, I really like the Noisemaker synth!

Oh wow, great to hear from the creator himself!

I was indeed expecting that (seemed to me the most logical design) but I am afraid something different is happening, both when loading Noisemaker with JUCE and in LogicX…please check the results of a test I did in LogicX in this reply in this thread (look at the attached image).

The issue illustrated by the image in the post above is the reason why I was wondering if there was a way using JUCE to edit the float mapping. Or at least to read the actual parameters as they are displayed in the GUI (like “saw” “sine” etc… for waveform parameters) but it looks that is not possible and I can only get/set the float value.

Interesting, are you maybe aware of such an option in LogicX?

Last but not least, I really like Noisemaker such “big” sounds for a digital synth. Great work!
I hope I can solve this issue to keep using it in this project.

A small update: I could reproduce the behaviour reported here for the mapping between floating point values of a plugin parameter, to a set of discrete choices (e.g. waveforms) for other synthesizer plugins. Does someone have any idea of why the 0-1 range is not divided by the number of discrete steps?What is going on?

a     |     b     |     c     |     d     |     e
0     |   0.25    |    0.5    |   0.75    |    1.0
    0.125       0.375       0.625       0.875
1 Like

This maybe helps:

int calcComboBoxValue(float value, int param)
{
    int numItems = this->getNumComboBoxItems(param);
    return (int)floorf(value * (numItems - 1.0f) + 1.0f + 0.5f);
}


float calcComboBoxValueNormalized(float value, int param)
{
    int numItems = this->getNumComboBoxItems(param);
    return (value - 1.0f) / (numItems - 1.0f);
}

int getNumComboBoxItems(int param)
{
    int numItems = 1;

    switch(param)
    {
    case VOICES: numItems = 6; break;
    case PORTAMENTOMODE: numItems = 3; break;
    case LFO1DESTINATION: numItems = 8; break;
    case LFO2DESTINATION: numItems = 8; break;
    case FREEADDESTINATION: numItems = 6; break;
    case FILTERTYPE: numItems = 12; break;
    case OSC1WAVEFORM: numItems = 3; break;
    case OSC2WAVEFORM: numItems = 5; break;
    case ENVELOPEEDITORDEST1: numItems = 8; break;
    case ENVELOPEEDITORSPEED: numItems = 6; break;
    }

    return numItems;
}

Thanks t0m for this answer, I indeed also noticed that pattern shortly after posting my previous message but I was afraid I had to then to check manually whether all the parameters were also mapped with the same criterion. But @kunz 's answer solves all my problems!
Thanks A LOT for your help.

Great @kunz thank you so much for sharing this!It clarifies everything in detail.

Just a curiosity, is it common practice in plugin development to use this parameter mapping?I see that this way you can map N parameters with only N-1 threshold values for the switch in the combobox but is that why you used this mapping instead of equally spaced intervals?or is there some other reason I am missing?
thanks a lot, bottom line is I am glad I can now keep using Noisemaker

I’m not sure why i did this that way. It just happened. Never thought that someone will come up with this about 9 years later :slight_smile:

Fair enough :slight_smile:

I have some related questions. I’m using RenderMan to load the Serum VST and then export audio via python/command line. I’ve used the code in this thread to try to enumerate all VST parameters which I can set. I’ve also tried the XML code here How to get a grouped representation xml with an audio processor value tree state All options show a list of a parameters that’s shorter than I expect. The XML doesn’t look good on the forum so I’m uploading a simple numeric list instead

for (int i = 0; i < plugin->getNumParameters(); i++) {
    std::cout << i << ": " << plugin.get()->getParameterName(i) << std::endl;
}

:

Serum Parameters

0: MasterVol: 0.7
1: A Vol: 0.75
2: A Pan: 0.100877
3: A Octave: 0.425781
4: A Semi: 0.5
5: A Fine: 0.5
6: A Unison: 0
7: A UniDet: 0.5
8: A UniBlend: 0.75
9: A Warp: 0.618421
10: A CoarsePit: 0.5
11: A WTPos: 0
12: A RandPhase: 1
13: A Phase: 0.5
14: B Vol: 0.679825
15: B Pan: 0.5
16: B Octave: 0.28125
17: B Semi: 0.5
18: B Fine: 0.5
19: B Unison: 0.4
20: B UniDet: 0.245614
21: B UniBlend: 0.75
22: B Warp: 0
23: B CoarsePit: 0.5
24: B WTPos: 0
25: B RandPhase: 1
26: B Phase: 0.5
27: Noise Level: 0
28: Noise Pitch: 0.5
29: Noise Fine: 0.5
30: Noise Pan: 0.5
31: Noise RandPhase: 0
32: Noise Phase: 0
33: Sub Osc Level: 0.653509
34: Sub Osc Pan: 0.5
35: Env1 Atk: 0.11
36: Env1 Hold: 0
37: Env1 Dec: 0.570271
38: Env1 Sus: 0.44412
39: Env1 Rel: 0.342128
40: OscA>Fil: 1
41: OscB>Fil: 1
42: OscN>Fil: 0
43: OscS>Fil: 0
44: Fil Type: 0.0337079
45: Fil Cutoff: 0.337719
46: Fil Reso: 0.1
47: Fil Driv: 0
48: Fil Var: 0
49: Fil Mix: 1
50: Fil Stereo: 0.5
51: Env2 Atk: 0.11
52: Env2 Hld: 0
53: Env2 Dec: 0.530929
54: Env2 Sus: 0.0789474
55: Env2 Rel: 0.329035
56: Env3 Atk: 0.11
57: Env3 Hld: 0
58: Env3 Dec: 0.197368
59: Env3 Sus: 0
60: Env3 Rel: 0.215
61: LFO1 Rate: 0.5
62: LFO2 Rate: 0.5
63: LFO3 Rate: 0.5
64: LFO4 Rate: 0.5
65: PortTime: 0
66: PortCurve: 0.5
67: Chaos1 BPM: 0
68: Chaos2 BPM: 0
69: Chaos1 Rate: 0.251189
70: Chaos2 Rate: 0.251189
71: A curve1: 0.4995
72: D curve1: 0.666
73: R curve1: 0.666
74: A curve2: 0.4995
75: D curve2: 0.791
76: R curve2: 0.666
77: A curve3: 0.4995
78: D curve3: 0.666
79: R curve3: 0.666
80: Mast.Tun: 0.5
81: Verb Wet: 0.2
82: VerbSize: 0.35
83: Decay: 0.35
84: VerbLoCt: 0
85: Spin Rate: 0.25
86: VerbHiCt: 0.35
87: Spin Depth: 0.2
88: EQ FrqL: 0.333
89: EQ FrqH: 0.666
90: EQ Q L: 0.6
91: EQ Q H: 0.6
92: EQ VolL: 0.5
93: EQ VolH: 0.5
94: EQ TypL: 0
95: EQ TypH: 0
96: Dist_Wet: 0.666667
97: Dist_Drv: 0.894737
98: Dist_L/B/H: 0
99: Dist_Mode: 0
100: Dist_Freq: 0.5
101: Dist_BW: 0.5
102: Dist_PrePost: 0
103: Flg_Wet: 1
104: Flg_BPM_Sync: 0
105: Flg_Rate: 0.25
106: Flg_Dep: 1
107: Flg_Feed: 0.5
108: Flg_Stereo: 0.5
109: Phs_Wet: 1
110: Phs_BPM_Sync: 0
111: Phs_Rate: 0.25
112: Phs_Dpth: 0.5
113: Phs_Frq: 0.5
114: Phs_Feed: 0.8
115: Phs_Stereo: 0.5
116: Cho_Wet: 0.337719
117: Cho_BPM_Sync: 0
118: Cho_Rate: 0.179825
119: Cho_Dly: 0.214912
120: Cho_Dly2: 0
121: Cho_Dep: 1
122: Cho_Feed: 0.1
123: Cho_Filt: 0.5
124: Dly_Wet: 0.3
125: Dly_Freq: 0.5
126: Dly_BW: 0.8
127: Dly_BPM_Sync: 1
128: Dly_Link: 0
129: Dly_TimL: 0.625
130: Dly_TimR: 0.625
131: Dly_Mode: 0
132: Dly_Feed: 0.4
133: Dly_Off L: 0.5
134: Dly_Off R: 0.5
135: Cmp_Thr: 0.5
136: Cmp_Rat: 0.75
137: Cmp_Att: 0.3
138: Cmp_Rel: 0.3
139: CmpGain: 0
140: CmpMBnd: 0
141: FX Fil Wet: 1
142: FX Fil Type: 0
143: FX Fil Freq: 0.5
144: FX Fil Reso: 0
145: FX Fil Drive: 0
146: FX Fil Var: 0
147: Hyp_Wet: 0.5
148: Hyp_Rate: 0.4
149: Hyp_Detune: 0.25
150: Hyp_Unison: 0.571429
151: Hyp_Retrig: 0
152: HypDim_Size: 0.5
153: HypDim_Mix: 0
154: Dist Enable: 1
155: Flg Enable: 0
156: Phs Enable: 0
157: Cho Enable: 1
158: Dly Enable: 0
159: Comp Enable: 0
160: Rev Enable: 0
161: EQ Enable: 0
162: FX Fil Enable: 0
163: Hyp Enable: 0
164: OscAPitchTrack: 1
165: OscBPitchTrack: 1
166: Bend U: 0.541667
167: Bend D: 0.458333
168: WarpOscA: 0.782609
169: WarpOscB: 0
170: SubOscShape: 0
171: SubOscOctave: 0.25
172: A Uni LR: 1
173: B Uni LR: 1
174: A Uni Warp: 0.5
175: B Uni Warp: 0.5
176: A Uni WTPos: 0.5
177: B Uni WTPos: 0.5
178: A Uni Stack: 0
179: B Uni Stack: 0
180: Mod 1 amt: 0.594298
181: Mod 1 out: 0
182: Mod 2 amt: 0.671053
183: Mod 2 out: 0.495
184: Mod 3 amt: 0.619141
185: Mod 3 out: 0.895
186: Mod 4 amt: 0.5
187: Mod 4 out: 1
188: Mod 5 amt: 0.5
189: Mod 5 out: 1
190: Mod 6 amt: 0.5
191: Mod 6 out: 1
192: Mod 7 amt: 0.5
193: Mod 8 out: 1
194: Mod 8 amt: 0.5
195: Mod 8 out: 1
196: Mod 9 amt: 0.5
197: Mod 9 out: 1
198: Mod10 amt: 0.5
199: Mod10 out: 1
200: Mod11 amt: 0.5
201: Mod11 out: 1
202: Mod12 amt: 0.5
203: Mod12 out: 1
204: Mod13 amt: 0.5
205: Mod13 out: 1
206: Mod14 amt: 0.5
207: Mod14 out: 1
208: Mod15 amt: 0.5
209: Mod15 out: 1
210: Mod16 amt: 0.5
211: Mod16 out: 1
212: Osc A On: 1
213: Osc B On: 1
214: Osc N On: 1
215: Osc S On: 1
216: Filter On: 1
217: Mod Wheel: 0
218: Macro 1: 0
219: Macro 2: 0
220: Macro 3: 0
221: Macro 4: 0
222: Amp.: 0.5
223: LFO1 smooth: 0
224: LFO2 smooth: 0
225: LFO3 smooth: 0
226: LFO4 smooth: 0
227: Pitch Bend: 0.5
228: Mod17 amt: 0.5
229: Mod17 out: 1
230: Mod18 amt: 0.5
231: Mod18 out: 1
232: Mod19 amt: 0.5
233: Mod19 out: 1
234: Mod20 amt: 0.5
235: Mod20 out: 1
236: Mod21 amt: 0.5
237: Mod21 out: 1
238: Mod22 amt: 0.5
239: Mod22 out: 1
240: Mod23 amt: 0.5
241: Mod23 out: 1
242: Mod24 amt: 0.5
243: Mod24 out: 1
244: Mod25 amt: 0.5
245: Mod25 out: 1
246: Mod26 amt: 0.5
247: Mod26 out: 1
248: Mod27 amt: 0.5
249: Mod27 out: 1
250: Mod28 amt: 0.5
251: Mod28 out: 1
252: Mod29 amt: 0.5
253: Mod29 out: 1
254: Mod30 amt: 0.5
255: Mod30 out: 1
256: Mod31 amt: 0.5
257: Mod31 out: 1
258: Mod32 amt: 0.5
259: Mod32 out: 1
260: LFO5 Rate: 0.5
261: LFO6 Rate: 0.5
262: LFO7 Rate: 0.5
263: LFO8 Rate: 0.5
264: LFO5 smooth: 0
265: LFO6 smooth: 0
266: LFO7 smooth: 0
267: LFO8 smooth: 0
268: FX Fil Pan: 0.5
269: Comp_Wet: 1
270: CompMB L: 0.5
271: CompMB M: 0.5
272: CompMB H: 0.5

Note that for the XML’s “Mod 1 amt”, if you go to the MATRIX panel of Serum, the AMOUNT parameter in the third column is non-default. For the XML’s “Mod 1 out”, which is non default, the corresponding thing in Serum is the OUTPUT column all the way on the right. Great, but how do I access parameters for things like SOURCE and DESTINATION? I’ve read the XML closely and don’t see them. Also, the Oscillators section has a dropdown menu for selecting the filepath of a wavetable. Could it be possible to get/set those with JUCE? What more can I debug? Thank you!

Probably not a parameter(saved in data chunk) hence only accessible through the UI

Hi guys!
I’m pretty noob in VST programs and plugin stuff,
I’m working on a deep learning project and want to generate real dataset based on TAL NoiseMaker plugin.
I tried to follow this thread, but I got confused a bit.

Is there a simple API in Python to generate audio from XML preset file? I need thousands of those.

Thanks,
Or Rimoch