Skip to content

Warn when Dolby Atmos is hijacking the lossless stream - #211

Open
blazemalan wants to merge 2 commits into
vincentneo:mainfrom
blazemalan:feature/atmos-warning
Open

Warn when Dolby Atmos is hijacking the lossless stream#211
blazemalan wants to merge 2 commits into
vincentneo:mainfrom
blazemalan:feature/atmos-warning

Conversation

@blazemalan

Copy link
Copy Markdown

What & why

When Spatial Audio / Dolby Atmos is enabled, Music plays the lossy AAC asset (decoded via ACMP4AACBaseDecoder @ 48 kHz) and never touches the Apple Lossless decoder. The parser only reads sample rates from ACAppleLosslessDecoder lines, so in this state it finds nothing to switch to and the output device silently stays at whatever rate it was — which looks like the app is "stuck."

This adds a positive detector for that state and a small red warning row in the menu so users understand why switching isn't happening.

How

  • CMPlayerParser.detectAtmos(_:) classifies the current CoreAudio log window:
    • alac decoder present → Lossless (false)
    • aac decoder @ 48 kHz with no alac → Atmos / Spatial (true)
    • neither (no decode info in window) → nil, so callers keep the previous value instead of flip-flopping during steady playback
  • OutputDevices.isAtmosActive — published flag, set inside the existing detection loop (reuses the logs already fetched; no extra log queries).
  • MenuView — red "⚠️ Dolby Atmos is on — not playing in Lossless" row shown while active.

Not a toggle (re: #91)

#91 asked for an on/off toggle. Apple doesn't expose the Atmos / Spatial setting to third-party apps, so a real toggle isn't possible from here. This makes the state visible instead — so it's obvious why the rate isn't switching, rather than leaving users guessing.

Testing

Built and run on macOS 26.5 (Tahoe), Native Instruments Komplete Audio 6:

  • Lossless track → no warning, switches to 88.2 kHz as normal (isAtmosActive = false)
  • Dolby Atmos "Always On" + Atmos track → red warning row appears (isAtmosActive = true); confirmed against ground-truth system logs showing the AAC / 48 kHz decode path with no alac
  • Atmos off again → warning clears on the next track change

Detection is log-based, consistent with the existing approach in the app. Diff is 3 files / ~45 lines. Happy to adjust wording, placement, or rework this against the v3 branch (#201) if you'd prefer.

When Spatial Audio / Dolby Atmos is enabled, Music plays the lossy AAC
asset (decoded via ACMP4AACBaseDecoder @ 48 kHz) and never touches the
Apple Lossless decoder, so there is no lossless sample rate to switch to
and the device silently stays put. This adds a positive detector for that
state and a red warning row in the menu so the user knows why switching
appears stuck.

- CMPlayerParser.detectAtmos(): classifies the current CoreAudio log
  window as Lossless (alac decoder), Atmos/Spatial (aac @ 48 kHz, no
  alac), or unknown (no decode info -> keep prior state).
- OutputDevices.isAtmosActive: published flag updated in the existing
  detection loop.
- MenuView: red "Dolby Atmos is on — not playing in Lossless" row shown
  while active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 22:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds detection of lossy Dolby Atmos/Spatial playback from CoreAudio logs and surfaces a UI warning when Atmos is active.

Changes:

  • Track Atmos/Spatial state in OutputDevices (isAtmosActive) and update it based on recent CoreAudio logs.
  • Introduce CMPlayerParser.detectAtmos(...) heuristic to infer Atmos vs lossless decoding.
  • Display a warning banner in MenuView when Atmos is detected.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
Quality/OutputDevices.swift Adds isAtmosActive state and updates it from parsed CoreAudio logs.
Quality/MenuView.swift Shows an in-app warning banner when Atmos is active.
Quality/CMPlayerStuff.swift Adds heuristic parser to detect Atmos/Spatial vs lossless decoding from log messages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +126 to +145
static func detectAtmos(_ entries: [SimpleConsole]) -> Bool? {
var sawLossless = false
var sawLossyAAC = false

for entry in entries {
let message = entry.message
if message.contains("ACAppleLosslessDecoder.cpp") {
sawLossless = true
}
if message.contains("ACMP4AACBaseDecoder.cpp"),
message.contains("Output format:"),
message.contains("48000 Hz") {
sawLossyAAC = true
}
}

if sawLossless { return false } // a lossless stream is decoding -> not Atmos
if sawLossyAAC { return true } // lossy AAC @ 48 kHz, no lossless -> Atmos/Spatial
return nil // no decode info in window -> unknown
}
Comment on lines +149 to +151
if let atmos = CMPlayerParser.detectAtmos(coreAudioLogs) {
DispatchQueue.main.async { self.isAtmosActive = atmos }
}
Comment thread Quality/MenuView.swift
Comment on lines +17 to +21
if outputDevices.isAtmosActive {
Text("⚠️ Dolby Atmos is on — not playing in Lossless")
.foregroundColor(.red)
Divider()
}
Iterate the (newest-first) log entries and return on the first lossless
or AAC decoder line, instead of letting any occurrence in the 5s window
decide. Fixes switching into Atmos, where a stale alac line from the
previous track could otherwise keep the result false.

Addresses PR review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@blazemalan

Copy link
Copy Markdown
Author

Thanks for the review! I pushed a commit that adopts the strongest suggestion, and want to add some context from testing on real hardware for the rest.

✅ Decide on the most recent decode line. detectAtmos now iterates the (newest-first) entries and returns on the first lossless/AAC decoder line, instead of letting any occurrence in the 5 s window decide. This also fixes a real case: switching into Atmos, where a stale alac line from the previous track was still in the window and would otherwise keep the result false.

Spatial/Atmos-specific markers — tested, not usable here. On macOS 26.5 with a Komplete Audio 6 + headphones, SpatializationManager / spatialAudioSourceCount = 1 fires whether Atmos is on or off (any spatialization-capable output triggers it), so it can't serve as a positive discriminator. I also didn't find EC-3/JOC tokens: Apple Music delivers the Atmos asset as AAC on this setup, not E-AC-3, so there's no Dolby codec identifier to match — decoder type was the only reliable signal I could find.

AAC false positives. The 48000 Hz qualifier already excludes typical 44.1 kHz lossy AAC, and the app assumes Lossless is enabled (so the lossless path is alac and a 48 kHz AAC decode ≈ the Atmos asset). The residual edge case is a track that's genuinely 48 kHz lossy AAC and not offered in lossless — happy to soften the copy to "not playing in Lossless" to stay codec-agnostic there if you'd prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants