Sample CodeiOS, iPadOS, Mac Catalyst, macOSReviewed 2026-07-21View on Apple Developer

Creating custom audio effects

At a glance

Item Summary
Purpose Implements and hosts an AUv3 low-pass filter with parameter automation, presets, custom UI, and real-time DSP.
App architecture Platform hosts share a Swift manager/UI and Audio Unit implementation that bridges through Objective-C++ to a C++ DSP kernel.
Main patterns Shared multi-target core, façade, delegate, framework subclass, adapter, parameter tree, real-time kernel.
Project style iOS/macOS app, extension, and framework targets reuse a substantial Shared tree.

Project structure

AUv3Filter/
├── iOS/
│   ├── AUv3Filter/
│   ├── AUv3FilterExtension/
│   └── AUv3FilterFramework/
├── macOS/
│   ├── AUv3Filter/
│   ├── AUv3FilterExtension/
│   └── AUv3FilterFramework/
└── Shared/
    ├── AudioUnitManager.swift
    ├── AUv3FilterDemoViewController.swift
    ├── AudioUnit/
    │   ├── AUv3FilterDemo.swift
    │   ├── AUv3FilterDemoParameters.swift
    │   └── Support/{FilterDSPKernelAdapter.*,FilterDSPKernel.hpp}
    └── Support/{Audio,User Interface}/

Structure observations

  • The archive is deliberately multi-target: host apps, AU extensions, and frameworks exist for both iOS and macOS.
  • Conditional type aliases and shared UI/service files minimize platform duplication; target-specific files provide lifecycle and factory glue.

Overall architecture

Reference code

Shared/AudioUnitManager.swift:104 — the manager registers, instantiates, binds, and connects the custom unit.

public init() {
    viewController = loadViewController()
    AUAudioUnit.registerSubclass(AUv3FilterDemo.self,
                                 as: componentDescription,
                                 name: componentName,
                                 version: UInt32.max)
    AVAudioUnit.instantiate(with: componentDescription) { audioUnit, error in
        self.audioUnit = audioUnit?.auAudioUnit as? AUv3FilterDemo
        self.connectParametersToControls()
        self.playEngine.connect(avAudioUnit: audioUnit)
    }
}

Interpretation

AudioUnitManager is the host-facing façade. The AU parameter tree is the control contract shared by host and plug-in UI. The adapter is the language and real-time boundary around the C++ kernel.

Ownership and state

Ownership evidence

Shared/AudioUnitManager.swift:29 — the manager exposes selected state but privately owns the audio-unit and playback infrastructure.

public class AudioUnitManager {
    private var audioUnit: AUv3FilterDemo?
    public weak var delegate: AUManagerDelegate?
    public private(set) var viewController: AUv3FilterDemoViewController!
    private let playEngine = SimplePlayEngine()
}
Owner Object or state Relationship Mutation authority
Platform MainViewController AudioUnitManager and host controls Creates and delegates Host UI for presentation; manager for AU state
AudioUnitManager Unit, plug-in view, parameters, observer token, play engine Creates/loads/retains Manager
AUv3FilterDemo Parameter helper, buses, preset state, adapter Audio Unit implementation owner AU hooks and parameter observers
FilterDSPKernelAdapter Kernel and buffered input bus Embedded C++ ivars Render/resource methods

Class and protocol design

Type Responsibility Depends on
AudioUnitManager Host-facing lifecycle, parameter binding, playback, plug-in UI loading AUv3 type and SimplePlayEngine
AUManagerDelegate Notify host controls when AU parameters change Two scalar callbacks
AUv3FilterDemo Implement AUAudioUnit buses, presets, parameters, rendering Base AU and adapter
AUv3FilterDemoViewController Bind custom filter UI to parameter tree AU parameters and FilterViewDelegate
FilterDSPKernelAdapter Make C++ DSP accessible to Swift/AU code FilterDSPKernel, BufferedInputBus
SimplePlayEngine Build host test graph and drive source audio/MIDI AVAudioEngine

Access control

Symbol Access Verified effect Likely rationale
Shared host/AU/framework types public Reused across app, extension, and framework module boundaries. Required by the multi-target layout.
AudioUnitManager.audioUnit, parameters, observer token, play engine private Host controllers use the façade rather than AU internals. Preserve binding and lifecycle invariants.
viewController public private(set) Hosts embed it but cannot replace the loaded plug-in UI. Keep construction authority in the manager.
Preset.audioUnitPreset and initializer fileprivate UI sees a wrapper while only the manager file unwraps Core Audio state. Avoid leaking Core Audio types upward.
AU parameter/preset backing state private External code uses overrides and parameter tree. Protect Audio Unit state.
C++ kernel adapter interface only Swift never sees C++ types. Isolate language and real-time details.

Reference code

Shared/AudioUnitManager.swift:13 — a public UI model wraps a file-confined Core Audio preset.

public struct Preset {
    fileprivate init(preset: AUAudioUnitPreset) { audioUnitPreset = preset }
    fileprivate let audioUnitPreset: AUAudioUnitPreset
    public var number: Int { audioUnitPreset.number }
    public var name: String { audioUnitPreset.name }
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Platform lifecycle and controls iOS/ and macOS/ host controllers Platform-specific UI glue.
AU host orchestration AudioUnitManager Shared façade across platforms.
Plug-in control UI AUv3FilterDemoViewController / FilterView Custom view and gesture logic.
Parameter definitions/observation AUv3FilterDemoParameters One canonical control contract.
Audio Unit lifecycle AUv3FilterDemo Framework subclass responsibility.
Real-time bridge and DSP FilterDSPKernelAdapter.mm / FilterDSPKernel.hpp Language seam and allocation-free render core.

Design patterns

Pattern Source evidence Purpose or tradeoff
Shared-core multi-target architecture Shared/AudioUnitManager.swift:30 Reuses behavior across six platform/packaging targets.
Façade Shared/AudioUnitManager.swift:30 Shields host UI from Audio Unit discovery and graph wiring.
Delegate Shared/AudioUnitManager.swift:24 Updates platform controls without coupling manager to UIKit/AppKit.
Adapter Shared/AudioUnit/Support/FilterDSPKernelAdapter.h:14 Exposes C++ DSP through Objective-C to Swift.
Parameter tree Shared/AudioUnit/AUv3FilterDemoParameters.swift:59 Provides one automation/state contract to host, UI, and kernel.
Real-time render block Shared/AudioUnit/Support/FilterDSPKernelAdapter.mm:95 Pulls input and processes events inside the AU callback.

Naming conventions

  • Manager, ViewController, Parameters, Adapter, and Kernel suffixes reveal layer roles.
  • AUv3FilterDemo is reused as the component prefix across app, extension, framework, and implementation types.
  • Target folders use platform first, then product role; reusable code lives under Shared.
  • Parameter names use domain vocabulary: cutoff, resonance, factoryPresets.

Architecture takeaways

  • Organize a cross-platform Audio Unit sample around a shared core and thin target adapters.
  • Make the parameter tree the common contract between host UI, plug-in UI, AU state, and DSP.
  • Keep Swift-to-C++ interop inside Objective-C++ and expose only an Objective-C adapter header.
  • Public access is justified at target/framework boundaries; private/fileprivate state protects host and render invariants.
  • Treat the host and extension as separate architectures connected by Audio Unit contracts.

Source map

Source file Relevant symbols
Shared/AudioUnitManager.swift Host façade, delegate, preset wrapper
Shared/Support/Audio/SimplePlayEngine.swift Host AVAudioEngine graph
Shared/AUv3FilterDemoViewController.swift Plug-in parameter UI
Shared/AudioUnit/AUv3FilterDemo.swift AU implementation
Shared/AudioUnit/AUv3FilterDemoParameters.swift Parameter tree
Shared/AudioUnit/Support/FilterDSPKernelAdapter.h Adapter contract
Shared/AudioUnit/Support/FilterDSPKernelAdapter.mm C++ ownership and render block