So I'm working on an instrument that has close to 100 parameters, and following the patterns in the examples I could find got quickly unwieldy. Wondering why one could not simply bind to key paths in the parameterTree, I eventually went down an unpleasant rabbit hole in finding out these KVO notifications do not come back in the application's preferred thread.
Any way we go, this seems a really un-idiomatic way to write UI code in Cocoa.
So.. I came up with this for my controller, which I don't like for several reasons, although I like it much more than dealing with outlets and actions for every control in the view.
Am I doing something unnecessarily the hard way?
Also, I'd think two things in XCode/AudioToolkit would be extremely valuable: 1) The ability to define a parameterTree as part of your xib/storyboard like you can with CoreData schemas, and 2) some sort of official object controller class provided which does this bridging.
@synthesize parameters = _parameters;
@synthesize parameterTree = _parameterTree;
- (void)dealloc {
if (observer != nil) {
[_parameterTree removeParameterObserver:observer];
observer = nil;
}
}
- (p2fmAudioUnit *)getAudioUnit {
return _audioUnit;
}
- (void)setAudioUnit:(p2fmAudioUnit *)audioUnit {
_audioUnit = audioUnit;
dispatch_async(dispatch_get_main_queue(), ^{
if ([self isViewLoaded]) {
[self connectView];
}
});
}
- (void) viewDidLoad {
[super viewDidLoad];
self.preferredContentSize = self.view.frame.size;
if (_audioUnit) {
[self connectView];
}
}
- (void)connectView {
AUParameterTree *tree = _audioUnit.parameterTree;
if (tree == nil) {
return;
}
self.parameterTree = tree;
[self refresh];
[_audioUnit addObserver:self
forKeyPath:@"allParameterValues"
options:NSKeyValueObservingOptionNew
context:NULL];
__weak AudioUnitViewController *weakSelf = self;
observer = [_parameterTree tokenByAddingParameterObserver:^(AUParameterAddress address, AUValue value) {
__strong AudioUnitViewController *strongSelf = weakSelf;
AUParameter *param = [strongSelf->_parameterTree parameterWithAddress:address];
if (param != nil) {
dispatch_async(dispatch_get_main_queue()
, ^{
[strongSelf refreshParam:param];
});
}
}];
}
- (void)refresh {
for (AUParameter *param in [_parameterTree allParameters]) {
[self refreshParam:param];
}
}
- (void)refreshParam:(AUParameter *)param {
NSArray *keys = [[param keyPath] componentsSeparatedByString:@"."];
NSUInteger len = [keys count];
if (len < 1) {
return;
}
NSMutableDictionary *dict = _parameters;
if (dict == nil) {
self.parameters = [[NSMutableDictionary alloc] init];
dict = _parameters;
}
for (NSUInteger i = 0; i < (len-1); i++) {
NSString *key = [keys objectAtIndex:i];
NSMutableDictionary *node = [dict objectForKey:key];
if (node == nil) {
node = [[NSMutableDictionary alloc] init];
[dict setValue:node forKey:key];
}
dict = node;
}
NSString *key = [keys lastObject];
BOOL addObserver = NO;
if ([dict objectForKey:key] == nil) {
addObserver = YES;
}
[dict setValue:@(param.value) forKey:key];
if (addObserver) {
[dict addObserver:self
forKeyPath:key
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:(void *)param];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<nskeyvaluechangekey,id> *)change
context:(void *)context {
if (object == _audioUnit && [keyPath isEqualToString:@"allParameterValues"]) {
dispatch_async(dispatch_get_main_queue()
, ^{
[self refresh];
});
} else if (context != NULL) {
id p = (__bridge id)context;
if ([p isKindOfClass:[AUParameter class]]) {
AUParameter *param = p;
AUValue new = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
AUValue old = [[change valueForKey:NSKeyValueChangeOldKey] floatValue];
if (new != old) {
[param setValue:new originator:observer];
}
}
}
}