1

I would like to embed a custom view created by an Audio Unit into a window that uses Auto Layout. After trying dozens of combinations, I still haven't found a way to do this which works with a large selection of Audio Units.

The goal would be to create a container NSView object with the Audio Unit View as a subview, and to set constraints in such a way that the size of the container matches that of the AU view, including when the latter resizes itself. The container should have translatesAutoresizingMaskIntoConstraints set to NO, so it plays nice with the rest of the window.

My first attempt was to create two constraints simply forcing the size of both (container and AU) views to be equal. This fails as the AU view then collapses to a very small size.

Another attempt was to create fixed size constraints for the container view that are initialised to match the AU view, to listen for NSViewFrameDidChangeNotification for the AU view, and to adjust the fixed sizes as required. This crashes as soon as the AU view wants to resize itself. The reason is that when I increase the size of the container to match the AU view, the autoresizing mask constraints instruct the AU view to increase its size again, causing an infinite loop.

What works best so far is to turn translatesAutoresizingMaskIntoConstraints off for the AU view and to set the size of the container by listening to frame change notifications. This appears to work for all AUv2 Audio Units, but for AUv3 Audio Units (in particular Apple's demo AUv3), I always get a view size of (1,1) which is obviously useless. I would be very grateful for any insight into how to make this work...

Martin
  • 201
  • 1
  • 7
  • When you say old style do you mean using `kAudioUnitProperty_CocoaUI` from an `AudioUnit` instance or `AUGenericView`? – sbooth Jun 22 '20 at 13:21
  • I was referring to "old style" as far as Auto Layout is concerned in the sense that the views I get from an Audio Unit always come with ```translatesAutoresizingMaskIntoConstraints``` set to ```YES```. The views themselves are obtained by calling ```[AUAudioUnit requestViewController:]```. – Martin Jun 22 '20 at 13:53

1 Answers1

0

Based on the sample project you provided this should work:

@interface MyViewController ()
{
    NSLayoutConstraint *widthConstraint, *heightConstraint;
}
@end

@implementation MyViewController


-(void)setFormatForBus:(AUAudioUnitBus*)bus
{
    AudioStreamBasicDescription fmt = {44100, kAudioFormatLinearPCM, kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked, 4, 1, 4, 2, 32, 0};  // Stereo audio on each bus
    AVAudioFormat* format = [[AVAudioFormat alloc] initWithStreamDescription:&fmt];
    NSError* error = nil;
    if (![bus setFormat:format error:&error]) {
        NSLog(@"Couldn't set the format for bus... %@", error);
        return;
    }
    
    bus.enabled = YES;
}

-(void)doLoad:(AVAudioUnit*)node controller:(NSViewController*)viewController
{
    NSView* auView = nil;
    if (viewController == nil) {
        auView = [[AUGenericView alloc] initWithAudioUnit:node.audioUnit displayFlags:(AUViewPropertiesDisplayFlag | AUViewParametersDisplayFlag)];
        auView.frame = self.view.frame;
        [self.view addSubview:auView];
    }
    else {
        self.auViewController = viewController;
        auView = self.auViewController.view;
        auView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.view addSubview:auView];
    }

    NSSize size = _auViewController.view.frame.size;

    widthConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:size.width];
    heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:size.height];

    [self.view addConstraints:@[widthConstraint, heightConstraint]];

    NSLayoutConstraint* top = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:auView
                                                           attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
    NSLayoutConstraint* bottom = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:auView
                                                              attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
    NSLayoutConstraint* left = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:auView
                                                            attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
    NSLayoutConstraint* right = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:auView
                                                             attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];

    [self.view addConstraints:@[top, bottom, left, right]];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    NSArray* list = [[AVAudioUnitComponentManager sharedAudioUnitComponentManager] componentsPassingTest: ^BOOL(AVAudioUnitComponent *comp, BOOL *stop) {
        return [comp.typeName containsString:@"Effect"];
    }];
    
    AVAudioUnitComponent* component = nil;
    for (AVAudioUnitComponent* comp in list) {
        if ([comp.name containsString:@"MultibandCompressor"])  // That allows itself to be squashed. Also, it doesn't resize itself when "details" is toggled
//        if ([comp.name containsString:@"FilterDemo"]) // That one is squashed both vertically and horizontally... (Apple's sample AUv3 Audio Unit, needs to be installed first.)
//        if ([comp.name containsString:@"GraphicEQ"])  // That one is squashed vertically
//                    if ([comp.name containsString:@"AUDelay"])
            component = comp;
    }
    
    [AVAudioUnit instantiateWithComponentDescription:component.audioComponentDescription options:0
                                   completionHandler:^(__kindof AVAudioUnit *audioUnit, NSError *error) {
        if (error) {
            NSLog(@"Error instantiating AU: %@", error);
            return;
        }
        
        self.audioUnit = audioUnit;
        AUAudioUnitBusArray* inputBusses = audioUnit.AUAudioUnit.inputBusses;
        AUAudioUnitBusArray* outputBusses = audioUnit.AUAudioUnit.outputBusses;
        
        if (!inputBusses.count || !outputBusses.count) {
            NSLog(@"No busses...");
            return;
        }
        
        [self setFormatForBus:[inputBusses objectAtIndexedSubscript:0]];
        [self setFormatForBus:[outputBusses objectAtIndexedSubscript:0]];
        
        NSError* AUerror = nil;
        if (![audioUnit.AUAudioUnit allocateRenderResourcesAndReturnError:&AUerror]){
            NSLog(@"Error allocating resources: %@", AUerror);
            return;
        }
        [audioUnit.AUAudioUnit requestViewControllerWithCompletionHandler:^(NSViewController* viewController) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self doLoad:audioUnit controller:viewController];
            });
        }];
    }];
}

- (void)viewWillLayout
{
    [super viewWillLayout];
    if(_auViewController != nil) {
        [self.view removeConstraints:@[widthConstraint, heightConstraint]];
        NSSize size = _auViewController.view.frame.size;
        widthConstraint.constant = size.width;
        heightConstraint.constant = size.height;
        [self.view addConstraints:@[widthConstraint, heightConstraint]];
    }
}

@end
sbooth
  • 16,646
  • 2
  • 55
  • 81
  • It works great for AUv2 Audio Units, but doesn't seem to work for Apple's demo AUv3 AU (download and run the compiled version at (hairer.org/AUv3Filter.zip) to register it with the OS and then select "FilterDemo" in the code). What happens there is that the size of the view remains stuck at (1,1) and never changes... – Martin Jun 23 '20 at 08:46
  • It does seem problematic to retrieve the default view size for AUv3 audio units. A freshly-allocated view controller returns (1,1) or some other size that doesn't make much sense as you say. Apple's AUv3 Host example exhibits the same behavior (1x1 size), it just doesn't use auto layout. You could probably determine acceptable sizes using `supportedViewConfigurations` iteratively but that won't necessarily give you the preferred size. This may be worth opening an incident with Apple DTS if you want an official answer. – sbooth Jun 23 '20 at 12:48
  • I see, that's rather disappointing... I can see from Apple's sample code that their view controller implements a `viewConfigurations` property that returns an array of supported configurations. That property is never actually referred to by the rest of their code, so I assume that this is part of the implementation of the official API... – Martin Jun 23 '20 at 13:25
  • ... Do you know if there's any way to query this? Simply calling `viewConfigurations` on the controller returned by `requestViewController` doesn't work, so the proxy they use for their IPC magic doesn't expose this. I know about `supportedViewConfigurations` and I could just pass it a huge array with a 10x10 grid covering the whole screen, but this is ugly as hell and really amounts to just flying blind. – Martin Jun 23 '20 at 13:26
  • `viewConfigurations` for the consumer is a method on `AUAudioUnit`. You pass it an array of `AUAudioUnitViewConfiguration` of various sizes and it reports which ones it can support. – sbooth Jun 23 '20 at 15:08
  • Yes, that sort of goes the wrong way around... I'll make use of one of my Apple support incident jokers as soon as they reopen next week and I'll report back if they have anything useful to say. (I have a suspicion that this is basically a bug in their AUv3Filter demo, but since most AUv3 developers will use this as a starting point, that would be pretty bad...) – Martin Jun 23 '20 at 15:24
  • Since WWDC is this week you could also post a question in the new Apple Developer Forums using the `Core Audio` tag. – sbooth Jun 24 '20 at 11:41
  • 1
    Apple confirmed that there is a bug in their demo AUv3: they simply forgot to set the `preferredContentSize` property on their controller... – Martin Jul 05 '20 at 23:39