Mixing spatial music
At a glance
| Item | Summary |
|---|---|
| Purpose | Preview ray-traced reverb by adjusting a spatialized multitrack audio mix in an immersive scene. |
| App architecture | MixingSpatialMusicApp owns AppModel; the immersive scene creates spatial emitters while mixer/settings views mutate source levels and visual models reflect the same audio-scene state. |
| Main patterns | SwiftUI scene composition, UI–RealityKit bridge, Observable state owner, Async update consumer, Scene-backed audio mixer |
| Project style | Code-rich sample with 21 scanned Swift file(s) and 1628 implementation line(s). |
Project structure
Source bundle/
└── MixingSpatialMusic/
├── AppModel.swift # AppModel, ImmersiveSpaceState
├── Views/
│ ├── ImmersiveView.swift # ImmersiveView
│ ├── AudioMixer/
│ │ └── MixerView.swift # MixerView
│ └── SettingsView.swift # SettingsView
├── MixingSpatialMusicApp.swift # MixingSpatialMusicApp
├── ECS/
│ └── ViewpointComponent.swift # ViewpointComponent
└── Model/
└── AudioStem.swift # AudioStem, VisualModel
Structure observations
- The runtime boundary is WindowGroup + ImmersiveSpace + SwiftUI/UIKit host; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
- The source is Swift; role-named types and feature folders separate the major responsibilities.
Overall architecture
flowchart LR
MixingSpatialMusicApp_1["MixingSpatialMusicApp"]
WindowGroup___ImmersiveSpace___SwiftUI_UIKit_host_2["WindowGroup + ImmersiveSpace + SwiftUI/UIKit host"]
ImmersiveView_3["ImmersiveView"]
AppModel_4["AppModel"]
RealityKit_entity_graph_5["RealityKit entity graph"]
RealityKit_spatial_audio_scene_6["RealityKit spatial-audio scene"]
MixingSpatialMusicApp_1 --> WindowGroup___ImmersiveSpace___SwiftUI_UIKit_host_2
WindowGroup___ImmersiveSpace___SwiftUI_UIKit_host_2 --> ImmersiveView_3
ImmersiveView_3 --> AppModel_4
AppModel_4 --> RealityKit_entity_graph_5
RealityKit_entity_graph_5 --> RealityKit_spatial_audio_scene_6
Reference code
MixingSpatialMusic/MixingSpatialMusicApp.swift:11 — executable or app-lifecycle anchor.
struct MixingSpatialMusicApp: App {
// ...
}The diagram is a responsibility flow, not a claim that every adjacent node directly calls the next. It separates lifecycle, presentation, stable state, framework/session work, and the RealityKit entity graph.
Ownership and state
classDiagram
MixingSpatialMusicApp *-- AppModel : appModel
AppModel *-- Entity : root
AppModel *-- Entity : audioSourcesRoot
AppModel --> AudioPlaybackGroupController : playbackController
Ownership evidence
MixingSpatialMusic/MixingSpatialMusicApp.swift:13 — representative stored state or nearest verified lifecycle anchor.
@main
struct MixingSpatialMusicApp: App {
// ...
@State private var appModel = AppModel()
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
MixingSpatialMusicApp |
AppModel as appModel |
creates and retains | The declaring type controls writes |
AppModel |
Entity as root |
creates and retains | The owning type coordinates writes |
AppModel |
Entity as audioSourcesRoot |
creates and retains | The owning type coordinates writes |
AppModel |
AudioPlaybackGroupController as playbackController |
stores and coordinates | The owning type coordinates writes |
Ownership is intentionally narrow: environment, bindings, observed objects, weak links, and delegate callbacks do not prove lifetime ownership. Initialized stored services and wrapper-managed state do; entities added inside a RealityKit content closure belong to that entity graph.
Class and protocol design
| Type | Responsibility | Depends on or conforms to |
|---|---|---|
MixingSpatialMusicApp (MixingSpatialMusic/MixingSpatialMusicApp.swift:11) |
Declares app lifecycle and top-level dependency lifetime. | App |
ImmersiveView (MixingSpatialMusic/Views/ImmersiveView.swift:11) |
Presents UI and forwards gestures or lifecycle events. | View |
AppModel (MixingSpatialMusic/AppModel.swift:14) |
Owns feature state and domain transitions. | Concrete framework collaborators |
ViewpointComponent (MixingSpatialMusic/ECS/ViewpointComponent.swift:11) |
Stores RealityKit entity data. | Component |
VisualModel (MixingSpatialMusic/Model/AudioStem.swift:28) |
Owns feature state and domain transitions. | Concrete framework collaborators |
MixerView (MixingSpatialMusic/Views/AudioMixer/MixerView.swift:13) |
Presents UI and forwards gestures or lifecycle events. | View |
SettingsView (MixingSpatialMusic/Views/SettingsView.swift:13) |
Presents UI and forwards gestures or lifecycle events. | View |
TrackView (MixingSpatialMusic/Views/AudioMixer/TrackView.swift:12) |
Presents UI and forwards gestures or lifecycle events. | View |
The reviewed source defines no local substitution protocol. Its App, View, delegate, renderer, ARKit, and RealityKit conformances are framework-facing contracts, so this document does not label the whole app protocol-oriented.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
private func makeAudioSource(for stem: AudioStem) -> Entity { (MixingSpatialMusic/AppModel.swift:121) |
private |
Use stays inside the declaration and same-file extensions permitted by Swift. | Inference: Hide lifecycle-sensitive state or an implementation step. |
fileprivate static let labeledDecibelValues: [Double] = [-60, -40, -30,... (MixingSpatialMusic/Views/AudioMixer/LabeledFader.swift:113) |
fileprivate |
Use is limited to declarations in this source file. | Inference: Share with same-file helpers without making the symbol module-wide. |
No reviewed Swift access example uses private(set), public, open; declarations without a modifier are internal.
Reference code
MixingSpatialMusic/AppModel.swift:121 — representative visibility boundary.
private func makeAudioSource(for stem: AudioStem) -> Entity {
// Construct an audio source entity for the stem.
let entity: Entity = .makeAudioSource(for: stem)
audioSourcesRoot.addChild(entity)
return entity
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application/command lifecycle | MixingSpatialMusicApp |
The executable boundary determines app, controller, scene, or command lifetime. |
| Presentation and user intent | ImmersiveView |
The view/controller forwards input and owns only presentation-local work. |
| Shared feature state and commands | AppModel |
A stable owner prevents view recomputation or callbacks from duplicating transitions. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| SwiftUI scene composition | MixingSpatialMusic/MixingSpatialMusicApp.swift:11 |
Keeps windows, volumes, and immersive presentation visible at the app boundary. |
| UI–RealityKit bridge | MixingSpatialMusic/Views/ImmersiveView.swift:16 |
Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle. |
| Observable state owner | MixingSpatialMusic/AppModel.swift:14 |
Keeps shared feature state in a stable owner rather than in transient view values. |
| Async update consumer | MixingSpatialMusic/AppModel.swift:93 |
Consumes long-lived framework output in a cancellable asynchronous task. |
| Scene-backed audio mixer | MixingSpatialMusic/Views/AudioMixer/MixerView.swift:100 |
Treats emitters as scene entities while one app model and mixer controls coordinate their playback parameters. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
MixingSpatialMusicApp; Model:AppModel,VisualModel; Component:ViewpointComponent; View:ImmersiveView,MixerView,SettingsView,TrackView. - ECS names pair data and behavior: components
ViewpointComponent; systems none. - Protocols: no app-defined protocol in the reviewed source.
- Commands use verb-led methods:
teleportToStartingViewpoint,teleport,loadVisualEnvironment,loadAudioEnvironment,loadAudio,addViewpoints,configureAudioMixGroups,makeAudioSource. - Files and folders use primary-type or responsibility names such as
Views,Models,Rendering,Components,Systems,Packages, or feature names where those boundaries exist.
Architecture takeaways
- Treat
MixingSpatialMusicAppas the owner of executable or scene lifecycle, not automatically as the owner of every entity created later. - Keep view/controller input near presentation, but move sessions, reconstruction, capture, rendering, shared game state, or documents into stable owners when their lifetime exceeds one callback or render pass.
- Tie asynchronous session/output consumers to an explicit owner so cancellation and teardown follow the same lifecycle.
Source map
| Source file | Relevant symbols |
|---|---|
MixingSpatialMusic/AppModel.swift:14 |
AppModel, ImmersiveSpaceState |
MixingSpatialMusic/Views/ImmersiveView.swift:11 |
ImmersiveView |
MixingSpatialMusic/Views/AudioMixer/MixerView.swift:13 |
MixerView |
MixingSpatialMusic/MixingSpatialMusicApp.swift:11 |
MixingSpatialMusicApp |
MixingSpatialMusic/ECS/ViewpointComponent.swift:11 |
ViewpointComponent |
MixingSpatialMusic/Model/AudioStem.swift:11 |
AudioStem, VisualModel |
MixingSpatialMusic/Views/SettingsView.swift:13 |
SettingsView |