Managing plugin parameters in AudioPluginInstance


#1

I am creating a plugin instance of the TAL-Noisemaker synth (which is available for free and should not have protection for debuggers), following the recipe that @t0m gave in this thread Connect to a VST plugin.

Everything works fine til now: for example I can get the parameters (in an AudioPluginParameter form) via AudioPluginInstance::getParameters() , I can get their names and their default values via the getName() and getValue() methods of AudioPluginParameter.

Nevertheless I found out that the discrete parameters of the synth, e.g. waveform of oscillator1 (name I get from getName() : “osc1waveform”) that can assume 3 values, are mapped in a non linear way to the 0 -1 continuous range that the AudioPluginParameter supports:

My questions are the following:

  1. is it possible to change the way discrete parameters are mapped to the 0 - 1 continuous range in an AudioPluginInstance instantiated by loading a 3rd party plugin?
  2. Is the above possible just using the AudioProcessorParameter array returned by AudioPluginInstance::getParameters()?
  3. Is it possible to delete and add parameters to a 3rd party plugin loaded as an AudioPluginInstance?

Any help/clarification/suggestion/link-to-docs is deeply appreciated.


#2

The answer to all of those is no…

A better question is: what are you trying to achieve? Have a look at how the GenericAudioProcessorEditor does things.

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.


#3

Thanks for your reply it already helps a lot.

A better question is: what are you trying to achieve?

I am trying to generate a big dataset of sounds (for research purposes) produced by 3rd party plugins (synths) by calling the processBlock function of an AudioPluginInstance. A very important feature of the dataset is that every sound need to be accompanied by the list of parameters the plugin used to produce a particular sound.
For discrete parameters like (e.g. “waveform” that could be {“sine”, “square”, “noise”}) I have a problem. I need to find an automated way of knowing exactly which waveform (and the same goes for the other discrete parameters) was employed to produce a given sound in the dataset.

The root of the problem is that I am not aware of the exact way the float values, that I have to pass to the AudioPluginInstance processor for discrete parameters as well, are mapped to the different “choices” of that discrete parameter.

After some reading around I was thinking that maybe modifying the NormalisableRange class and adapt it for this purpose could help (but I am unsure whether and how the VST processor utilises that class).
Alternatively, I was wondering is there a way to read (for example) which waveform was used to produce a sound?
when loading the plugin in a DAW that parameter comes with a drop-down menu with {“sine”, “square”, “noise”} so I guess that information is stored somewhere…

Thanks for your patience and I hope in your understanding if I am missing something basic. I am relatively noob but I am attempting to dive deeper into JUCE and its logic atm.


#4

The GenericAudioProcessorEditor handles discrete parameters in the way you want, presenting a drop-down of the available values - take the code from there.


#5

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?


#6

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.


#7

Get labels for the values a parameter can take:

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


#8

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)?


#9

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.


#10

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:


#11

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.


#12

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.


#13

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!


#14

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.


#15

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?


#16
a     |     b     |     c     |     d     |     e
0     |   0.25    |    0.5    |   0.75    |    1.0
    0.125       0.375       0.625       0.875

#17

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;
}

#18

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.


#19

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


#20

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: