Audio Units (AUv3) MIDI extension – Part 3: Presets
AUv3 MIDI Presets
In previous blog posts I created a working AUv3 MIDI processor that adds an interval to incoming MIDI note messages. The interval can be customized via an AUParameter.
Now I’ll show you how to make v3 presets.
Introduction
Take a look at AuAudioUnit.h if you haven’t yet. A lot of the “new” (AUv3( Audio Unit API is here.
This time look for what it offers for presets.
Minus the comments, we have this regarding presets:
1 2 3 4 |
@property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) NSArray<AUAudioUnitPreset *> *factoryPresets; @property (NS_NONATOMIC_IOSONLY, copy, nullable) NSDictionary<NSString *, id> *fullState; @property (NS_NONATOMIC_IOSONLY, copy, nullable) NSDictionary<NSString *, id> *fullStateForDocument; @property (NS_NONATOMIC_IOSONLY, retain, nullable) AUAudioUnitPreset *currentPreset; |
The AUAudioUnitPreset class is not complicated. It is simply a name and number.
1 2 3 4 |
@interface AUAudioUnitPreset : NSObject <NSSecureCoding> @property (NS_NONATOMIC_IOSONLY) NSInteger number; @property (NS_NONATOMIC_IOSONLY, copy) NSString *name; @end |
Let’s go through these members.
Defining Factory presets
We see that our Audio Unit’s superclass defines an array of AUAudioUnitPresets named factoryPresets. Clever naming. So, we need to create this array and return it in this property.
My first shot at this was to synthesize the inherited property into an array instance variable. Then, I created a method to instantiate the AUAudioUnitPresets.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
NSArray<AUAudioUnitPreset *> *_presets; @synthesize factoryPresets = _presets; ... - (void) setupFactoryPresets { NSMutableArray* presetItems = [NSMutableArray new]; AUAudioUnitPreset* newPreset = [AUAudioUnitPreset new]; newPreset.number = 0; newPreset.name = @"Frobnozz"; [presetItems addObject:newPreset]; newPreset = [AUAudioUnitPreset new]; newPreset.number = 1; newPreset.name = @"SnizzWhiz"; [presetItems addObject:newPreset]; _presets = [NSArray arrayWithArray:presetItems]; _currentFactoryPresetIndex = 0; _currentPreset = _presets[_currentFactoryPresetIndex]; } |
If I had a lot of presets to create, this code looks pretty wet. So let’s DRY it out.
1 2 3 4 5 6 |
- (AUAudioUnitPreset*)createPreset:(NSInteger)number name:(NSString*)name { AUAudioUnitPreset* newPreset = [AUAudioUnitPreset new]; newPreset.number = number; newPreset.name = name; return newPreset; } |
The thing is, we don’t really need a method to create the presets. Why not just override the array?
1 2 3 4 5 6 7 |
- (NSArray*)factoryPresets { NSArray *presetArray = @[ [self createPreset:0 name:@"Frobnozz"], [self createPreset:1 name:@"SnizzWhiz"], ]; return presetArray; } |
It’s works! Here they are.
What does FilterDemo do?
(Remember the previous blog post on C++? The FilterDemo’s filtering code is actually in a C++ class named FilterDSPKernel. You’ll see this a lot in audio unit code).
FilterDemo does this for two of the inherited properties: factoryPresets and currentPreset. If you don’t call @synthesize on a property, you will get a variable with the property name prefixed with an underscore. So, we see that the property currentPreset is not synthesized, but the variable _currentPreset is defined. They also define an array of AUAudioUnitPresets, and then “point” the inherited property factoryPresets to it. In their implementation they have an array of hard-coded presets, so they keep an index into it in order to set them.
1 2 3 4 5 6 7 8 9 10 11 |
@implementation AUv3FilterDemo { // C++ members need to be ivars; they would be copied on access if they were properties. FilterDSPKernel _kernel; BufferedInputBus _inputBus; AUAudioUnitPreset *_currentPreset; NSInteger _currentFactoryPresetIndex; NSArray<AUAudioUnitPreset *> *_presets; } @synthesize parameterTree = _parameterTree; @synthesize factoryPresets = _presets; |
So, for the interval MIDI plugin, I’ll do the same thing.
1 2 3 4 5 6 7 8 9 10 11 12 |
@implementation AUParamsPresetsAudioUnit { // C++ members need to be ivars; they would be copied on access if they were properties. IntervalPlugin *_intervalPlugin; AUParameter *intervalParam; AUAudioUnitPreset *_currentPreset; NSInteger _currentFactoryPresetIndex; NSArray<AUAudioUnitPreset *> *_presets; } @synthesize parameterTree = _parameterTree; @synthesize factoryPresets = _presets; |
Now, let’s set them up in initWithComponentDescription.
I defined kDefaultFactoryPreset to be 0 which I used to initialize _currentFactoryPresetIndex.
I create the _presets array (which factoryPresets points to) using the createPreset method.
The _currentPreset is then set to the first preset.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
_currentFactoryPresetIndex = kDefaultFactoryPreset; _presets = @[ [self createPreset:0 name:@"Minor Second"], [self createPreset:1 name:@"Major Second"], [self createPreset:2 name:@"Minor Third"], [self createPreset:3 name:@"Major Third"], [self createPreset:4 name:@"Fourth"], [self createPreset:5 name:@"Tritone"], [self createPreset:6 name:@"Fifth"], [self createPreset:7 name:@"Minor Sixth"], [self createPreset:8 name:@"Major Sixth"], [self createPreset:9 name:@"Minor Seventh"], [self createPreset:10 name:@"Major Seventh"], [self createPreset:11 name:@"Octave"] ]; _currentPreset = self.factoryPresets[_currentFactoryPresetIndex]; |
Cool. We’re set up now.
How does FilterDemo handle accessing the currentPreset?
Here is their code. Actually there is nothing we need to change in this.
Borrow it!
1 2 3 4 5 6 7 8 9 10 |
- (AUAudioUnitPreset *)currentPreset { if (_currentPreset.number >= 0) { NSLog(@"Returning Current Factory Preset: %ld\n", (long)_currentFactoryPresetIndex); return [_presets objectAtIndex:_currentFactoryPresetIndex]; } else { NSLog(@"Returning Current Custom Preset: %ld, %@\n", (long)_currentPreset.number, _currentPreset.name); return _currentPreset; } } |
What we can see here is that if the preset number is < 0, then the system considers it to be a User Defined Preset. We just return it since the system keeps track of it. How does FilterDemo handle setting the current preset? Once again, it checks to see if the preset number is a User Defined Preset or not - the handling is different. For a factory preset, it loops through the _preset array to find the matching preset, The parameters are retrieved from the tree and their values are set to the factory values. Then a bit of housekeeping to update _currentPreset and _currentFactoryPresetIndex.
For User Defined Presets, _currentPreset is simply set to the preset passed in. More on User Presets below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
- (void)setCurrentPreset:(AUAudioUnitPreset *)currentPreset { if (nil == currentPreset) { NSLog(@"nil passed to setCurrentPreset!"); return; } if (currentPreset.number >= 0) { // factory preset 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; // set factory preset as current _currentPreset = currentPreset; _currentFactoryPresetIndex = factoryPreset.number; NSLog(@"currentPreset Factory: %ld, %@\n", (long)_currentFactoryPresetIndex, factoryPreset.name); break; } } } else if (nil != currentPreset.name) { // set custom preset as current _currentPreset = currentPreset; NSLog(@"currentPreset Custom: %ld, %@\n", (long)_currentPreset.number, _currentPreset.name); } else { NSLog(@"setCurrentPreset not set! - invalid AUAudioUnitPreset\n"); } } |
What’s that “presetParameters” I’m using to set the value?
To actually define the factory preset values, I created a struct to hold the values. It is really simple in this plugin since there is only one parameter (the interval). But you can imagine that in a synth there would be a metric ton of them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static const UInt8 kNumberOfPresets = 12; static const NSInteger kDefaultFactoryPreset = 0; typedef struct FactoryPresetParameters { AUValue intervalValue; } FactoryPresetParameters; static const FactoryPresetParameters presetParameters[kNumberOfPresets] = { { 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 6 }, { 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, }; |
My modification to FilterDemo’s current preset setter is to change the AUParameter being updated of course.
1 2 3 4 5 6 |
... if (currentPreset.number == factoryPreset.number) { AUParameter *intervalParameter = [self.parameterTree valueForKey: @"intervalParameter"]; intervalParameter.value = presetParameters[factoryPreset.number].intervalValue; ... |
Try it out!
Choose a preset and you’ll set it updating!
Defining User presets
Your audio unit inherits this property:
1 |
@property (NS_NONATOMIC_IOSONLY, copy, nullable) NSDictionary<NSString *, id> *fullState; |
When you save a new preset, e.g. in AUM press the + in the Presets title bar, this property will be accessed. By default, the keys in this dictionary are: manufacturer, data, type, subtype, and version. What we need to do is add keys/values for our parameters to this dictionary. So, create a mutable dictionary, add to that and return it. You could just call setObject:forKey for each parameter, but adding a params dictionary is tidier if you have many parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (NSDictionary<NSString *,id> *)fullState { NSLog(@"calling: %s", __PRETTY_FUNCTION__ ); NSMutableDictionary *state = [[NSMutableDictionary alloc] initWithDictionary: super.fullState]; // you can do just a setObject:forKey on state, but in real life you will probably have many parameters. // so, add a param dictionary to fullState. NSDictionary<NSString*, id> *params = @{ @"intervalParameter": [NSNumber numberWithInt: intervalParam.value], }; state[@"fullStateParams"] = [NSKeyedArchiver archivedDataWithRootObject: params]; return state; } |
When the user preset is selected, the setter is called. So, grab the archived data you wrote in the getter, and then extract the parameter values. Here there is only one parameter, but you may have many.
1 2 3 4 5 6 7 |
- (void)setFullState:(NSDictionary<NSString *,id> *)fullState { NSLog(@"calling: %s", __PRETTY_FUNCTION__ ); NSData *data = (NSData *)fullState[@"fullStateParams"]; NSDictionary *params = [NSKeyedUnarchiver unarchiveObjectWithData:data]; intervalParam.value = [(NSNumber *)params[@"intervalParameter"] intValue]; } |
There is also a fullStateForDocument property for “global” properties such as master tuning. If you need this, define accessors for it using the same procedure I described for fullState.
To test, set an interval from the segmented control, then save it as a “user defined preset”. (Press the + in AUM’s title bar). Choose a factory preset, note the value, then press your user preset. It should have the value you saved. If not, pet the cat and grab another espresso.
Summary
There is a new API for using presets that is bridged to the v2 API for compatibility. It’s actually quite a bit easier now.
Resources
Apple’s v2 Preset documentation.
v2: Audio Units – How to correctly save and restore Audio Unit presets
Previous posts in this series
Audio Units (AUv3) MIDI extension – Part 0: Getting Started
Audio Units (AUv3) MIDI extension – Part 1: Parameters
Audio Units (AUv3) MIDI extension – Part 2: C++
This is the same repo as the previous blog post, but there is a new target named AUParamsPresets containing this new code.
Github repo