ofxAudioUnit parameter callbacks

how can you make a callback to pick up parameter changes in an audio unit?

i know the function AudioUnitGetParameter can get the value instantaneously, but would be better to look for it only when changed.

looks like the function AudioUnitAddPropertyListener allows for this but the arguments it takes is a bit unclear for how to bind to some other method.

@admsyn, any advice on this?

You’ve got the right idea, but there’s a difference between parameters and properties (confusingly). Parameters are things like filter cutoff frequency and mixer volume (things you might put on a knob and expose to a user). Properties are more “internal” and are things like sample rate, connection status, buffer size etc.

Listening to parameters is a bit more complicated than listening to properties for some reason. You create a listener, give the listener a callback/block to call, and then associate the listener with a parameter (or several parameters).

I’m doing this in the params example. First add an event listener to the app:

class ofApp : public ofBaseApp {
  ...
  AUEventListenerRef auEventListener;
  static void audioUnitParameterChanged(void * context,
                                        void * object,
                                        const AudioUnitEvent * event,
                                        UInt64 hostTime,
                                        AudioUnitParameterValue value);
};

then in ofApp::setup()

CFRunLoopRef runLoop = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetCurrentEventLoop());

AUEventListenerCreate(&ofApp::audioUnitParameterChanged,
                      this,
                      runLoop,
                      kCFRunLoopDefaultMode,
                      0.05, // minimum callback interval (seconds)
                      0.05, // callback granularity (for rate-limiting)
                      &auEventListener);
    
AudioUnitParameter param = {
  .mAudioUnit = lowpass,
  .mParameterID = kLowPassParam_CutoffFrequency,
  .mScope = kAudioUnitScope_Global,
  .mElement = 0
};
    
AUListenerAddParameter(auEventListener, this, &param);

and finally, implement the audioUnitParameterChanged function declared in ofApp.h:

void ofApp::audioUnitParameterChanged(void *context, void *object, const AudioUnitEvent *event, UInt64 hostTime, AudioUnitParameterValue parameterValue)
{
    cout << "param changed" << endl;
}

Note that that’s a static function, so you won’t be able to use the ofApp’s variables directly. By passing “this” into the previous functions above like I did, the context arg in the callback will be a pointer to your app.

There’s block-based event listeners you can use as well if you understand blocks. These seem a bit less complicated.

You won’t necessarily be notified of all parameter changes this way. If you call the simple-ish AudioUnitSetParameter like in the params example, you won’t get callbacks. If you need to get callbacks from that sort of event, switch to AUParameterSet which does the same thing, but also notifies listeners. Parameter changes from the GUI should work, though.

If you wanted to listen to properties, the structure would be to add a static method to your ofApp like:

class ofApp : public ofBaseApp {
  ... // other stuff

  static void audioUnitPropertyChanged(void * context,
                                       AudioUnit unit,
                                       AudioUnitPropertyID property,
                                       AudioUnitScope scope,
                                       AudioUnitElement element);
}

and setup the callback like:

void ofApp::setup() {
  AudioUnitAddPropertyListener(unit,
                               kAudioUnitProperty_SetRenderCallback,
                               &ofApp::audioUnitPropertyChanged,
                               this);
}

void ofApp::audioUnitPropertyChanged(void *context, AudioUnit unit, AudioUnitPropertyID property, AudioUnitScope scope, AudioUnitElement element) 
{
    cout << "property changed" << endl;
}
1 Like

yes, this works perfectly.

however, i can’t implement it through the projectGenerator or after adding ofxAudioUnit manually. the line:

CFRunLoopRef runLoop = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetCurrentEventLoop());

won’t compile, as it can’t find GetCurrentEventLoop(). i tried adding all the frameworks from the example project manually (including Carbon), but still can’t locate this function. do you know what i may be missing?

Ah, right, yeah that is Carbon. I was just taking bits and pieces from some old Apple documentation. You can replace the runLoop variable with CFRunLoopGetCurrent() in the AUEventListenerCreate() call.

perfect, works great!

the only thing i am using this for right now is to keep the AU gui and the parameter values in the OF app in sync. doing so through the callback seems rather complicated (and i imagine potentially inefficient). is using the callback the only way to do this or is there some way of getting a reference to the parameter value and binding it to a variable in my OF app?

right now i’m running AUListenerAddParameter(auEventListener, this, &param); 100+ times for all the parameters – is there a better way to do this?

is there some way of getting a reference to the parameter value and binding it to a variable in my OF app?

No, the host / AU divide is basically a brick wall, you have to work through a layer of indirection for everything.

right now i’m running AUListenerAddParameter(auEventListener, this, &param); 100+ times for all the parameters – is there a better way to do this?

I think that’s the right idea. The documentation for these functions implies the intention is to associate a ton of knobs with the AU’s internal parameters. As in, I’m pretty sure AU GUIs use the exact same mechanisms to sync up the GUI with the AU themselves.

You could move the init process over to a for loop at least, if you get the unit’s parameter list via getParameterList().

It can be a bit more generic, if you pass in the address of your ofApp variable that you want to associate with a param in AUListenerAddParameter() it should become the object pointer in the callback. I haven’t tested that though.

e.g.

AUListenerAddParameter(auEventListener, &localAppVariable, &param);
1 Like

hi all

thanks for the addon admsyn, it is great!
and this callback is working too,
however I would like to get the id of the value begining change, is it possible?
I would like to set the values coming back from the callback to values in my app

best
ben

Hi @strimbob, not sure what you mean. Do you just want to set an audio unit’s parameters to values you already have in your ofApp? That’d be the same workflow as is shown in the params example.

yes i can see that i was not very clear!
thanks for your responses
i hope this is clearer -)
this is what i am trying to do…

make a generic loader for an audio unit, which does not known what the pararmeter are call
when it loads.

so i am using the code above like this
///// in setup

 reverb.connectTo(mixer).connectTo(output);
 CFRunLoopRef runLoop = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetCurrentEventLoop());
 AUEventListenerCreate(&ofApp::audioUnitParameterChanged,
                      this,
                      runLoop,
                      kCFRunLoopDefaultMode,
                      0.05, // minimum callback interval (seconds)
                      0.05, // callback granularity (for rate-limiting)
                      &auEventListener);
   getparam(reverb);

/////

then this is the getparam function ////
which gets all of the paramerters and adds a Listener to each one.
///

   void ofApp::getparam(ofxAudioUnit &audioUnit){
  vector <AudioUnitParameterInfo>  _param = audioUnit.getParameterList();
  for(int p = 0;p<_param.size();p++){
     AudioUnitParameter param = {
        .mAudioUnit = audioUnit,
        .mParameterID =  p,
        .mScope = kAudioUnitScope_Global,
        .mElement = 0
    };
    
    AUListenerAddParameter(auEventListener, this, &param);
}
}

/////////////////////////////////

and this is the Listener

/////////////////////////////////

  void ofApp::audioUnitParameterChanged(void *context, void *object, const AudioUnitEvent *event, UInt64 hostTime, AudioUnitParameterValue parameterValue)
   {

cout << "param changed " << parameterValue <<" " << " "  <<      endl;
  }

//////////

what i cant seem to do is get the id of the parameter in the Listener , or be able to set a paramater back to ofApp variable
when i tried

AUListenerAddParameter(auEventListener, &localAppVariable, &param);

i got a memory address back but the memory address did not change when i click on other paramaters in the audio unit
and i could not set to it to the ofapp.

or maybe this is not the way to do it at all

i think what you are looking for is the ParameterID in the callback function audioUnitParameterChanged:

event->mArgument.mParameter.mParameterID

this identifies the parameter that has been changed.

amazing thank you!!

Best
Ben

how do you set the

     event->mArgument.mParameter.mParameterID

back into the ofApp from the static function?

Best
Ben

what do you mean? like how to copy the value into some float or ofParameter in your ofApp? i usually keep a vector or map of the parameters i am keeping in sync.

map<int, ofParameter<float> > parameters;

void ofApp::audioUnitParameterChanged(void *context, void *object, const AudioUnitEvent *event, UInt64 hostTime, AudioUnitParameterValue parameterValue){
    int id = event->mArgument.mParameter.mParameterID;
    parameters[id].set(parameterValue);
}

yes that it!

Thank you for your help -)

Best
Ben

hello again

i am making good progress with my mixer!

i have now 8 channel that can load 8 audioUnit, i will share it back when it is done.
and i have got openframeworks to do the system call for getting audioUnit installed on the system
here is that piece of code if anyone needs it

in ofApp

    OSType ofApp::convertStringtoOSType(string str){
char *shPathChar;
shPathChar = new char[str.length() + 1 ];
strcpy( shPathChar, str.c_str() );
char* myCharPtr = shPathChar;
return  (myCharPtr[0] << 24) | (myCharPtr[1] << 16) | (myCharPtr[2] << 8) | myCharPtr[3];
}
//--------------------------------------------------------------
void ofApp::getInstalledAudioUnits(){
vector<string > callBackTermail = systemCall();

cout << callBackTermail.size() << endl;
for(int x = 0;x<callBackTermail.size();x++){
    vector<string> splitStringAudioUnti = ofSplitString(callBackTermail[x], " ");
    if(splitStringAudioUnti.size() == 8){ // filter out other text
         string isitaSynth = "aumu";     // are you a synth?
        if(isitaSynth == splitStringAudioUnti[0]){
        OSType  _audioUnitTypeV  = convertStringtoOSType(splitStringAudioUnti[0]);
        OSType _audioUnitSubType = convertStringtoOSType(splitStringAudioUnti[1]);
        OSType  _audioUnitManufacturer = convertStringtoOSType(splitStringAudioUnti[2]);
        string _audioUnitFullManfatutereName = splitStringAudioUnti[6];
        string _audioUnitName = splitStringAudioUnti[7];
        audioUnitTypeV.push_back(_audioUnitTypeV);
        audioUnitSubTypeV.push_back(_audioUnitSubType);
        audioUnitManufacturerV.push_back(_audioUnitManufacturer);
        audioUnitFullManfatutereName.push_back(_audioUnitFullManfatutereName);
        audioUnitName.push_back(_audioUnitName);
        }
    }
}

for(int x = 0;x<audioUnitName.size();x++ ){
    cout << audioUnitFullManfatutereName[x] << " " << audioUnitName[x]  << " " <<  audioUnitTypeV[x] << " " << x <<  endl;
}
}

//--------------------------------------------------------------
vector <string>ofApp::systemCall(){

ofBuffer installedAudioUnits;
installedAudioUnits.clear();
installedAudioUnits = (ofSystem("auval -a"));
for(int x = 0;x<installedAudioUnits.size();x++){
    string audioUnitText = installedAudioUnits.getNextLine();
    if( audioUnitText != ""){
        audioUnitTextV.push_back(audioUnitText);
    }
}
return audioUnitTextV;
}

and in .h

    vector<string > audioUnitTextV;
vector <OSType> audioUnitTypeV;
vector <OSType> audioUnitSubTypeV;
vector <OSType> audioUnitManufacturerV;
vector<string> audioUnitFullManfatutereName;
vector<string>  audioUnitName;
vector<string> splitStringAudioUnti;
 OSType convertStringtoOSType(string str);
    vector <string> systemCall();

void getInstalledAudioUnits();

and call in ofApp

getInstalledAudioUnits();

and then you call

ofxAudioUnitSampler audioUnitSampler;
audioUnitSampler.setup(audioUnitTypeV[whateverNumberYouWantToLoad], audioUnitSubTypeV[whateverNumberYouWantToLoad],audioUnitManufacturerV[whateverNumberYouWantToLoad]); // drum

and you can get the name of the synth here

audioUnitName[whateverNumberYouWantToLoad]

//______________________________________________________________________________
but i have two more questions,

the first one is…
how do you delete/ remove a audioUnit after it has been setup?

and can you hide the GUI of the audioUnit, there is showUI() but i cant find a hideUI

thanks for all the great work!

Best
Ben

1 Like

i guess the same way you’d delete any audio unit, when it goes out of scope, or if it’s a pointer which is deleted. however, the audio unit library sometimes seems to crash when OF app exits, so maybe it would crash too if you tried to delete it. better would be to just take it out of your effects chain and let it be…

there is no hideUI() – not sure if the GL window even has access to it once it’s launched. as far as i know you have to close the window manually .