Skip to content

Smart Volume: Studio-Quality AGC & Intelligent Loudness Compensation#317

Closed
djbob2000 wants to merge 11 commits into
ronitsingh10:mainfrom
djbob2000:feature/agc-and-compressor
Closed

Smart Volume: Studio-Quality AGC & Intelligent Loudness Compensation#317
djbob2000 wants to merge 11 commits into
ronitsingh10:mainfrom
djbob2000:feature/agc-and-compressor

Conversation

@djbob2000

@djbob2000 djbob2000 commented May 28, 2026

Copy link
Copy Markdown

Overview

This PR significantly upgrades the loudness engine in FineTune to resolve audible volume fluctuations, pumping artifacts, and bass loss, while introducing broadcasting-grade AGC controls. It addresses user feedback regarding volume stability and exposes granular controls over loudness dynamics and spectral compensation.

This PR closes and resolves the following issues:


image

Key Improvements

1. Dual-Band AGC Architecture & Phase-Linear Crossover

  • Linkwitz-Riley Crossover: Replaced the single-band AGC with a dual-band structure (Bass and Master bands split at 150 Hz) using a custom 4th-order Linkwitz-Riley crossover (LinkwitzRileyCrossover.swift). This ensures flat magnitude summation and eliminates phase cancellations.
  • Bidirectional Bass-to-Master Coupling: Implemented a bidirectional $\pm3.0$ dB coupling clamp on the bass band gain relative to the master band (keeping bassBand.currentGainDb within [masterBand.currentGainDb - 3.0, masterBand.currentGainDb + 3.0]). This prevents both unnatural independent bass pumping (upper limit) and excessive bass loss during heavy bass transients (lower limit).

2. Orban-Style Progressive Ratio & Soft-Knee Compression

  • Added Progressive Ratio (soft-knee compression) to the AGC: the compression ratio dynamically scales (starting at a gentle minRatio of 2:1 up to an infinite maxRatio for deep overshoots) using an exponential factor (progressiveRate = 0.15), removing harsh brickwall transitions.
  • Settings are fully integrated into LoudnessEqualizerSettings.swift for precise code-level tuning.

3. Silence Gate, Idle Gain Fallback & Exponential Slowdown

  • Idle Gain Fallback: During silence, the AGC gain slowly and smoothly drifts to a configurable parked level (silenceGateIdleGainDb defaulting to -24.0 dB) instead of snapping to unity gain (0 dB), avoiding sudden volume pops when audio resumes.
  • Soft-Gating Slowdown Zone: Implemented an exponential slowdown zone between -12.0 dB and -16.0 dB. The active release speed is scaled down using pow(gateSlowdownFactor, 1.0 - gatingFactor) (with a factor of 0.086 tuned for conversational speech dynamics) as the signal drops toward the gate threshold.

4. Custom Parametric Sidechain Filter

  • Replaced the standard ITU-R BS.1770 K-weighting in the active Equalizer sidechain with a custom 7-stage parametric sidechain filter (ParametricSidechainFilter.swift), mirroring Stereo Tool's sidechain parameters (38 Hz Butterworth High Pass + 6 peaking EQ bands).
  • The legacy KWeightingFilter has been preserved and integrated into the test suite for behavior and frequency response comparison.

5. Dedicated Post-AGC Compressor

  • Added a new fast-acting PostAgcCompressor.swift immediately after the Loudness Equalizer in the signal path.
  • It operates strictly downward-only (default threshold -3.0 dBFS, ratio 10.0, attack 1.0 ms, release 11.6 ms) with a soft knee (0.1 dB) and exponential release slowdown to catch transient overshoots that the slower AGC envelope follower misses.

6. UI & Settings Enhancements

  • Redesigned the "Volume" section in the AudioTab.swift: split the legacy unified loudness setting into independent controls:
    • Smart Volume (Loudness Equalization): Features a Drive slider (0.0 to 1.0 (+24db max)) to control the input gain driving the leveler.
    • Loudness Compensation: Features a Boost slider (now extended up to 3.0 for greater flexibility) to adjust the amount of low-frequency equal-loudness compensation.
  • Migrated legacy unifiedLoudnessEnabled settings transparently inside SettingsManager.swift.

7. Equal-Loudness Compensator Upgrades & Headroom Management

  • Linear Volume Mapping & Direct Gain Scaling: Updated LoudnessCompensator.swift to map the system volume linearly to estimated phon levels (ranging from 20 to a reference level of 85 phon, up from 80). The intensity scale is now applied directly to the fitted band gains instead of adjusting the phon levels, ensuring more transparent and predictable compensation across the entire range.
  • Preventing ISO 226 Clipping: The compensator computes the realized frequency response of the 4-section biquad cascade and dynamically subtracts the peak gain (peakDB) from all section gains. This guarantees that equal-loudness boosts will never exceed 0 dBFS or cause digital clipping, eliminating the dependency on downstream hard limiters.

8. Stability & Routing Fixes

  • Audio Routing Fallback: Added robust error handling in AudioEngine.swift during output device changes (e.g. headphone connection/disconnection). If switching the device on a tap fails, the engine falls back to recreateTap(for:) to prevent audio dropouts or orphaned IO processes.

Verification

Automated Tests

Comprehensive unit test suites cover the new dynamic features, filters, and safety bounds:

  • LinkwitzRileyCrossover2Tests (allpass reconstruction and magnitude response verification).
  • AgcPhonOffsetSmootherTests (fast/slow smoothing paths).
  • PostAgcCompressorTests (threshold, compression ratio, exponential release, and NaN safety).
  • LoudnessEqualizerTests (gate slowdown, sudden drop protection, progressive ratio curves, and the new bass band clamping under heavy bass input).
  • ISO226ContoursTests (estimated phon mapping curves and volume steps).

Result: ** TEST SUCCEEDED **

GitLab CI and others added 2 commits May 29, 2026 00:32
…stack allocation for performance and update LoudnessEqualizer for RT-thread efficiency
@djbob2000 djbob2000 force-pushed the feature/agc-and-compressor branch from 7243b62 to 0dca336 Compare May 29, 2026 08:09
…allel using SIMD4 and consolidate sidechain filtering in LoudnessEqualizer.
@djbob2000

Copy link
Copy Markdown
Author

Hey everyone, I read your feedback and I hear you. I’ve fixed everything you asked for.

Please make sure to share your thoughts on the updated AGC + ISO226. I tried to make the AGC as universal as possible — I believe it’s significantly better than SoundSource Magic Boost, and I took inspiration from professional audio solutions.

Let’s make this product useful for everyone.

@djbob2000 djbob2000 force-pushed the feature/agc-and-compressor branch from 1c6fd8b to e62aa0c Compare June 1, 2026 06:57
@djbob2000 djbob2000 force-pushed the feature/agc-and-compressor branch from 1c9744b to ed97c8b Compare June 16, 2026 13:00
@djbob2000 djbob2000 force-pushed the feature/agc-and-compressor branch from f8c9846 to 6d3b29f Compare June 17, 2026 14:35
@djbob2000 djbob2000 changed the title Preventing Loudness Compensation Clipping & New Professional-grade Auto Gain Control (Smart Volume) Smart Volume: Studio-Quality AGC & Intelligent Loudness Compensation Jun 17, 2026
@djbob2000

Copy link
Copy Markdown
Author

@djbob2000

Copy link
Copy Markdown
Author

@ronitsingh10 Do you think we should keep the AGC and Drive switches, or remove the adjustment and just enable them together with Loudness Compensation?

@ronitsingh10

ronitsingh10 commented Jun 18, 2026

Copy link
Copy Markdown
Owner

@djbob2000, thank you for keeping this branch current. You merged main in twice, kept CI green, and your Linkwitz-Riley split sums flat across the band, which is the part most people get wrong. Let me answer your question directly, then argue for going a step further than either option you offered.

You asked whether to keep the AGC and Drive switches or fold them under Loudness Compensation. I'd take a third path: remove the adjustments entirely, make both effects automatic, and keep them as two separate features instead of one bundled switch.

One thing shifted while you were iterating. #304 merged on June 14 and closed #302, so the volume pumping this PR set out to fix is already gone on main. That takes the time pressure off and lets us aim for the version people leave switched on.

1. Drop the Drive and Boost sliders, and compute both amounts automatically.
Someone who wants their headphones to sound right does not know what "Drive" or input gain means, and a Boost slider mostly invites them to dial in distortion. Every shipping loudness contour I can find derives the amount from the volume position instead of a strength knob: THX Loudness Plus stays flat at reference and adds correction as you turn down, Audyssey Dynamic EQ behaves the same way, and the RME ADI-2 fades its bass and treble gain over a 20 dB window on its own. CamillaDSP writes the rule out in its README: full correction about 20 dB below the reference level, scaling linearly above it. The one product I found with a real strength knob, APU Loudness Contour, is a $30 mastering plugin for engineers shaping a mix, not a playback tool. So Boost should be computed, not dialed.

2. Move Loudness Compensation onto the output device, and let it switch automatically.
The reason to compensate is the transducer: a laptop speaker rolls off the low end, and each pair of headphones needs its own curve. The same digital level is a different real loudness on every device, so one global curve over-boosts a loud output and under-boosts a quiet one. The RME ADI-2, the reference DAC for this feature, sets its loudness reference per device for that reason. eqMac's per-device profile request drew 25 reactions before they shipped it as auto-switching presets, and FineTune already does this for AutoEQ. A loudness curve is also a linear filter, so applying it per app and letting the system mix the streams comes out identical to applying it once on the mix, which means this is plumbing you already have rather than new real-time DSP.

3. Keep the leveler as its own opt-in, off by default.
The contour and the leveler solve different problems: the contour restores the bass the ear loses at low volume, the leveler evens loudness over time. People reach for them at different moments, and the leveler is the one they switch off when it pumps music or ducks under a mic, so it earns its own toggle and a default of off. SoundSource's Magic Boost ships as a single click with zero parameters, which is the bar for a consumer leveler, so the Drive knob can go here too. Give it a name that says what it does without "loudness" in it; your "Smart Volume" was already heading the right way, since Windows burned that word on a compressor people disable.

Here is the focused PR I would merge happily: per-device automatic Loudness Compensation, one toggle, no sliders, cloning the AutoEQ device-keyed pattern, with the headroom logic doing what Boost did by hand (boost where digital headroom exists, otherwise attenuate the midband so it cannot clip, the way CamillaDSP and the RME do it). That keeps the strongest parts of your work and sheds the parts people won't understand.

A few specifics to make it land:

  • Keep the 1 kHz midrange at unity. The current headroom code reshapes the curve and pulls the midrange below 0 dB, which thins out low-volume playback; the boost-or-attenuate-mid approach above holds the anchor in place instead.
  • Guard the one NaN path. Int32(floor(NaN)) in the loudness math is unguarded and a NaN can slip through to the output, which is not allowed on the CoreAudio thread, so a one-line finite-guard plus a NaN test closes it.
  • Split the routing change out. The headphone tap-switch fallback in AudioEngine is useful on its own, but it is unrelated to loudness and will review faster as a separate PR.
  • Park the AGC work behind that off-by-default flag if you want to keep it. The dual-band split is worth holding onto; the progressive-ratio path looks inert in steady state, so I'd cut it unless it earns its place.

Would you take the per-device compensation as the first PR? And if you see it differently, have a cleaner implementation in mind, or think another approach makes more sense, tell me. I would rather change the plan now than have you build one you do not believe in.

@djbob2000 djbob2000 closed this Jun 18, 2026
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.

[Bug] Loudness Equalization can cause audible volume fluctuations

2 participants