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

Using voice processing

At a glance

Item Summary
Purpose Demonstrates voice-processing I/O, bypass behavior, recording/playback, and live power meters.
App architecture A UIKit controller owns session/UI lifecycle while an AudioEngine façade owns nodes, buffers, recording, and taps.
Main patterns Screen/service split, graph owner, read-only provider protocol, notifications, focused signal-processing helper.
Project style Flat single-target sample with audio service and small metering view/model helpers.

Project structure

AVEchoTouch/
├── ViewController.swift
├── AudioEngine.swift
├── LevelMeterView.swift
├── PowerMeter.swift
├── MeterTable.swift
└── Audio/
    ├── Synth.aif
    └── sampleVoice*.wav

Structure observations

  • AudioEngine centralizes AVAudioEngine graph/lifecycle behavior; the view controller retains AVAudioSession interruption and route UI policy.
  • Meter calculation (PowerMeter/MeterTable) is separate from meter drawing (LevelMeterView).

Overall architecture

Reference code

AVEchoTouch/AudioEngine.swift:95 — one service configures voice processing, node routing, recording, and metering taps.

func setup() {
    let input = avAudioEngine.inputNode
    try? input.setVoiceProcessingEnabled(true)
    let mainMixer = avAudioEngine.mainMixerNode
    avAudioEngine.connect(fxPlayer, to: mainMixer, format: fxBuffer.format)
    avAudioEngine.connect(speechPlayer, to: mainMixer, format: speechBuffer.format)
    avAudioEngine.connect(recordedFilePlayer, to: mainMixer, format: voiceIOFormat)
    input.installTap(onBus: 0, bufferSize: 256, format: voiceIOFormat) { buffer, _ in
        self.voiceIOPowerMeter.process(buffer: buffer)
    }
}

Interpretation

The screen coordinates user-facing lifecycle and system notifications. AudioEngine is authoritative for graph objects and recording/playback state. Taps feed focused meter objects that expose only display-ready levels.

Ownership and state

Ownership evidence

AVEchoTouch/ViewController.swift:71 — the screen constructs the service, injects its meter providers, then starts it.

audioEngine = try AudioEngine()
speechMeter.levelProvider = audioEngine.speechPowerMeter
fxMeter.levelProvider = audioEngine.fxPowerMeter
voiceIOMeter.levelProvider = audioEngine.voiceIOPowerMeter
setupAudioSession(sampleRate: audioEngine.voiceIOFormat.sampleRate)
audioEngine.setup()
audioEngine.start()
Owner Object or state Relationship Mutation authority
ViewController AudioEngine, outlets, notification response Creates and coordinates View controller for UI/session response
AudioEngine Engine, nodes, buffers, file, recording flag Creates and retains AudioEngine methods/taps
PowerMeter Per-channel power samples and conversion tables Owns calculation state Tap-driven process methods
LevelMeterView Display link and LED layers Owns rendering state View; reads provider only

Class and protocol design

Type Responsibility Depends on
ViewController Session setup, interruption/route recovery, UI commands AudioEngine
AudioEngine Graph, voice-processing mode, record/play lifecycle AVAudioEngine and nodes
AudioLevelProvider Read-only meter capability AudioLevels value
PowerMeter Calculate RMS/peak values with Accelerate PCM buffers, MeterTable
LevelMeterView Poll a provider and render LEDs AudioLevelProvider

Access control

Symbol Access Verified effect Likely rationale
Engine/nodes/buffers/file flags private The screen cannot rewire nodes or partially change recording resources. Preserve graph and lifecycle invariants.
voiceIOFormat, isRecording, meter objects public private(set) Consumers read/configure UI from them but cannot replace them. Expose outputs while retaining authority.
ViewController.audioEngine private Other screens cannot operate this screen’s service. Screen-scoped ownership.
PowerMeter values/tables/calculator private Consumers see only normalized levels. Hide signal-processing state.
Types mostly implicit internal App-local collaboration despite a few explicit public properties. Single app target.

Reference code

AVEchoTouch/AudioEngine.swift:24 — readable service outputs keep external setters closed.

public private(set) var voiceIOFormat: AVAudioFormat
public private(set) var isRecording = false
public private(set) var speechPowerMeter = PowerMeter()
public private(set) var fxPowerMeter = PowerMeter()
public private(set) var voiceIOPowerMeter = PowerMeter()

Logic ownership and placement

Logic Owning type or file Placement rationale
UI/session notifications ViewController Presentation and app lifecycle policy.
Voice-processing graph and bypass AudioEngine Same owner as input/output nodes.
Recording and recorded playback AudioEngine Coupled file/node state.
RMS/peak calculation PowerMeter Focused Accelerate-based transformation.
Power-to-display conversion MeterTable Reusable lookup helper.
LED rendering LevelMeterView View-only concern.

Design patterns

Pattern Source evidence Purpose or tradeoff
Audio façade AVEchoTouch/AudioEngine.swift:11 Prevents UIKit from manipulating graph objects directly.
Provider protocol AVEchoTouch/LevelMeterView.swift:16 Meter view accepts any readable level source.
Observer AVEchoTouch/ViewController.swift:35 Responds to interruptions, routes, and media-service resets.
Signal-processing helper AVEchoTouch/PowerMeter.swift:37 Isolates format conversion and vDSP calculations.
Tap-based stream processing AVEchoTouch/AudioEngine.swift:112 Samples live node buffers without coupling meter rendering to the graph.

Naming conventions

  • Framework wrapper uses the direct role name AudioEngine; views use UIKit ViewController/View suffixes.
  • Commands state behavior: bypassVoiceProcessing, toggleRecording, speechPlayerPlay.
  • Readable state follows is naming: isRecording, isPlaying, isNewRecordingAvailable.
  • Meter types separate raw responsibility (PowerMeter) from display (LevelMeterView).

Architecture takeaways

  • Give one service ownership of the entire AVAudioEngine graph and recording lifecycle.
  • Expose meter data through a tiny read-only protocol rather than sharing audio nodes.
  • Keep session interruption policy at the app/screen boundary and graph recovery in the audio service.
  • Separate signal calculation from visualization so each can evolve independently.

Source map

Source file Relevant symbols
AVEchoTouch/ViewController.swift Service ownership, session and notification handling
AVEchoTouch/AudioEngine.swift Voice-processing graph, recording, playback
AVEchoTouch/PowerMeter.swift PCM power calculation
AVEchoTouch/LevelMeterView.swift Provider protocol and rendering
AVEchoTouch/MeterTable.swift dB lookup conversion