0 Replies
      Latest reply on May 3, 2019 3:49 PM by Paul Forgey
      Paul Forgey Level 1 Level 1 (0 points)

        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];
                    }
                }
            }
        }