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

Building a signal generator

At a glance

Item Summary
Purpose Generates selectable waveforms in a custom real-time audio source node.
App architecture SwiftUI state flows through a Swift manager and Objective-C++ adapter to a C++ DSP kernel.
Main patterns Observable manager, language-bridge adapter, render callback, waveform strategy table, parameter ramping.
Project style Single app target organized by language boundary rather than feature folders.

Project structure

SignalGenerator/
├── SignalGeneratorApp.swift
├── ContentView.swift
├── AudioManager.swift
├── SignalGenerator.h
├── SignalGenerator.mm
├── SignalGeneratorKernel.h
├── ParameterRamp.h
└── WaveFunction.h

Structure observations

  • Swift owns presentation and engine composition; Objective-C++ is the narrow interoperability seam.
  • Header-only C++ types isolate sample generation, ramping, and waveform selection from UI concerns.

Overall architecture

Reference code

SignalGenerator/AudioManager.swift:32 — the manager connects the render callback to the engine graph.

let srcNode = AVAudioSourceNode(renderBlock: signalGenerator.renderBlock)
let mainMixer = engine.mainMixerNode
let output = engine.outputNode
signalGenerator.setSampleRate(outputFormat.sampleRate)
engine.attach(srcNode)
engine.connect(srcNode, to: mainMixer, format: inputFormat)
engine.connect(mainMixer, to: output, format: outputFormat)

Interpretation

AudioManager is the application façade. Parameter changes cross the Objective-C interface, while audio-frame production remains in C++ and is invoked by AVAudioEngine’s real-time callback.

Ownership and state

Ownership evidence

SignalGenerator/AudioManager.swift:10 — the manager retains the bridge and engine as private implementation objects.

@Observable class AudioManager {
    // ...
}
Owner Object or state Relationship Mutation authority
SignalGeneratorApp AudioManager Creates with @State and binds into the view AudioManager and bound UI properties
AudioManager Engine, bridge, UI-facing parameters Creates and retains AudioManager
SignalGenerator Kernel and pending parameter struct Embedded Objective-C++ ivars Bridge setters/render block
SignalGeneratorKernel Phase, ramps, waveform function Value-owned DSP state Kernel methods on render path

Class and protocol design

Type Responsibility Depends on
AudioManager Map observable controls to DSP setters and compose the audio graph AVAudioEngine, SignalGenerator
SignalGenerator Present a Swift/Objective-C-compatible API and render block C++ kernel
SignalGeneratorKernel Produce the next sample from smoothed parameters ParameterRamp, WaveFunction
ParameterRamp Move parameter values without abrupt discontinuities C++ numeric state
WaveFunction Select waveform calculation by enum Function-pointer table

Access control

Symbol Access Verified effect Likely rationale
AudioManager.signalGenerator / engine private UI code cannot rewire the graph or access DSP internals. Keep graph invariants centralized.
SignalGenerator header methods Objective-C interface Swift sees only render/setup/parameter operations. Make the bridge deliberately narrow.
Kernel implementation state C++ private Callers use setup/update/sample methods rather than phase fields. Protect DSP invariants.
Waveform description witnesses public Satisfy public protocol requirements used by the UI. Language conformance requirement, not a public framework architecture.
Swift app types implicit internal Remain within the app module. The target is an app, not a library.

Reference code

SignalGenerator/SignalGenerator.mm:19 — C++ ownership is hidden inside the adapter implementation.

@implementation SignalGenerator {
    // ...
}
// ...
@end

Logic ownership and placement

Logic Owning type or file Placement rationale
Control state and engine lifecycle AudioManager Swift-observable application layer.
Inter-language conversion and callback SignalGenerator.mm Only file that needs Objective-C and C++ together.
Per-frame DSP SignalGeneratorKernel.h Real-time logic stays independent of UI/runtime bridging.
Smoothing ParameterRamp.h Reusable numerical responsibility.
Waveform algorithms WaveFunction.h Compact strategy selection.

Design patterns

Pattern Source evidence Purpose or tradeoff
Observable façade SignalGenerator/AudioManager.swift:10 Gives UI one coherent audio API.
Adapter/bridge SignalGenerator/SignalGenerator.mm:45 Exposes a C++ kernel as an AVAudio source-node block.
Strategy table SignalGenerator/WaveFunction.h:13 Selects waveform behavior without a class hierarchy.
Parameter ramp SignalGenerator/ParameterRamp.h:12 Reduces artifacts from control changes.
Real-time callback SignalGenerator/SignalGenerator.mm:51 Lets AVAudioEngine request exactly the frames it needs.

Naming conventions

  • Manager and kernel suffixes communicate layer and role: AudioManager, SignalGeneratorKernel.
  • Setters mirror domain controls: setFrequency, setAmplitude, setWaveform, setSampleRate.
  • File extensions expose the language boundary: .swift, .mm, and header-only C++ .h.
  • Waveform names domain choices; WaveFunction names executable strategies.

Architecture takeaways

  • Put Objective-C++ in a narrow adapter so most app code remains idiomatic Swift or C++.
  • Keep engine graph ownership in one manager and per-frame work in a small kernel.
  • Copy UI parameters into render-owned state once per callback, not through repeated object lookup per sample.
  • Use access control to prevent presentation code from mutating the audio graph directly.

Source map

Source file Relevant symbols
SignalGenerator/SignalGeneratorApp.swift App-owned AudioManager
SignalGenerator/AudioManager.swift Observable state and engine graph
SignalGenerator/SignalGenerator.h Bridge contract
SignalGenerator/SignalGenerator.mm Bridge implementation and render block
SignalGenerator/SignalGeneratorKernel.h DSP kernel
SignalGenerator/ParameterRamp.h Parameter smoothing
SignalGenerator/WaveFunction.h Waveform functions