Skip to content

[AUv3] Factory-created instances retain a null AUAudioUnit pointer #157

Description

@spkfb

[AUv3] Factory-created instances retain a null AUAudioUnit pointer

Summary

The standard AUv3 factory path creates the C++ wrapper with a null Objective-C AUAudioUnit reference and does not appear to update that reference after the actual audio unit instance has been created.

As a result, the wrapper may continue sending messages to nil for operations that depend on the real AUAudioUnit instance.

Severity

P0 — Blocking

This affects the standard AUv3 extension instantiation path.

Affected area

modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm

Current behaviour

The factory path constructs the C++ wrapper before the Objective-C audio unit exists:

auto* cpp = new AudioPluginProcessorAUv3 (nil, desc, 0, error);

The wrapper is initialized with nil, after which the Objective-C AUAudioUnit instance is created and associated with the wrapper.

The wrapper's stored AUAudioUnit reference does not appear to be replaced with the newly created instance before initialization-dependent methods are used.

Potentially affected calls include:

[au maximumFramesToRender]
[au musicalContextBlock]
[au transportStateBlock]
[au scheduleMIDIEventBlock]

Impact

Depending on the code path, this may cause:

  • resource allocation to use invalid defaults;
  • MIDI output scheduling to be unavailable;
  • musical context and transport state to be unavailable;
  • incorrect maximum render-frame handling;
  • an AUv3 instance that initializes only partially.

Objective-C messages sent to nil do not necessarily crash, which can make this issue difficult to detect while still leaving the audio unit in an invalid state.

Steps to reproduce

  1. Build a YUP plugin as an AUv3 extension.
  2. Instantiate it through the standard AUAudioUnitFactory path.
  3. Add assertions or logging around the wrapper's stored AUAudioUnit reference.
  4. Allocate render resources.
  5. Query transport, musical context, maximum render frames, or MIDI scheduling.

Actual result

The C++ wrapper may still hold a null AUAudioUnit reference after the real audio unit has been created.

Expected result

The C++ wrapper must receive the final AUAudioUnit instance before any initialization that depends on it.

Initialization should run once, after both sides of the bridge are fully connected.

Suggested fix

Separate construction from attachment and initialization:

auto* cpp = new AudioPluginProcessorAUv3 (desc, options, error);
auto* audioUnit = [[YUPAudioUnit alloc] initWithComponentDescription:desc
                                                             options:options
                                                               error:error];

cpp->attachAudioUnit(audioUnit);
YUPAudioUnit::setThis(audioUnit, cpp);

if (!cpp->initialize())
{
    // Return an error and destroy the partially created instance.
}

Also consider:

  • asserting that au != nil in all methods that require it;
  • preventing initialize() from being called more than once;
  • making incomplete initialization fail explicitly.

Acceptance criteria

  • The wrapper stores the real AUAudioUnit instance before initialization.
  • Initialization runs exactly once.
  • maximumFramesToRender is read from the correct instance.
  • Transport and musical-context callbacks are obtained from the correct instance.
  • MIDI scheduling uses the correct instance.
  • Factory instantiation succeeds in an AUv3 host.
  • A regression test verifies that the stored AUAudioUnit reference is non-null.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions