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
flowchart LR
Host["iOS/macOS host UI"] --> Manager["AudioUnitManager"]
Manager --> Unit["AUv3FilterDemo"]
Manager --> Play["SimplePlayEngine"]
Manager --> PluginUI["AUv3FilterDemoViewController"]
PluginUI --> Params["AUParameterTree"]
Unit --> Params
Unit --> Adapter["FilterDSPKernelAdapter ObjC++"]
Adapter --> Kernel["FilterDSPKernel C++"]
Play --> Graph["AVAudioEngine graph"]
Graph --> Unit
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
classDiagram
MainViewController *-- AudioUnitManager : creates
AudioUnitManager *-- SimplePlayEngine : owns
AudioUnitManager *-- AUv3FilterDemo : instantiated unit
AudioUnitManager *-- AUv3FilterDemoViewController : loads
AUv3FilterDemo *-- AUv3FilterDemoParameters : owns
AUv3FilterDemo *-- FilterDSPKernelAdapter : owns via base
FilterDSPKernelAdapter *-- FilterDSPKernel : C++ ivar
AudioUnitManager o-- AUManagerDelegate : weak
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 |