Creating Factory Presets in v3 API

I'm attempting to create Factory presets using the v3 Audio Unit API on iOS. I can not find any documentation on how one would go about doing this. All I can seem to find is an AUAudioUnitPreset class that I'm assuming you override but I don't know how you would go about setting up what parameters is should set. Any insight is appreciated


Edit: I found this article https://forums.developer.apple.com/message/30945#30945. So it appears I don't want to subclass AUAudioUnitPreset but I'm still uncertain about creating the Presets for iOS so that I can set the proper parameters

Unfortunetly, preset managment capabilities are very limited on iOS. It's just a flat list of preset names a user can make a selection from. Please file a radar for some enhancments, I did 🙂


What I did was I captured fullState and saved them as files in the extenion bundle. So when user selects a parameter I load it from the file. Something like this:


- (void)setCurrentPreset:(AUAudioUnitPreset *)preset {
        NSDictionary *fullState = [NSDictionary dictionaryWithContentsOfURL:[self.class urlForPresetWithName:preset.name]];
        if (fullState) {
            self.fullState = fullState;
        }



Hope this helps.

Factory Presets are a set of pre-defined audio unit settings that are shipped with an Audio Unit. They define common settings that an Audio Unit will ordinarily use. For example, a reverb Audio Unit might want to include several factory presets such as a small room or a large hall.


User states are custom settings that can be created by a user. Essentially, a user state is a snapshot of the current settings on an audio unit at any given time, which could be stored in a file format.


You don't really need to save and load full state for a Factory Preset. As mentioned in the other forum post, AUAudioUnitPreset is a wrapper around the AUPreset. Just a name and number for your preset which internal to your AU can represent a specific set of parameter values.


To look at one way to do this, see the V2 Filter.cpp file that's part of the V2 AudioUnitExamples sample (search for presets) - we'll be updating the V3 sample shortly but the concept is identical.

Thanks for further clarification, theanalogkid. I just looked into V2 Filter example and then read the AUPreset headers. Which said “If < 0, then preset is a user preset” and then I realised user can save presets in one host and the presets will be made available in another. I had no idea this was possible. Wish AUAudioUnitPreset docs had said the same thing.



Just to confirm that I understand this correctly, if I get a setCurrentPreset callback from the host passing AUAudioUnitPreset object with the `number` property that’s negative it means user is trying to save a custom preset. In this case AU has an opportunity to serialise the current parameter settings and save it as a preset. And then the AU can make it available in another host as a user preset (i.e. with a negative `number` property). e.g. User saves preset in GarageBand then it can be made available in say AUM? This is really cool if this is possible.



Another thing that I wanted to mention is the expansion of presets API. I filed a feature request radar, but thought I’d raise it here as well. I wish instead of having factory presets as an array it could be a tree structure. Or at least have one level of depth. So say if I have a synth which has a big library of presets I can group them in say Leads, Bass, Drums, Keys etc. In addition, would be great to provide each preset with tags, that can describe sound qualities of each preset e.g. bright, dark, thin, distorted, complex etc. A bit like Logic’s Alchemy synth. Basically, an AU would provide all these tags and directory/tree structure via API to the host. And then each host can choose how it wants to display this info to the users. I think this would be a great feature. Otherwise, every AU would have its own preset browser which I’m not a fan of. Would be a great feature for both iOS and OSX.

Just to add a quick follow up to my last post. After playing with the setCurrentPreset API and GarageBand, I realised it's not possible to use it to save and share the presets across the hosts. It seems the host can call setCurrentPreset method on the AU with the AUAudioUnitPreset.number property set to a negative number. Which is not much use for the AU other than knowing the currently loaded use preset name.


It would be great if Apple could add an API and make it possible to share the user presets across the hosts.

Yes, indeed...and a feature request is already on file for that one. Thanks!


As a note - while TN2157 is an older document referring to the v2 APIs and discusses OS X only, it may be worth a quick read. In AUAudioUnit.h, the relevant property names are commented as "Bridged to the v2 property..." and there's a discussion in the TN of when -1 would be used as preset number.


Audio Units - How to correctly save and restore Audio Unit presets.


  • factoryPresets is bridged to the v2 property kAudioUnitProperty_FactoryPresets
  • currentPreset is bridged to the v2 property kAudioUnitProperty_PresentPreset
  • fullState is bridged to the v2 property kAudioUnitProperty_ClassInfo.

My Audio Unit Noobiness is probably showing, but I'm also struggling with this (and the lack of documentation).

My first thought was to create an NSArray of AUAudioUnitPreset objects and point the factoryPresets property to it, but it's a read-only property.

So now I guess I need to override the getter for the property... Am I on the right track with this code?


- (AUAudioUnitPreset*)makePreset:(NSInteger)number name:(NSString*)name {
    AUAudioUnitPreset* aaup = [[AUAudioUnitPreset alloc] init];
    aaup.name = name;
    aaup.number = number;
    return aaup;
}

- (NSArray*)factoryPresets {
    presetArray = nil;
    presetArray = [tempArray arrayByAddingObject:[self makePreset:0 name:@"80s Analog Kit"]];
    presetArray = [tempArray arrayByAddingObject:[self makePreset:1 name:@"90s Analog Kit"]];
    return presetArray;
}

Where's *tempArray* initialised? But I think the code doesn't do what you think it does. If I was to do a minimal rewrite I would do this:


- (NSArray*)factoryPresets {
    NSArray *presetArray = @[
         [self makePreset:0 name:@"80s Analog Kit"],
         [self makePreset:1 name:@"90s Analog Kit"],
    ];
    return presetArray;
}



Also, don't forget to override this method: - (void)setCurrentPreset:(AUAudioUnitPreset *)preset

Works like a charm! Thanks!


(tempArray was actually supposed to say presetArray, so I was on the right track, but your version is cleaner - and actually works).

In addition to the preset management code example in filter sample code (static array containing the parameters value of each preset), I've created an AUAudioUnit subclass providing convenience methods to load and save user's presets from/to the disk. Have a look on GitHub/FredAntonCorvest/Common-AudioUnit-V3. I'm sure it will help.

I have implementing saving and loading presets, but I can't figure out how to notify the parameter listeners that the values of the audio unit has changed for V3. The document Audio Units - How to correctly save and restore Audio Unit presets. only describes how to notify parameter changes in MacOS X.


Thanks!

Are you listenting for "allParameterValues". There's a mention of this pseudo-property in AUAudioUnit.h for the parameterTree property.


"AUAudioUnit has an additional pseudo-property, "allParameterValues", on which KVO notifications are issued in response to certain events where potentially all parameter values are invalidated. This includes changes to currentPreset, fullState, and fullStateForDocument."

I see, thanks for your reply! I usually try not to use KVO though...


Another issue that you might be aware of:


When bridging to the v2 audio units provided by Apple, the parameter observer token callback is not always called, which I suppose is a bug.


releaseTimeParameter!.value = some_value // should trigger the callback (luckily, the changes applies to the tree)


// Setting releaseTimeParameter!.value doesn't invoke the parameter change callback.
// Changes applies correctly to the tree, though

private var releaseTimeParameter : AUParameter? {
    get {
        return _parameterTree?.parameter(withID: kLimiterParam_DecayTime,
                                         scope: kAudioUnitScope_Global,
                                         element: 0)
    }
}

private func connectParametersToControls() {
    // Find our parameters by their identifiers.
   
    parameterObserverToken = _parameterTree!.token(byAddingParameterObserver: { [unowned self] address, value in
      // NOT CALLED
      DispatchQueue.main.async {
        if address == self.releaseTimeParameter!.address {
          self.releaseKnob.value = value
        }
        else if address == self.inGainParameter!.address {
            self.inGainKnob.value = value
        }
      }
    })
}
Here for Object-C - AUv3 Extension...
- (instancetype)initWithComponentDescription:(AudioComponentDescription)componentDescription options:(AudioComponentInstantiationOptions)options error:(NSError **)outError {
    self = [super initWithComponentDescription:componentDescription options:options error:outError];

    if (self == nil) {
        return nil;
    }
    /
    _currentFactoryPresetIndex = kDefaultFactoryPreset;
    _presets = @[NewAUPreset(0, @"First Preset"),
                 NewAUPreset(1, @"Second Preset"),
                 NewAUPreset(2, @"Third Preset")];

    self.currentPreset = _presets.firstObject;
  
    return self;
}


and to set any parameters...


- (AUAudioUnitPreset *)currentPreset
{
    if (_currentPreset.number >= 0) {
        return [_presets objectAtIndex:_currentFactoryPresetIndex];
    } else {
        return _currentPreset;
    }
}
- (void)setCurrentPreset:(AUAudioUnitPreset *)currentPreset
{
    if (nil == currentPreset) { return; }
   
    if (currentPreset.number >= 0) {
        /
        for (AUAudioUnitPreset *factoryPreset in _presets) {
            if (currentPreset.number == factoryPreset.number) {
               
                /
                AUParameter *cutoffParameter = [self.parameterTree valueForKey: @"cutoff"];
                AUParameter *resonanceParameter = [self.parameterTree valueForKey: @"resonance"];
               
                cutoffParameter.value = presetParameters[factoryPreset.number].cutoffValue;
                resonanceParameter.value = presetParameters[factoryPreset.number].resonanceValue;
                */
               
               
                /
                _currentPreset = currentPreset;
                _currentFactoryPresetIndex = factoryPreset.number;
                break;
            }
        }
    } else if (nil != currentPreset.name) {
        /
        _currentPreset = currentPreset;
    } else {
    }
}


See example for more informations....

Creating Factory Presets in v3 API
 
 
Q