Improvements To Handle AU Parameter Ranges


#1

Would it be possible to make some refinements to the Juce AudioProcessor and AU Wrapper classes. Audio Unit parameters don’t have to be in the 0-1 range and can provide more detailed information to the host (such as min, max etc.) so they can be displayed correctly when automated or used in generic UIs. The changes are minimal and should not affect user code as they have default implementations which just call the standard get/set parameter etc.

I believe VST 3 and possibly 2.4 have some sort of similar concept when scaling parameters from normalised to plain values. Although I haven’t looked into VST fully this system could perhaps be fitted into the VST Wrapper as well. Is anybody else doing a similar thing?

The changes are as follows:

AudioProcessor.h

    //==============================================================================
    /** Some hosts may call this if they support parameter ranges outside of the
        0 - 1.0 range. This should return the value of your full-scale parameter.
     
        By default this just returns your normalised parameter by calling getParameter().
     */
    virtual float getScaledParameter (int parameterIndex);
    
    /** Some hosts may call this if they support parameter ranges outside of the
        0 - 1.0 range. The newValue argument could be any number so you should allow
        for this in your code.
     
        By default this just sets your normalised parameter by calling setParameter().
     */
    virtual void setScaledParameter (int parameterIndex, float newValue);
    
    /** Sets a full scale parameter and notifies the host, similar to
        setParameterNotifyingHost().
     */
	void setScaledParameterNotifyingHost (int parameterIndex, float newValue);
    
    /** Some hosts may call this if they support parameter ranges outside of the
        0 - 1.0 range. This should return the minimum value your parameter can have.
     */
	virtual float getParameterMin (int parameterIndex);

    /** Some hosts may call this if they support parameter ranges outside of the
        0 - 1.0 range. This should return the maximum value your parameter can have.
     */
    virtual float getParameterMax (int parameterIndex);
	
    /** Some hosts may call this if they support parameter ranges outside of the
        0 - 1.0 range. This should return the default value your parameter has.
     */
    virtual float getParameterDefault (int parameterIndex);

AudioProcessor.cpp

float AudioProcessor::getScaledParameter (int parameterIndex)
{
    return getParameter (parameterIndex);
}

void AudioProcessor::setScaledParameter (int parameterIndex, float newValue)
{
    setParameter(parameterIndex, newValue);
}

void AudioProcessor::setScaledParameterNotifyingHost (int parameterIndex, float newValue)
{
    setScaledParameter (parameterIndex, newValue);
    sendParamChangeMessageToListeners (parameterIndex, newValue);
}

float AudioProcessor::getParameterMin (int parameterIndex)
{
    return 0.0f;
}

float AudioProcessor::getParameterMax (int parameterIndex)
{
    return 1.0f;
}

float AudioProcessor::getParameterDefault (int parameterIndex)
{
    return 1.0f;
}[/code]

juce_AU_Wrapper.mm:
[code]line 417:
            outParameterInfo.minValue = juceFilter->getParameterMin (index);
            outParameterInfo.maxValue = juceFilter->getParameterMax (index);
            outParameterInfo.defaultValue = juceFilter->getParameterDefault (index);

line 437:
            outValue = juceFilter->getScaledParameter ((int) inID);

line 452:
            juceFilter->setScaledParameter ((int) inID, inValue);

Audio Units also have a AudioUnitParameterUnit enum which is used by the host to display and map values accordingly but I’m still thinking of an elegant place to put something similar.

Anyway, adding this would save me having to patch Juce every time I update and could be useful to others. Is anybody doing something similar in their plugins?


#2

Damn, one of the to-do-list items I’ve had hanging around for a long time is to refactor the parameter system, providing proper objects for each param, and allowing lots of improved flexibility. Sorry, it keeps getting pushed to the bottom of my pile!


#3

Yeh I though it had been mentioned before, no worries though I’m sure the stuff you’re working currently is more important.

You probably have some better ideas but here is how I currently handle parameters if it helps at all.

[code]/** Parameter Units - currently values are the same as the AudioUnit enums
/
enum ParameterUnit
{
UnitGeneric = 0, /
untyped value generally between 0.0 and 1.0 /
UnitIndexed = 1, /
takes an integer value (good for menu selections) /
UnitBoolean = 2, /
0.0 means FALSE, non-zero means TRUE /
UnitPercent = 3, /
usually from 0 -> 100, sometimes -50 -> +50 /
UnitSeconds = 4, /
absolute or relative time /
UnitSampleFrames = 5, /
one sample frame equals (1.0/sampleRate) seconds /
UnitPhase = 6, /
-180 to 180 degrees /
UnitRate = 7, /
rate multiplier, for playback speed, etc. (e.g. 2.0 == twice as fast) /
UnitHertz = 8, /
absolute frequency/pitch in cycles/second /
UnitCents = 9, /
unit of relative pitch /
UnitRelativeSemiTones = 10, /
useful for coarse detuning /
UnitMIDINoteNumber = 11, /
absolute pitch as defined in the MIDI spec (exact freq may depend on tuning table) /
UnitMIDIController = 12, /
a generic MIDI controller value from 0 -> 127 /
UnitDecibels = 13, /
logarithmic relative gain /
UnitLinearGain = 14, /
linear relative gain /
UnitDegrees = 15, /
-180 to 180 degrees, similar to phase but more general (good for 3D coord system) /
UnitEqualPowerCrossfade = 16, /
0 -> 100, crossfade mix two sources according to sqrt(x) and sqrt(1.0 - x) /
UnitMixerFaderCurve1 = 17, /
0.0 -> 1.0, pow(x, 3.0) -> linear gain to simulate a reasonable mixer channel fader response /
UnitPan = 18, /
standard left to right mixer pan /
UnitMeters = 19, /
distance measured in meters /
UnitAbsoluteCents = 20, /
absolute frequency measurement : if f is freq in hertz then /
/
absoluteCents = 1200 * log2(f / 440) + 6900 /
UnitOctaves = 21, /
octaves in relative pitch where a value of 1 is equal to 1200 cents*/
UnitBPM = 22, /* beats per minute, ie tempo /
UnitBeats = 23, /
time relative to tempo, ie. 1.0 at 120 BPM would equal 1/2 a second /
UnitMilliseconds = 24, /
parameter is expressed in milliseconds /
UnitRatio = 25, /
for compression, expansion ratio, etc. */

UnitCustomUnit			= 26	/* this is the parameter unit type for parameters that present a custom unit name */

};

/** This file defines a parameter used in an application.

Both full-scale and normalised values must be present for
AU and VST host campatability.

/
class PluginParameter
{
public:
/
* Create a default parameter.

	This just uses some standard default values so it can be used as a placeholder.
	Call init() once you know the parameter values.
 
	@see init()
 */
PluginParameter();

/** Creates a copy of another parameter.
 */
PluginParameter (const PluginParameter& other);

/** Initialise the parameter.
	Used to set up the parameter as required.
 */
void init (const String& name_, ParameterUnit unit_, String description_,
           double value_, double min_ =0.0f, double max_ =1.0f, double default_ =0.0f,
           double skewFactor_ =1.0f, double smoothCoeff_ =0.1f, double step_ =0.01, String unitSuffix_ =String::empty);

inline Value& getValueObject()                              {   return valueObject;     }

inline double getValue()                                    {   return double (valueObject.getValue()); }
inline double getNormalisedValue()                          {   return normaliseValue (getValue());     }
void setValue (double value_);
void setNormalisedValue (double normalisedValue);
inline double getSmoothedValue()                            {   return smoothValue;     }
inline double getSmoothedNormalisedValue()                  {   return normaliseValue (smoothValue);     }

inline double getMin()                                      {   return min;             }
inline double getMax()                                      {   return max;             }
inline double getDefault()                                  {   return defaultValue;    }

void smooth();
void setSmoothCoeff (double newSmoothCoef);
inline double getSmoothCoeff()                              {   return smoothCoeff;     }

void setSkewFactor (const double newSkewFactor);
void setSkewFactorFromMidPoint (const double valueToShowAtMidPoint);
inline double getSkewFactor()                               {   return skewFactor;      }

void setStep (double newStep);
inline double getStep()                                     {   return step;            }

inline const String getName()                               {   return name;            }
inline ParameterUnit getUnit()                              {   return unit;            }
inline const String getUnitSuffix()                         {   return unitSuffix;      }
void setUnitSuffix (String newSuffix);

void writeXml (XmlElement& xmlState);
void readXml (const XmlElement* xmlState);

/** Sets up a given slider with the parmeters properties.
 */
void setupSlider (Slider& slider);

private:

Value valueObject;
String name, description, unitSuffix;
double min, max, defaultValue;
double smoothCoeff, smoothValue;
double skewFactor, step;
ParameterUnit unit;

double normaliseValue (double scaledValue);

JUCE_LEAK_DETECTOR (PluginParameter);

};
[/code]

[code]PluginParameter::PluginParameter()
{
init(“parameter”, // name
UnitGeneric, // unit
"A parameter", // description
1.0, // value
0.0, // min
1.0, // max
0.0, // default
1.0, // skew factor
0.1, // smooth coeff
0.01, // step
String::empty); // unit suffix
}

PluginParameter::PluginParameter (const PluginParameter& other)
{
name = other.name;
description = other.description;
unitSuffix = other.unitSuffix;
min = other.min;
max = other.max;
defaultValue = other.defaultValue;
smoothCoeff = other.smoothCoeff;
smoothValue = other.smoothValue;
skewFactor = other.skewFactor;
step = other.step;
unit = other.unit;
setValue (double (other.valueObject.getValue()));
}

void PluginParameter::init(const String& name_, ParameterUnit unit_, String description_,
double value_, double min_, double max_, double default_,
double skewFactor_, double smoothCoeff_, double step_, String unitSuffix_)
{
name = name_;
unit = unit_;
description = description_;

min = min_;
max = max_;
setValue (value_);
defaultValue = default_;

smoothCoeff = smoothCoeff_;
smoothValue = getValue();

skewFactor = skewFactor_;
step = step_;

unitSuffix = unitSuffix_;

// default label suffix's, these can be changed later
switch (unit)
{
	case UnitPercent:       setUnitSuffix("%");                         break;
	case UnitSeconds:       setUnitSuffix("s");                         break;
	case UnitPhase:         setUnitSuffix(CharPointer_UTF8 ("°"));      break;
	case UnitHertz:         setUnitSuffix("Hz");                        break;
	case UnitDecibels:      setUnitSuffix("dB");                        break;
	case UnitDegrees:       setUnitSuffix(CharPointer_UTF8 ("°"));      break;
	case UnitMeters:        setUnitSuffix("m");                         break;
	case UnitBPM:           setUnitSuffix("BPM");                       break;
	case UnitMilliseconds:  setUnitSuffix("ms");                        break;
	default:                                                            break;
}	

}

void PluginParameter::setValue(double value)
{
valueObject = jlimit (min, max, value);
}

void PluginParameter::setNormalisedValue(double normalisedValue)
{
setValue ((max - min) * jlimit (0.0, 1.0, normalisedValue) + min);
}

void PluginParameter::setUnitSuffix(String newSuffix)
{
unitSuffix = newSuffix;
}

void PluginParameter::smooth()
{
if (smoothValue != getValue())
{
if( (smoothCoeff == 1.0) || almostEqual (smoothValue, getValue()) )
smoothValue = getValue();
else
smoothValue = ((getValue() - smoothValue) * smoothCoeff) + smoothValue;
}
}

void PluginParameter::setSmoothCoeff (double newSmoothCoef)
{
smoothCoeff = newSmoothCoef;
}

void PluginParameter::setSkewFactor (double newSkewFactor)
{
skewFactor = newSkewFactor;
}

void PluginParameter::setSkewFactorFromMidPoint (const double valueToShowAtMidPoint)
{
if (max > min)
skewFactor = log (0.5) / log ((valueToShowAtMidPoint - min) / (max - min));
}

void PluginParameter::setStep (double newStep)
{
step = newStep;
}

void PluginParameter::writeXml (XmlElement& xmlState)
{
xmlState.setAttribute (name, getValue());
}

void PluginParameter::readXml(const XmlElement* xmlState)
{
setValue (xmlState->getDoubleAttribute (name, getValue()));
}

void PluginParameter::setupSlider(Slider &slider)
{
slider.setRange (min, max, step);
slider.setSkewFactor (skewFactor);
slider.setValue (getValue(), false);
slider.setTextValueSuffix (unitSuffix);
}

double PluginParameter::normaliseValue(double scaledValue)
{
return ((scaledValue - min) / (max - min));
}[/code]


#4

Hi Jules,

I contacted Dave about the ideas he exposed here, to know if we could gather our forces to get this done a way it can be integrated easily in the AudioProcessor’s interface, as this is an update that would be most welcome for both of us (and maybe a few other people).

First, I’d like to know if you already started something, so we don’t end up in parallel work.

Here are some more ideas that can be added to Dave’s propositions:
[list]
[]All the development here shall not break user’s code, and only implement more features to handle parameters.[/]
[]Parameters are using a Value object to retain their value. As Values can be based on a lot of types, we could also have String parameters, integer (discrete) or boolean parameters, or even custom objects (provided that they are reference counted).[/]
[]Only automatable parameters are declared to the host, but it shall be possible to have internal parameters (eg: for settings, options etc…)[/]
[]Automatable parameter values are interfaced with the host in the floating point normalised 0-1 range, but within the plugin they shall be accessed using the scaled (mapped/ranged/whatever) representation.[/]
[]The normalised value of automatable parameters depends on the base type of the parameter. If it is straightforward for integers, float and booleans, non-number types like String shall declare/reimplement a method to convert their scaled value to a normalised value.[/]
[]The ability of Value objects to automatically update dependancies can be used to link parameters together, though we need to make sure this is not going to mess up with the host (eg: meta parameters in AU).[/]
[]The storage of Parameter objects shall remain the task of the developer[/]
[]Parameters (at least automatables) shall still be accessed by an integer ID value, in the range 0 - N-1 (for N parameters declared).[/]
[/list]

More to come…

Edit: I looked at the API of VST3 SDK, and it looks like there are some similar methods to define min, max, default values etc… So it can be used in the formats VST3, AU, AAX…


#5

Thanks - that all sounds extremely sensible, and I’d greatly appreciate a crowd-sourced design for this!

I did start sketching out some ideas for this ages ago, but that was before I created the Value class, so best to start again with that in mind.


#6

This looks pretty good to me. A little different to the way I do it, but more comprehensive. Another idea to think about (or reject as you please)…

My parameters class implements the parameter values in an array that I dimension according to the number of preset programs specified. This makes all the parameter related methods in the AudioProcessor very neat and easy to maintain. For example:

float MyAudioProcessor::getParameter (int index) { if (index==attack.paramId) return (float)attack.getAutomationValue(currentProgram); else if (index==release.paramId) return (float)release.getAutomationValue(currentProgram); else return 0.0f; }

For non-automatable parameters you can set the array size to 1.

Just dreaming here, but it would also be nice to have a parameter management facility in The Jucer so you could easily define your parameters and attach them to the sliders right then & there. I don’t ask for much do I? :wink:


#7

Another suggestion is to store converted values within the parameter class and implement methods to access these. I do this for parameters which are set in dB in my UI, but used in linear form by my audio processor. That way the log to linear conversion is done as the parameter is changed, and doesn’t have to be done each time a block is processed.


#8

Thanks Jules. I made some tests with the Value class today, to do the following:
[list]
[]Create a Value object in the AudioProcessor subclass that will act as the parameter value container (set to float, initial value to 0.f)[/]
[]Create a Slider in the AudioProcessorEditor subclass, set it up (range 0-1, continuous)[/]
[]Bind the slider’s Value object to the parameter Value object (using referTo)[/][/list]

At this point, controlling the value of the parameter and having the UI updated when the parameter changes works like a charm. Interfacing the parameter Value object with get/setParameter makes automation reading work (both the internal value and the position of the slider are updated according to the automation line).

Now let’s kick into automation writing.

When moving the slider, we need to call the following:
[list]
[]beginParameterChangeGesture: this can be easily bound to sliderDragStarted.[/]
[]setParameterNotifyingHost: the problem here is that this method will call setParameter, thus might create a loop (because of the link between the parameter’s Value and the slider’s).[/]
[]endParameterChangeGesture: this can be easily bound to sliderDragEnded.[/][/list]

@dave, how do you handle automation writing?

@Jules: is there a way to un-bind two Values objects? Make one use a different ValueSource object? I tried something like that, but either I did something wrong or it is not meant to be done…


#9

I guess you’d just use referTo() to connect it to some other Value?


#10

I tried to call referTo(Value()) to bind it to an invalid/empty value, it seems it did not work (though my loop problem can come from somewhere else), but I will make some more tests.


#11

I think that ought to work…


#12

regarding the loop when you move the knob in the UI
you just need to check for isMouseButtonDown() and discard parameter changes in that case.


#13

Even though AU supports non 0-1 range variables, often one would still need to define a mapping of parameter values to displayed values.

For example, in a frequency parameter shown in Hz, one would want octaves to be the same distance in automation and physical controllers.

There is an AU parameter flag to say that you want the parameter to have a “logarithmic scale”, but Logic doesn’t support it.
The way to do it that does work is to define a mapping using the kAudioUnitProperty_ParameterStringFromValue property (and kAudioUnitProperty_ParameterValueFromString for the reverse mapping).

tl;dr: I see no reason for AU params internal values not to be in 0-1 range like all else, as a mapping to displayed values needs to be defined anyhow…


#14

I guess the natural values would me more useful everywhere, with the normalised value being calculated only for automations. The mapping would happen between the normalised value and the natural one, but the question remains: whose job is it? The host or the plugin? If this is the host’s, then as you said, some will inevitably have a different behaviour than others.

The problem here is that everyone that already has a behaviour implemented regarding parameter mapping/range/scaling will have done it differently, so it might be hard to find an interface that does not clash with someone else’s…

Though, if some cases require special attention (parameters whose range is controlled by another parameter, dependencies, links and other special stuff), they might be solved as a special handling of the default case, build over it (in the form of a reimplemented method, an implemented callback or any other pattern-wise solution we can find to deal with evolutive code).

Handling all the cases would bloat up the code and make it quite difficult to understand and to use, but if the default behaviour is powerful enough to cover a few, and if there is a fallback for tricky scenarios, then that could be a good interface to work with.


#15

Okay, I performed some unit tests on the Value class, it does indeed disconnect when calling referTo(Value()). My loop must be somewhere else, I’ll try and find it.

I tried some variations on dave’s work, using the couple var/Value to make shape-shifting parameters (I tried with C++ templates before, but it’s too static and not very easy to use). I also used Value objects for the min, max and default values, so we can think about parameters whose range can be dynamically/automatically changed (without any call to setMin/setMax etc…).

I would like to add some tests when updating either of these members (including the parameter’s value itself), to check that value and default are between min and max, but this assumes that all these are of a numerical type or, at least, provide comparison operators (which might not be the case, and/or even if they do, might be irrelevant to the situation, eg: String comparison).


#16

Hi all,

I’ve been testing the Value objects to handle dependencies between parameters in my plugin, and I found the following:

[list]
[]If it is damn easy to create a one to one dependency, creating complex graphs is more complicated.
Example: 3 Values with the following connections:
[list]
[
]A.referTo(B)[/]
[
]B.referTo© -> This breaks connection between A and B.[/][/list]
It is then quite difficult to add more complex dependencies, without having a single object at the centre of the graph that acts like a proxy for other Value object to connect to (star diagram).[/
][/list]
[list]
[]The performance of the var object is not ideal (when accessing parameters in the real time thread), given that even if one uses only a double to interface the Value, var objects are created and destroyed to interface the underlying data used by the ValueSource (in set/getValue).
Possible solution:
[list]
[
]Use a template for Value class, using a var as a default type argument (to keep existing code unchanged), but with the possibility to create Value objects that would only use this data type, even for interfacing the ValueSource (that could encapsulate the type given by the template argument as well).[/][/list][/][/list]


#17

Well… values aren’t really designed for real-time threads, they do all kinds of allocation and message-posting internally, I don’t recommend using them on your audio thread.


#18

Indeed, I also tried buffering the value within the parameter (to allow a faster get while keeping sync behaviour for the set), but as synchronisation of values is done by an async updater, it makes the unit tests fail when setting then getting a parameter and expect equality.

Actually, first I was using the var object everywhere to interface the parameters (to set range, interval etc…), with the hope that it would bring a good diversity when dealing with discrete parameters, on/off switch as booleans etc… but the performance was terrible. I now use a double as the interface type (as I needed only numerical types), and the performance is actually quite acceptable (about 1% CPU per instance on an 8 core MacPro, with 1000 calls to Value::getValue() every 32 samples).


#19

Okay for the performance, but what about the chain dependencies?

I’m trying to add a parent/child behaviour to a subclass of Value, to see if such a thing is possible (the root of the tree being the Value every child refers to, so internally it remains a star diagram).


#20

I’ve already played this game in detail and have a product in the field implementing all this. You cannot define custom non-linear mappings in AU, so you are always best off mapping to 0 to 1 apart from enumerated types, which you map from 0 to count-1. All you need to do is add string arrays for the enum types, and to the string to value and value to string conversions. The minimal interface I could come up with to do this is below where every param is either a float, an enum with 2 values (bool), or an enum with more than two values (indexed).

You have a converter class for each parameter to do the actual conversions:

    class ConvertValue
	{
	public:
		//==============================================================================
		juce_UseDebuggingNewOperator

		ConvertValue () {};
		virtual ~ConvertValue () {};
		virtual double ConvertFromText (const char* const s) = 0;
		virtual DisplayString ConvertToText (double val, bool exact = false) = 0;
		virtual double ConvertToNormalized (double val) = 0;
		virtual double ConvertFromNormalized (double val) = 0;
		virtual int ConvertFromNormalizedInt (double val) { return static_cast <int> (ConvertFromNormalized (val)); }
		virtual int GetEnumCount () const { return 0; }
	};

A couple of delegation functions in the juce_AudioProcessor.h

    /** Returns the value of a parameter as a text string using the specified value. */
    virtual const String getParameterValueToText (int parameterIndex, float value);

    /** Returns the value of a parameter as a float converted from the specified text string. */
    virtual float getParameterValueFromText (int parameterIndex, const String& text);

    enum ParameterType
    {
        ParameterType_Float = 0,
        ParameterType_Boolean,
        ParameterType_Enum,
    };

    virtual ParameterType getParameterType (int parameterIndex)
    {
        return ParameterType_Float;
    }
    
    virtual int getParameterNumEnum (int parameterIndex)
    {
        return 0;
    }
class JuceAU : ....
{
private:
    ::CFMutableArrayRef* parameterValueStrings;

public:
    JuceAU (AudioUnit component)
        : AUBase (component, 1, 1)
        , prepared (false)
    {

...
        parameterValueStrings = new ::CFMutableArrayRef [juceFilter->getNumParameters()];
        
        // construct the enum string arrays
        for (int i=0; i<juceFilter->getNumParameters(); i++)
        {
            ::CFMutableArrayRef choicesArray = 0;
            if (juceFilter->getParameterType (i) == AudioProcessor::ParameterType_Enum)
            {
                int enumCount = juceFilter->getParameterNumEnum (i);
                jassert (enumCount != 0);

                choicesArray = CFArrayCreateMutable (0, enumCount, 0);
                for (int k=0; k<enumCount; k++)
                {
                    String enumString = juceFilter->getParameterValueToText (i, (float)(k)/(float)(enumCount-1));
                    ::CFArrayAppendValue (choicesArray, enumString.toCFString ());
                }
            }
            parameterValueStrings[i] = choicesArray;
        }

    ~JuceAU()
    {
        for (int i = activeUIs.size(); --i >= 0;)
            [((JuceUIViewClass*) activeUIs.getUnchecked(i)) filterBeingDeleted: this];

        if (juceFilter != 0)
        {
            for (int i = 0; i < juceFilter->getNumParameters(); ++i)
            {
                if (parameterValueStrings[i])
                {
                    ::CFRelease(parameterValueStrings[i]);
                }
            }
        }
        delete [] parameterValueStrings;
        parameterValueStrings = 0;

...

    }
...

    //==============================================================================
    ComponentResult GetPropertyInfo (AudioUnitPropertyID inID,
                                     AudioUnitScope inScope,
                                     AudioUnitElement inElement,
                                     UInt32& outDataSize,
                                     Boolean& outWritable)
    {
        OSStatus result = noErr;
        if (inScope == kAudioUnitScope_Global)
        {
            switch (inID)
            {

...

                case kAudioUnitProperty_ParameterStringFromValue:
                {
                    if (static_cast<int>(inElement) >= juceFilter->getNumParameters())
                    {
                        return kAudioUnitErr_InvalidElement;
                    }
                    outWritable = false;
                    outDataSize = sizeof (::AudioUnitParameterStringFromValue);
                    return noErr;
                }
                break;
                
                case kAudioUnitProperty_ParameterValueFromString:
                {
                    if (static_cast<int>(inElement) >= juceFilter->getNumParameters())
                    {
                        return kAudioUnitErr_InvalidElement;
                    }
                    outWritable = false;
                    outDataSize = sizeof (::AudioUnitParameterValueFromString);
                    return noErr;
                }
                break;
                
            }
        }
        result = TheAuBaseClass::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable);
        return result;
    }

    ComponentResult GetProperty (AudioUnitPropertyID inID,
                                 AudioUnitScope inScope,
                                 AudioUnitElement inElement,
                                 void* outData)
 
    {
        OSStatus result = noErr;
        if (inScope == kAudioUnitScope_Global)
        {
            switch (inID)
            {

...

                case kAudioUnitProperty_ParameterStringFromValue:
                {
                    ::AudioUnitParameterStringFromValue* sfv = reinterpret_cast< ::AudioUnitParameterStringFromValue* >(outData);
                    if (static_cast<int>(sfv->inParamID) >= juceFilter->getNumParameters())
                    {
                        return kAudioUnitErr_InvalidElement;
                    }
                    String valueString;
                    if (sfv->inValue == 0)
                    {
                        valueString = juceFilter->getParameterText (sfv->inParamID);
                    }
                    else
                    {
                        valueString = juceFilter->getParameterValueToText( sfv->inParamID, scaleFromAuValue (sfv->inParamID, *sfv->inValue));
                    }
                    sfv->outString = valueString.toCFString ();
                    return noErr;
                }
                break;
                case kAudioUnitProperty_ParameterValueFromString:
                {
                    ::AudioUnitParameterValueFromString* vfs = reinterpret_cast< ::AudioUnitParameterValueFromString* >(outData);				
                    if (static_cast<int>(vfs->inParamID) >= juceFilter->getNumParameters())
                    {
                        return kAudioUnitErr_InvalidElement;
                    }
                    String valueString = String::fromCFString (vfs->inString);
                    vfs->outValue = scaleToAuValue (vfs->inParamID, juceFilter->getParameterValueFromText(vfs->inParamID, valueString));
                    return noErr;
                }
                break;
            }
        }

        result = TheAuBaseClass::GetProperty (inID, inScope, inElement, outData);
        return result;
	
    }


    //==============================================================================
    ComponentResult GetParameterInfo (AudioUnitScope inScope,
                                      AudioUnitParameterID inParameterID,
                                      AudioUnitParameterInfo& outParameterInfo)
    {
        if (inScope == kAudioUnitScope_Global)
        {
            const int index = (int) inParameterID;

            if (juceFilter == 0 && index >= juceFilter->getNumParameters())
            {
                return kAudioUnitErr_InvalidParameter;
            }
            
            outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable
                                      | kAudioUnitParameterFlag_IsReadable
                                      | kAudioUnitParameterFlag_HasCFNameString;

            const String name (juceFilter->getParameterName (index));

            // set whether the param is automatable (unnamed parameters aren't allowed to be automated)
            if (name.isEmpty() || ! juceFilter->isParameterAutomatable (index))
                outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime;

            if (juceFilter->isMetaParameter (index))
                outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta;

            AUBase::FillInParameterName (outParameterInfo, name.toCFString(), false);

            switch (juceFilter->getParameterType (index))
            {
                case AudioProcessor::ParameterType_Float:
                {
                    outParameterInfo.unit = kAudioUnitParameterUnit_Generic;
                }
                break;
                
                case AudioProcessor::ParameterType_Boolean:
                {
                    outParameterInfo.unit = kAudioUnitParameterUnit_Boolean;
                }
                break;
                
                case AudioProcessor::ParameterType_Enum:
                {
                    outParameterInfo.unit = kAudioUnitParameterUnit_Indexed;
                }
                break;
            }
            outParameterInfo.flags |= kAudioUnitParameterFlag_ValuesHaveStrings;
            outParameterInfo.minValue = scaleToAuValue (index, 0.0f);
            outParameterInfo.maxValue = scaleToAuValue (index, 1.0f);
            outParameterInfo.defaultValue = scaleToAuValue (index, juceFilter->getParameter (index));

            return noErr;
        }
        return AUBase::GetParameterInfo (inScope, inParameterID, outParameterInfo);
    }
    
    float scaleToAuValue (int index, float value)
    {
        int enumCount = juceFilter->getParameterNumEnum (index);
        if (enumCount > 1)
        {
            value *= (float)(enumCount-1);
            value = (float)((int)(value + 0.5f));
        }
        return value;
    }

    float scaleFromAuValue (int index, float value)
    {
        int enumCount = juceFilter->getParameterNumEnum (index);
        if (enumCount > 1)
        {
            value = (float)((int)(value + 0.5f));
            value /= (float)(enumCount-1);
        }
        return value;
    }

    ComponentResult GetParameter (AudioUnitParameterID inID,
                                  AudioUnitScope inScope,
                                  AudioUnitElement inElement,
                                  Float32& outValue)
    {
        if (inScope != kAudioUnitScope_Global)
        {
            return kAudioUnitErr_InvalidScope;
        }
        if (juceFilter == 0 && inID >= (AudioUnitParameterID)juceFilter->getNumParameters())
        {
            return kAudioUnitErr_InvalidParameter;
        }
        outValue = scaleToAuValue ((int) inID, juceFilter->getParameter ((int) inID));
        return noErr;
    }

    ComponentResult SetParameter (AudioUnitParameterID inID,
                                  AudioUnitScope inScope,
                                  AudioUnitElement inElement,
                                  Float32 inValue,
                                  UInt32 inBufferOffsetInFrames)
    {
        if (inScope != kAudioUnitScope_Global)
        {
            return kAudioUnitErr_InvalidScope;
        }
        if (juceFilter == 0 && inID >= (AudioUnitParameterID)juceFilter->getNumParameters())
        {
            return kAudioUnitErr_InvalidParameter;
        }
        juceFilter->setParameter ((int) inID, scaleFromAuValue ((int) inID, inValue));
        return noErr;
    }

	/*! @method GetParameterValueStrings */
	virtual OSStatus GetParameterValueStrings
    (
        AudioUnitScope inScope,
        AudioUnitParameterID index,
        CFArrayRef *outStrings
    )
    {
        if (inScope == kAudioUnitScope_Global && juceFilter != 0 && index < (AudioUnitParameterID)juceFilter->getNumParameters())
        {
            if (juceFilter->getParameterType (index) != AudioProcessor::ParameterType_Enum)
            {
                return kAudioUnitErr_InvalidProperty;
            }
            if (outStrings != 0)
            {
                *outStrings = parameterValueStrings [index];
                ::CFRetain(parameterValueStrings [index]);
            }
            return noErr;
        }
        return kAudioUnitErr_InvalidProperty;
    };

...
}