Repository navigation
feat(audio): keep app volume during calls by holding a ducking exemption (fixes #241) - #482
Open
CharlieBaker2002 wants to merge 1 commit into
Open
CharlieBaker2002 wants to merge 1 commit into
CharlieBaker2002 wants to merge 1 commit into
Conversation
macOS ducks every other HAL client on an output device to 0.178 (-15 dB) while a voice-processing client (FaceTime's avconferenced, WhatsApp, …) is on a call. FineTune renders every tapped app through its own HAL client, so the whole mixer — and a tapped call app itself — dropped to ~18% for the length of the call (ronitsingh10#67, ronitsingh10#113, ronitsingh10#156, ronitsingh10#241, ronitsingh10#262, ronitsingh10#360, ronitsingh10#404, ronitsingh10#426). coreaudiod never ducks a client that holds a duck request of its own. The new CallDuckingGuard holds AudioDeviceDuck(device, 0.999) on every output device, which exempts FineTune's replay while leaving the call's ducking of audio that bypasses FineTune (ignored apps, system sounds) untouched. The request is bound to the process/device pair: it works before any IO starts and survives aggregate teardown, so one request per device from AudioEngine is enough (checked on macOS 26.6 against _UpdateClientVolumeScalarDucking in coreaudiod's log). AudioDeviceDuck is exported but undocumented; it is resolved with dlsym and the feature degrades to today's behaviour if a future macOS drops it. - Settings → Audio → "Keep Volume During Calls" (keepVolumeDuringCalls, default on); turning it off releases the requests immediately. - Unit tests for the guard's bookkeeping and the setting's default and decoding; README and troubleshooting updated. Fixes ronitsingh10#241 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Oct 31, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #241. Should also resolve the "call audio is very quiet while FineTune runs" reports in #113, #156, #262, #360, #404 and #426 — see How it relates to other PRs below.
Problem
While a call app drives Apple's voice-processing IO (FaceTime's
avconferenced, WhatsApp, …), coreaudiod ducks every other HAL client on the output device to a combined volume scalar of 0.178 (-15 dB). FineTune renders every tapped app through its own HAL client, so the whole mixer drops to ~18% for the length of the call. When FineTune taps the call app too, the call itself goes through that same client and gets ducked as well — that is the faint call audio in the issues above.This is a long-standing macOS complaint with no setting to turn it off — e.g. the r/mac thread Possible to turn off audio ducking for FaceTime? (now archived) asks exactly that, and #241 asks FineTune for a toggle.
From coreaudiod's log on macOS 26.6.2 during a FaceTime call (FineTune 1.9.0 is pid 40318,
avconferencedis pid 787):(@vfmatzkin's analysis in #113 found the same
0.177828scalar.)Fix
coreaudiod never ducks a client that holds a duck request of its own. Its log calls those clients "duckers" and gives them a scalar of 1.0 whatever anyone else requests. So FineTune now holds a request of 0.999 (-0.01 dB, inaudible) on every output device via
AudioDeviceDuck:Everything FineTune replays stays at full volume; the call's ducking of audio that bypasses FineTune (ignored apps, system sounds) is untouched, and the call app's own audio is unaffected either way.
Three properties of the request make the implementation small. All three were checked against
_UpdateClientVolumeScalarDuckingin coreaudiod's log during a live FaceTime call, using a probe that renders through a private aggregate device exactly the wayProcessTapControllerdoes:So the request is bound to the process ↔ device pair, not to an IO proc, and one request per output device from
AudioEngineis enough — nothing in the real-time tap code changes.What's in the diff
Audio/Engine/CallDuckingGuard.swiftAudioDeviceDuckwithdlsym, holds/releases requests per device, keeps a held set so repeated calls are no-ops. The duck function is injectable for tests.Audio/Engine/AudioEngine.swiftstop()and when the setting is turned off.Settings/SettingsManager.swiftAppSettings.keepVolumeDuringCalls, default on; missing key decodes as on.Views/Settings/Tabs/AudioTab.swiftFineTuneTests/CallDuckingGuardTests.swift,AppSettingsKeepVolumeDuringCallsTests.swiftREADME.md,README.zh-CN.md,guide/troubleshooting.mdAbout
AudioDeviceDuckIt is exported by CoreAudio but not declared in the public headers; WebKit and Chromium call it with the same signature (
AudioDeviceID, Float32 level, const AudioTimeStamp*, Float32 ramp), and Mozilla's bindings note it has existed since macOS 10.13. It is resolved at runtime, so if a future macOS drops it the guard is a silent no-op and calls duck FineTune exactly as they do today. FineTune already relies on two SPIs of the same kind (TCCAccessRequest,responsibility_get_pid_responsible_for_pid). The technique is the one ShutTheDuckOff (GPL-3.0, same as FineTune) is built on; nothing was copied from it.How it relates to other PRs
avconferencedby name. This PR deliberately has no app or daemon list: the exemption applies to whatever FineTune renders, so the "quiet call" symptom goes away for every present and future call app. Tapping a call app can still disturb its echo cancellation; the existing ignore feature covers that, and this PR composes with fix(audio): leave apps untapped while they capture input (fixes quiet call audio + echo) #413 (dynamic untap while an app captures input) if that lands.Testing
xcodebuild test -skip-testing:FineTuneUITests(the CI invocation): 978 passed, 0 failed on macOS 26.6.2 / Xcode 27.0 / Apple Silicon.Music stayed at full volume for the rest of the call and
avconferencedkept its own 1.000 scalar. Relaunching during the same call withkeepVolumeDuringCallsset tofalsein settings.json: no request is made and FineTune's client is ducked to 0.177828, i.e. exactly 1.9.0's behaviour; relaunching with it back on: ducker at 1.000 on all three output devices. The drop and recovery were audible. The runtime release path (setEnabled(false)→AudioDeviceDuck(…, 1.0)) is covered by the unit tests, and the probe confirmed that a 1.0 request turns the client back into a duckee (_DuckClientVolumeScalar: … unducking device … is a duckee … 0.177828).🤖 Generated with Claude Code