Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/detail/auv3/auv3_audiounit.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
// Subclasses (generated by build-helper) override this to provide plugin-specific info
- (AUAudioUnit *)createAudioUnitWithComponentDescription:(AudioComponentDescription)desc
error:(NSError **)error;
// The generated override must build its audio unit through this: the host's
// factory call and -loadView's self-bootstrap race, and only one audio unit may
// ever exist per view controller. `make` runs only if there is none yet.
- (AUAudioUnit *)createAudioUnitOnce:(ClapAUv3AudioUnit * (^)(NSError **))make error:(NSError **)error;
@end

@interface ClapAUv3AudioUnit : AUAudioUnit
Expand Down
44 changes: 41 additions & 3 deletions src/detail/auv3/auv3_audiounit.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2077,7 +2077,13 @@ - (BOOL)setGUISize:(uint32_t)width height:(uint32_t)height
// misbehaviour.
auto *gui = _impl->_plugin->_ext._gui;
if (gui->adjust_size) gui->adjust_size(_impl->_plugin->_plugin, &width, &height);
return gui->set_size(_impl->_plugin->_plugin, width, height) ? YES : NO;
if (!gui->set_size(_impl->_plugin->_plugin, width, height)) return NO;

// Keep the cached size in step: prepareGUIAndReturnWidth:height: hands it
// back, and that is how the view controller learns the adjusted size.
_impl->_guiWidth = width;
_impl->_guiHeight = height;
return YES;
}

- (void)setViewController:(ClapAUv3ViewController *)vc
Expand Down Expand Up @@ -2277,6 +2283,8 @@ @implementation ClapAUv3ViewController
CGSize _pluginSize;
// We already tried to create the audio unit ourselves (see _bootstrapAudioUnit).
BOOL _bootstrapAttempted;
// Serializes the two creation paths (see -createAudioUnitOnce:error:).
std::mutex _audioUnitMutex;
}

- (void)loadView
Expand Down Expand Up @@ -2309,6 +2317,10 @@ - (void)loadView

- (void)setAudioUnit:(ClapAUv3AudioUnit *)audioUnit
{
if (_audioUnit == audioUnit) return;
// Never drop a live audio unit by bare overwrite: releasing the last reference
// reaches plugin->terminate() with the GUI still created, which CLAP forbids.
if (_audioUnit) [self _destroyGUI];
_audioUnit = audioUnit;
// Establish the back-reference so the AU can return us from
// requestViewControllerWithCompletionHandler:
Expand Down Expand Up @@ -2362,6 +2374,22 @@ - (BOOL)_ownComponentDescription:(AudioComponentDescription *)outDesc
return NO;
}

// Both creation paths meet here: the host's factory call, which arrives on an
// XPC worker thread, and -loadView's self-bootstrap on the appex main thread.
// Unserialized they both see a nil audioUnit and build an audio unit, and the
// loser is then thrown away with its GUI still alive. Nothing under this lock
// ever waits on the main queue, so the losing path only stalls.
- (AUAudioUnit *)createAudioUnitOnce:(ClapAUv3AudioUnit * (^)(NSError **))make error:(NSError **)error
{
std::lock_guard<std::mutex> lock(_audioUnitMutex);
if (_audioUnit) return _audioUnit;

ClapAUv3AudioUnit *au = make(error);
if (!au) return nil;
self.audioUnit = au;
return au;
}

// Out-of-process AUv3: the viewbridge loads our view from inside
// -beginRequestWithExtensionContext:, before the host has asked the factory for
// an audio unit, and it captures preferredContentSize as the view is vended —
Expand Down Expand Up @@ -2565,10 +2593,20 @@ - (void)viewDidLayoutSubviews
[self.audioUnit setGUISize:(uint32_t)bounds.size.width height:(uint32_t)bounds.size.height];
}

// Ensure the CLAP plugin's subview fills the container
// adjust_size may have snapped the container bounds (fixed aspect ratio,
// step sizes), and the plugin laid out at the snapped size — so the subview
// has to get that size too, not the raw bounds.
CGRect childFrame = bounds;
uint32_t agreedWidth = 0, agreedHeight = 0;
if ([self.audioUnit prepareGUIAndReturnWidth:&agreedWidth height:&agreedHeight] && agreedWidth > 0 &&
agreedHeight > 0)
{
childFrame = CGRectMake(0, 0, agreedWidth, agreedHeight);
}

for (CLAPWRAP_ViewClass *subview in self.view.subviews)
{
subview.frame = bounds;
subview.frame = childFrame;
}
}
}
Expand Down
13 changes: 6 additions & 7 deletions src/detail/auv3/build-helper/build-helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,19 +481,18 @@ int main(int argc, char **argv)
cppf
<< "- (AUAudioUnit *)createAudioUnitWithComponentDescription:(AudioComponentDescription)desc\n"
<< " error:(NSError **)error {\n"
<< " // The view controller may have created it already, to answer a size\n"
<< " // query before the host got here (see -_bootstrapAudioUnit).\n"
<< " if (self.audioUnit) return self.audioUnit;\n"
<< " ClapAUv3AudioUnit *au = [[ClapAUv3AudioUnit alloc] initWithComponentDescription:desc\n"
<< " // -loadView may be bootstrapping one right now on the main thread, so the\n"
<< " // check and the build have to happen under the base class's lock.\n"
<< " return [self createAudioUnitOnce:^ClapAUv3AudioUnit *(NSError **err) {\n"
<< " return [[ClapAUv3AudioUnit alloc] initWithComponentDescription:desc\n"
<< " options:0\n"
<< " error:error\n"
<< " error:err\n"
<< " clapName:@\""
<< escapeObjCString(u.clapname) << "\"\n"
<< " clapId:@\""
<< escapeObjCString(u.clapid) << "\"\n"
<< " clapIndex:" << idx << "];\n"
<< " self.audioUnit = au;\n"
<< " return au;\n"
<< " } error:error];\n"
<< "}\n"
<< "@end\n\n";

Expand Down
13 changes: 6 additions & 7 deletions src/detail/auv3/templates/generated_auv3_entrypoints.hxx.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@
@implementation @AUV3_FACTORY_CLASS_NAME@
- (AUAudioUnit *)createAudioUnitWithComponentDescription:(AudioComponentDescription)desc
error:(NSError **)error {
// The view controller may have created it already, to answer a size
// query before the host got here (see -_bootstrapAudioUnit).
if (self.audioUnit) return self.audioUnit;
ClapAUv3AudioUnit *au = [[ClapAUv3AudioUnit alloc] initWithComponentDescription:desc
// -loadView may be bootstrapping one right now on the main thread, so the
// check and the build have to happen under the base class's lock.
return [self createAudioUnitOnce:^ClapAUv3AudioUnit *(NSError **err) {
return [[ClapAUv3AudioUnit alloc] initWithComponentDescription:desc
options:0
error:error
error:err
clapName:@"@AUV3_CLAP_NAME@"
clapId:@"@AUV3_CLAP_ID@"
clapIndex:0];
self.audioUnit = au;
return au;
} error:error];
}
@end
Loading