Skip to content

Atmos, spatial and multichannel audio for Dark Cosmos

Atmos, spatial and multichannel audio for Dark Cosmos

Section titled “Atmos, spatial and multichannel audio for Dark Cosmos”

Research date 2026-10-08. Read-only; nothing in bullet-heaven or godot-tvos was changed. Fork inspected at ~/Claude/godot-tvos/godot @ efa009712e. SDK headers checked: AppleTVOS27.0.sdk.

  • True Dolby Atmos (objects) from real-time game audio: not reachable. [Likely] Your prior belief holds: no public tvOS/iOS API takes real-time PCM plus object metadata and emits Atmos. The only Atmos output path is AVPlayer playing pre-encoded E-AC-3 JOC (or AC-4/TrueHD sources), and that can’t take a live game mix.
  • Discrete 5.1/7.1 LPCM over HDMI from Apple TV: reachable, but not with Godot as it is. [Likely] tvOS exposes up to 8 output channels via AVAudioSession.setPreferredOutputNumberOfChannels. Godot’s iOS/tvOS CoreAudio driver never asks for them, so today it is always stereo on Apple TV.
  • AirPods head-tracked spatial audio for a game: reachable on tvOS 18+/iOS 18+/macOS 15+ through the AudioToolbox Spatial Mixer AU (kAudioUnitProperty_SpatialMixerEnableHeadTracking) plus the self-service com.apple.developer.coremotion.head-pose entitlement. [Likely] That is a few days of fork work.
  • The bigger gap is the game, not the engine. [Certain] audio/audio_manager.gd plays everything through non-positional AudioStreamPlayer with no panning at all, so a surround-capable driver would carry the same centred stereo mix to the front L/R pair. Godot’s AudioStreamPlayer2D only ever writes channel pair 0 (front L/R) even in 5.1/7.1 mode (scene/2d/audio_stream_player_2d.cpp ~L182). Real surround needs game-side positioning work too.
  • [Likely] AVPlayer is the only observed Atmos path on iOS/tvOS. A developer on the Apple forums (Nov 2024): “the only way I’ve seen an iOS/tvOS device output Dolby Atmos is through AVPlayer”. They had filed a feature request because even AVSampleBufferAudioRenderer fed E-AC-3 JOC doesn’t output Atmos. No Apple staff contradicted this. (forum 750372)
  • [Certain] The tvOS 27 SDK has no Atmos-encoding or object-output API. The only “Atmos” symbol in AVFAudio/AudioToolbox/CoreAudioTypes is the read-only AVAudioSession.renderingMode enum value AVAudioSessionRenderingModeDolbyAtmos (tvOS 17.2+). It reports what the system is doing with loaded content; you can’t set it. There’s an ec-3 format ID but no evidence of an on-device E-AC-3/JOC encoder. Real-time JOC encoding is Dolby-licensed tech that Apple doesn’t expose.
  • [Likely] AVAudioEngine/PHASE/spatial mixer do not render to Atmos over HDMI. They render to the route’s channel layout (stereo, 5.1 or 7.1 PCM) or binaural. A 2022 tvOS thread found SceneKit/AVAudioEngine outputNode stuck at 2 channels on an Atmos system, with no answer. (forum 700337) PHASE has been on tvOS since 17.0, but nothing in its docs mentions Atmos output.
  • [Likely] Trap for device testing: the AVR may say “Dolby Atmos” anyway. With Atmos enabled and Continuous Audio Connection on, Apple TV 4K wraps all output, including stereo and multichannel PCM, in Dolby MAT. Apple says older receivers “might indicate an Atmos signal” while audio plays “in its original format”. (Apple support) An Atmos light proves nothing; check the receiver’s per-channel input display.
  • [Certain] setPreferredOutputNumberOfChannels(_:) has been available on tvOS since 9.0. Apple’s docs: requests above maximumOutputNumberOfChannels fail, “only certain devices and peripherals support this feature”, and it must be called “only after setting the audio session’s category and mode, and activating the session”. Read back outputNumberOfChannels to check it worked. (docs)
  • [Certain] An Apple evangelist (2015) said Apple TV “does support multichannel PCM audio as well as AC3 passthrough over HDMI”. Typical output is 24-bit/48 kHz, depending on the sink. (forum 17202)
  • [Likely] It’s fiddly in practice. One developer saw max=6, preferred=6 return success, and output still 2 (unresolved; Apple asked whether a multichannel receiver was attached). (17202) With the Apple TV Surround setting on “Auto”, maximumOutputNumberOfChannels returned 8 against a 5.1 DD receiver, and channel mapping came out scrambled on the AVAudioPlayer path. (forum 24961)
  • [Likely] Precedent that it works: SDL3. Its CoreAudio backend calls setPreferredOutputNumberOfChannels under #ifdef SDL_PLATFORM_TVOS only, with the comment “Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS”. It also sets an explicit layout: kAudioChannelLayoutTag_DVD_12 for 5.1 and kAudioChannelLayoutTag_WAVE_7_1 for 7.1. (SDL_coreaudio.m) VLC’s iOS/tvOS AudioUnit output does the same request-then-verify, and builds the layout from the HDMI route’s AVAudioSessionChannelDescription.channelLabels. (VLC commit, VLC MR 2786)
  • [Likely] AirPlay routes report max 2 channels, so this is HDMI-only.
  • [Certain] drivers/coreaudio/audio_driver_coreaudio.mm init() uses kAudioUnitSubType_RemoteIO on iOS/tvOS. It reads the output-scope stream format and accepts 2, 4, 6 or 8 channels (anything else becomes stereo). It then maps channels through get_speaker_mode_by_total_channels: 4 = SPEAKER_SURROUND_31, 6 = SPEAKER_SURROUND_51, 8 = SPEAKER_SURROUND_71.
  • [Certain] No AVAudioSession call anywhere in the fork requests channels. A grep for setPreferredOutputNumberOfChannels, maximumOutputNumberOfChannels and setActive in drivers/ and platform/ finds nothing. The session is only configured in drivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm, by setCategory from audio/general/ios/session_category. Dark Cosmos doesn’t set that, so it gets the default Ambient. No channel layout is set on the AU, and there’s no route-change listener on iOS/tvOS (the device-change listener is MACOS_ENABLED only).
  • [Likely] So the session stays at its 2-channel default and Godot runs SPEAKER_MODE_STEREO on Apple TV even with a 7.1 AVR attached. AudioServer.get_speaker_mode() on the device would confirm this in one line.
  • [Likely] On Mac, multichannel probably already works. The HAL path reads the default device’s channel count, and a 5.1/7.1 device gives Godot surround mode.
  • [Likely] Godot interleaves channel pairs: pair 0 = FL/FR, 1 = C/LFE, 2 = rear L/R, 3 = side L/R. That matches WAVE_7_1 (L R C LFE Rls Rrs Ls Rs) and DVD_12 for 5.1, which is the layout SDL sets.

What would need changing (fork, tvOS-only)

Section titled “What would need changing (fork, tvOS-only)”
  1. In the tvOS app-delegate session setup (or driver init() before reading the format): set category Playback on tvOS (test whether Ambient also works) and setActive:YES. Then call setPreferredOutputNumberOfChannels(min(max, 8)) and read outputNumberOfChannels back. Gate this #ifdef TVOS_ENABLED, per SDL’s iOS breakage note.
  2. Re-read the RemoteIO output format after that (the existing switch then picks 6 or 8). Set kAudioUnitProperty_AudioChannelLayout on the input scope, or swizzle from the route’s channel labels the way VLC does, so Godot’s pair order lands on the right speakers. [Guessing] whether RemoteIO honours the layout property; the label-swizzle fallback is safe.
  3. Add an AVAudioSessionRouteChangeNotification handler that re-initialises on HDMI/AVR changes. AudioServer already handles a channel-count change via init_channels_and_buffers().
  4. Effort: ~80-150 lines of Obj-C++ in the fork, about 1 day plus device iteration [Guessing]. This also needs a fork rebuild and normal-lib swap, the same process as the HDR work.

Game side (needed to hear any benefit): route positional SFX through AudioStreamPlayer3D with a listener mapped to the arena plane, because 3D players pan across all pairs. Alternatively, patch AudioStreamPlayer2D::_update_panning in the fork to write pairs 1-3. Music/ambience can use MIX_TARGET_SURROUND. There’s also a design call: on a top-down screen, what does “behind you” mean? Off-screen threats, boss telegraphs, ambience bed. 2-4 days [Guessing].

3. AirPods spatial audio with head tracking

Section titled “3. AirPods spatial audio with head tracking”
  • [Certain] APIs and availability:
    • CMHeadphoneMotionManager: iOS 14 / macOS 14 / watchOS 7, not tvOS. It needs NSMotionUsageDescription. (docs)
    • The newer automatic route is tvOS 18 / iOS 18 / macOS 15. Entitlement com.apple.developer.coremotion.head-pose (Xcode “Head Pose” capability, no request process documented) enables head-pose-driven orientation for AVAudioEnvironmentNode.isListenerHeadTrackingEnabled, AudioToolbox kAudioUnitProperty_SpatialMixerEnableHeadTracking, and PHASE PHASEListener.automaticHeadTrackingFlags. (entitlement, PHASE updates June 2024)
    • Personalised HRTF needs com.apple.developer.spatial-audio.profile-access. AVGameBypassSystemSpatialAudio (Info.plist) stops PHASE from fighting the Control Center spatial toggle (same doc).
  • [Certain] The tvOS 27 SDK header has kAudioUnitProperty_SpatialMixerEnableHeadTracking available tvos(18.0). It also has kSpatialMixerSourceMode_AmbienceBed, which spatialises a multichannel bus around the listener “as far-field sources” using the bus’s AudioChannelLayout, and kSpatialMixerOutputType_Headphones/BuiltInSpeakers/ExternalSpeakers.
  • Best Godot integration [Likely]: keep Godot’s mixer and have it render a 7.1 bed. Feed that into an AUSpatialMixer instance (AmbienceBed source mode, Headphones output, head tracking on), then into RemoteIO. Only do this when the route is Bluetooth headphones. On HDMI, send the 7.1 directly (section 2); on iPhone speakers, optionally use the BuiltInSpeakers output type. This reuses the section 2 work: once Godot can mix 7.1, binaural is one more AU in the chain. Effort: 2-4 days on top of section 2 [Guessing]. PHASE/AVAudioEnvironmentNode would mean replacing Godot’s mixer per source, which is far more work and not recommended.
  • [Guessing] Free option: the system “Spatialize Stereo” (Control Center on iOS) can already spatialise any stereo app output on AirPods. Whether tvOS applies this to a third-party game’s stereo, and whether it head-tracks, needs a device check. It also has no positional meaning, just a widened stereo field.
  • [Guessing] setSupportsMultichannelContent(true) (tvOS/iOS 15+) is documented for “Now Playing” apps. It may affect how the system spatialises multichannel to AirPods, but it doesn’t fit a game.
Option Reachable Effort Payoff
Atmos objects from live mix No [Likely] n/a n/a
7.1 LPCM over HDMI (Apple TV + AVR) Yes [Likely] fork ~1 day + game 2-4 days Real for AVR owners only (small share of players)
AirPods head-tracked binaural (tvOS/iOS 18+) Yes [Likely] +2-4 days on top of 7.1 Good on iPhone/iPad; on Apple TV, AirPods users only
System Spatialize Stereo Possibly already [Guessing] 0 Width only, not positional

Suggested order: a 30-minute probe build first (below), then fork 7.1 if the probe shows 8 channels and a correct mapping, then game-side positioning, then spatial mixer for AirPods. Don’t start any of it until the game has positional audio worth spreading. Today’s mix is non-positional, so the probe is the only cheap step.

Device test against Chris’s AVR (what must be confirmed)

Section titled “Device test against Chris’s AVR (what must be confirmed)”

Probe: a tiny fork patch or debug-only Obj-C hook that logs, on Apple TV, maximumOutputNumberOfChannels, outputNumberOfChannels before and after the preferred-channels request, the current route port type and every channelLabel, renderingMode, and Godot’s AudioServer.get_speaker_mode(). Then:

  1. Channel ID test: play a voice/tone on each of the 8 channels in turn (FL, FR, C, LFE, RL, RR, SL, SR). On the AVR, confirm each lands on the right speaker, and that its signal-info page shows “Multi-ch PCM 7.1” (or MAT with the right input-channel indicators). Don’t trust the Atmos badge alone.
  2. Repeat with Apple TV Audio Format set to Auto / Dolby Digital 5.1 / Change Format on/off and Continuous Audio Connection on/off. Forum evidence says “Auto” can over-report channels and scramble the map, and Dolby Digital mode re-encodes to AC-3 5.1 (lossy, with added latency).
  3. Session category Ambient vs Playback: which one actually gets more than 2 channels.
  4. Latency and hitches: AV sync on a hit SFX, and hitches telemetry, since 8-channel mixing costs Godot’s mixer about 4x the per-voice work of stereo.
  5. Hot-plug: switch AVR input / power-cycle mid-run and confirm no silent stall (route-change handling).
  6. AirPods (later): Apple TV with AirPods Pro on tvOS 18+. Check that head-pose entitlement head tracking moves the bed, and check the result against the Control Center spatial toggle.