Capturing stereo audio from built-In microphones
At a glance
| Item |
Summary |
| Purpose |
Selects built-in microphone data sources and polar patterns, then records, meters, plays, and shares stereo audio. |
| App architecture |
A UIKit controller owns an audio façade and delegates metering and playback completion to focused collaborators. |
| Main patterns |
Controller/service split, weak delegate, protocol adapter, explicit state enum, value model for stereo layout. |
| Project style |
Single target grouped into Audio, Model, Extensions, and custom view files. |
Project structure
StereoAudioCapture/
├── MainViewController.swift
├── Audio/
│ ├── AudioController.swift
│ └── MeterTable.swift
├── Model/StereoLayout.swift
├── StereoLevelMeterView.swift
├── StereoLayoutView.swift
└── Extensions/
├── FileManagerExtensions.swift
└── UIViewExtensions.swift
Structure observations
AudioController is the single boundary around audio session, recorder, player, and file state.
- Meter rendering and microphone-layout rendering are separate custom views;
StereoLayout translates framework orientations into display cases.
Overall architecture
flowchart LR
User["User"] --> VC["MainViewController"]
VC --> Audio["AudioController"]
Audio --> Session["AVAudioSession data source"]
Audio --> Recorder["AVAudioRecorder"]
Audio --> Player["AVAudioPlayer"]
Audio --> Files["Documents recording.wav"]
Meter["StereoLevelMeterView"] --> Provider["StereoLevelsProvider"]
Audio -. conforms .-> Provider
VC --> Layout["StereoLayoutView"]
Reference code
StereoAudioCapture/Audio/AudioController.swift:134 — the façade resolves a UI option into an AVAudioSession data source and stereo orientation.
func selectRecordingOption(_ option: RecordingOption,
orientation: Orientation,
completion: (StereoLayout) -> Void) {
let session = AVAudioSession.sharedInstance()
guard let preferredInput = session.preferredInput,
let dataSources = preferredInput.dataSources,
let newDataSource = dataSources.first(where: { $0.dataSourceName == option.dataSourceName })
else { return }
// Configure the selected source and report its layout.
}
Interpretation
The UIKit controller maps buttons and orientation to coarse audio commands. AudioController owns framework state and produces a semantic StereoLayout; the meter only knows the small StereoLevelsProvider capability.
Ownership and state
classDiagram
MainViewController *-- AudioController : creates
MainViewController --> StereoLevelMeterView : outlet
MainViewController --> StereoLayoutView : outlet
AudioController *-- AVAudioRecorder : owns
AudioController *-- AVAudioPlayer : owns while playing
AudioController *-- MeterTable : owns
AudioController o-- AudioControllerDelegate : weak
StereoLevelMeterView o-- StereoLevelsProvider : observes
Ownership evidence
StereoAudioCapture/MainViewController.swift:15 — the screen creates its audio service and supplies it to the meter/delegate relationships.
class MainViewController: UIViewController {
// ...
}
| Owner |
Object or state |
Relationship |
Mutation authority |
MainViewController |
AudioController and screen state |
Creates and issues user commands |
Controller for audio; view controller for UI |
AudioController |
Recorder, player, URL, timer, state |
Creates/retains and coordinates |
AudioController |
StereoLevelMeterView |
Display link and child meters |
Creates and renders |
Meter view |
StereoLayoutView |
Layout image |
Retains view state |
Layout view from semantic model |
Class and protocol design
| Type |
Responsibility |
Depends on |
MainViewController |
Translate UIKit events to audio commands and present results |
AudioController |
AudioController |
Configure session, record/play, persist, and expose levels |
AVFAudio APIs |
StereoLevelsProvider |
Read-only stereo meter capability |
StereoChannelLevels |
Meterable |
Normalize recorder/player meter APIs |
AVAudioRecorder, AVAudioPlayer conformances |
StereoLayout |
Map physical orientation and source orientation to a UI case |
AVAudioSession orientation enums |
Access control
| Symbol |
Access |
Verified effect |
Likely rationale |
AudioController.state |
private(set) |
UI can inspect state but only controller operations change it. |
Preserve the record/play state machine. |
| Recorder/player/URL/timer |
private |
Callers cannot manipulate framework objects independently. |
Keep lifecycle transitions atomic at the façade. |
RecordingOption.dataSourceName |
fileprivate |
Display name is broadly readable; hardware lookup key stays in AudioController.swift. |
Prevent UI dependence on session identifiers. |
Orientation.inputOrientation |
fileprivate extension |
Conversion is usable only by audio-controller code in this file. |
Localize framework mapping. |
LevelMeterView |
private nested-file type |
Only the stereo meter composes raw channel meters. |
Hide a rendering implementation detail. |
| App types |
implicit internal |
Available only inside the app module. |
No framework API is exported. |
Reference code
StereoAudioCapture/Audio/AudioController.swift:38 — the model exposes a title but confines its device key to this file.
struct RecordingOption: Comparable {
let name: String
fileprivate let dataSourceName: String
}
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| UI state and actions |
MainViewController |
UIKit screen concern. |
| Session/source/polar-pattern selection |
AudioController |
Coupled framework configuration. |
| Recording and playback lifecycle |
AudioController |
Same owner as state and file URL. |
| dB-to-linear conversion |
MeterTable |
Focused numerical helper. |
| Level visualization |
StereoLevelMeterView |
Rendering/display-link concern. |
| Physical layout mapping |
StereoLayout |
Pure domain translation. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Façade/controller |
StereoAudioCapture/Audio/AudioController.swift:46 |
Gives the screen one coherent audio boundary. |
| Weak delegate |
StereoAudioCapture/Audio/AudioController.swift:83 |
Reports playback completion without retaining the screen. |
| Protocol adapter |
StereoAudioCapture/Audio/AudioController.swift:282 |
Treats recorder and player uniformly for metering. |
| Read-only provider |
StereoAudioCapture/StereoLevelMeterView.swift:23 |
Decouples the meter from audio lifecycle details. |
| Explicit state model |
StereoAudioCapture/Audio/AudioController.swift:32 |
Makes stopped/playing/recording transitions visible. |
Naming conventions
- Coordinator names use role nouns:
AudioController, MainViewController.
- Capability protocols use provider/delegate suffixes:
StereoLevelsProvider, AudioControllerDelegate.
- Commands use concrete verbs:
selectRecordingOption, setupRecorder, record, play.
- UI files match their primary view; model and extension folders expose their purpose.
Architecture takeaways
- Hide device-source identifiers while presenting a small display model to UI code.
- Use protocols to normalize only the capability the consumer needs, such as metering.
- Keep recorder, player, state, and persistence under one lifecycle authority.
- Map hardware orientation into a semantic value before updating the view.
Source map
| Source file |
Relevant symbols |
StereoAudioCapture/MainViewController.swift |
Screen coordination and delegate conformance |
StereoAudioCapture/Audio/AudioController.swift |
Session, recording, playback, protocols, state |
StereoAudioCapture/Audio/MeterTable.swift |
Level conversion |
StereoAudioCapture/StereoLevelMeterView.swift |
Meter provider and display |
StereoAudioCapture/Model/StereoLayout.swift |
Layout mapping |