Warn when Dolby Atmos is hijacking the lossless stream - #211
Conversation
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>
There was a problem hiding this comment.
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
MenuViewwhen 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.
| 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 | ||
| } |
| if let atmos = CMPlayerParser.detectAtmos(coreAudioLogs) { | ||
| DispatchQueue.main.async { self.isAtmosActive = atmos } | ||
| } |
| 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>
|
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. Spatial/Atmos-specific markers — tested, not usable here. On macOS 26.5 with a Komplete Audio 6 + headphones, AAC false positives. The |
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 fromACAppleLosslessDecoderlines, 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:alacdecoder present → Lossless (false)aacdecoder @ 48 kHz with noalac→ Atmos / Spatial (true)nil, so callers keep the previous value instead of flip-flopping during steady playbackOutputDevices.isAtmosActive— published flag, set inside the existing detection loop (reuses the logs already fetched; no extra log queries).MenuView— red "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:
isAtmosActive = false)isAtmosActive = true); confirmed against ground-truth system logs showing the AAC / 48 kHz decode path with noalacDetection 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.