From 7f82119001047ef301cc7bee0f6f77d41678f182 Mon Sep 17 00:00:00 2001 From: darken Date: Tue, 14 Apr 2026 15:50:25 +0200 Subject: [PATCH] fix(aap): Correct Adaptive noise capability flags and level semantics Switch the 0x4D init packet flags from 0x0E to 0xD7 to match the value captured from Apple's own stack (librepods AACPManager). The previous value traced to MagicPodsCore and matches no known Apple capture. Invert the Adaptive Audio Noise level on write and read: wire 0 means max noise reduction, wire 100 means transparency-like. UI stays in intuitive 100 = max NC semantics, matching librepods' slider behavior. --- .../capod/pods/core/apple/aap/protocol/AapSetting.kt | 5 ++--- .../core/apple/aap/protocol/DefaultAapDeviceProfile.kt | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt index cf7da789..d1c49160 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt @@ -93,9 +93,8 @@ sealed class AapSetting { val enabled: Boolean, ) : AapSetting() - // Wire format matches librepods spec (tested on AirPods Pro 2). On AirPods Pro 3 the device - // accepts and echoes the value but no audible effect has been observed — may need additional - // enable packet or different handling. Requires more investigation. + // UI-space 0..100 (100 = max noise reduction). Wire value is inverted — conversion lives in + // the device profile. Pro 3 silently accepts writes (no echo) but the value persists. data class AdaptiveAudioNoise( val level: Int, ) : AapSetting() diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt index af2a25ce..cbeb325c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt @@ -86,7 +86,7 @@ class DefaultAapDeviceProfile( override fun encodeInitExt(): ByteArray? { if (!model.features.needsInitExt) return null return byteArrayOf( - 0x04, 0x00, 0x04, 0x00, 0x4d, 0x00, 0x0e, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x4d, 0x00, 0xd7.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ) } @@ -101,7 +101,9 @@ class DefaultAapDeviceProfile( is AapCommand.SetVolumeSwipeLength -> buildSettingsMessage(SETTING_VOLUME_SWIPE_LENGTH, command.value.wireValue) is AapCommand.SetVolumeSwipe -> buildSettingsMessage(SETTING_VOLUME_SWIPE, encodeAppleBool(command.enabled)) is AapCommand.SetPersonalizedVolume -> buildSettingsMessage(SETTING_PERSONALIZED_VOLUME, encodeAppleBool(command.enabled)) - is AapCommand.SetAdaptiveAudioNoise -> buildSettingsMessage(SETTING_ADAPTIVE_AUDIO_NOISE, command.level.coerceIn(0, 100)) + // Wire semantics are inverted: wire 0 = max noise reduction, wire 100 = min (transparency-like). + // UI value 0..100 follows user intuition (100 = max NC), so flip on write/read. + is AapCommand.SetAdaptiveAudioNoise -> buildSettingsMessage(SETTING_ADAPTIVE_AUDIO_NOISE, 100 - command.level.coerceIn(0, 100)) is AapCommand.SetEndCallMuteMic -> buildEndCallMuteMicMessage(command.muteMic, command.endCall) is AapCommand.SetMicrophoneMode -> buildSettingsMessage(SETTING_MICROPHONE_MODE, command.mode.wireValue) is AapCommand.SetEarDetectionEnabled -> buildSettingsMessage(SETTING_EAR_DETECTION_ENABLED, encodeAppleBool(command.enabled)) @@ -242,7 +244,7 @@ class DefaultAapDeviceProfile( AapSetting.PersonalizedVolume::class to AapSetting.PersonalizedVolume(enabled) } SETTING_ADAPTIVE_AUDIO_NOISE -> { - AapSetting.AdaptiveAudioNoise::class to AapSetting.AdaptiveAudioNoise(level = value) + AapSetting.AdaptiveAudioNoise::class to AapSetting.AdaptiveAudioNoise(level = 100 - value.coerceIn(0, 100)) } SETTING_MICROPHONE_MODE -> { val mode = AapSetting.MicrophoneMode.Mode.fromWire(value) ?: return null