Skip to content

DAX applies Virtual Bass Enhancement on HDA devices; our converter doesn't #14

Description

@antoinecellerier

Setup

Bass-burst captures on the X1 Yoga (DEV_0287_SUBSYS_17AA22E6, profile=Dynamic) — sustained sine tones at 50/80/120/180 Hz — surfaced clear evidence that DAX3 applies Virtual Bass Enhancement (psychoacoustic harmonic synthesis) that our converter doesn't reproduce.

Evidence

Spectrum of the captured 50 Hz tone region (loud, peak −5 dBFS), Hann-windowed:

freq        magnitude
 50.0 Hz   −36.3 dBFS    ← fundamental
150.0 Hz   −37.6 dBFS    ← 3rd harmonic (1.3 dB below fundamental)
250.0 Hz   −38.1 dBFS    ← 5th harmonic
300.0 Hz   −49.4 dBFS    ← 6th
200.0 Hz   −50.6 dBFS    ← 4th
350.0 Hz   −54.3 dBFS    ← 7th

For comparison, the 180 Hz capture in the same battery has all non-fundamental peaks ≥35 dB below the fundamental — clean sine, crest factor 4.4 dB. The 50 Hz region's crest factor is 22.9 dB.

The pattern is the textbook signature of missing-fundamental synthesis: dominant odd harmonics at near-fundamental amplitude, fundamental preserved (not boosted), even harmonics suppressed. The auditory system reconstructs a 50 Hz percept from the harmonic complex — the standard psychoacoustic-bass mechanism (SRS TruBass, Waves MaxxBass, Dolby's own VBE).

What our converter does

make_preset() in dolby_to_easyeffects.py enables Calf BassEnhancer (bass_enhancer#0) only when is_soundwire=True. HDA devices like the X1 Yoga get no VBE in the generated chain. Our 50 Hz capture in the same battery is just an attenuated 50 Hz fundamental at −50.5 dBFS with no significant harmonic content — clean sine, crest 3 dB.

So the X1 Yoga preset is missing the harmonic-generation stage entirely.

What the XML says

The X1 Yoga XML has all VBE-adjacent fields populated:

<bass-enhancer-enable value="0"/>
<bass-enhancer-cutoff-frequency value="200"/>
<virtual-bass-mode value="0"/>
<virtual-bass-mix-freqs value="94,469"/>
<virtual-bass-subgains value="-32,-144,-192"/>
<virtual-bass-src-freqs value="35,160"/>

The enable=0 / mode=0 flags read as "off", but every other parameter is non-default and looks configured. Two readings:

  1. mode=0 is honestly off, and DAX has a baseline VBE that always runs (not in the schema). The supporting parameters are dead schema slots.
  2. mode=0 is a specific mode label (auto / default), and the supporting parameters configure it.

The captures don't disambiguate, but reading (2) feels more consistent — DAX is clearly doing harmonic synthesis, the schema fields are populated, and "configured but ignored" is a strange shape for shipped tunings.

The converter currently treats the entire bass-enhancer-* / virtual-bass-* schema family as skipped (per Finding 5 in docs/design-notes.md), so neither reading is acted on today.

Implications

  • The X1 Yoga (and presumably other HDA devices) ships without psychoacoustic bass synthesis on Linux despite the XML carrying configuration for it.
  • This is part of the EE-vs-DAX bass gap on real content (separate from the steady-state chain-attenuation gap tracked in EQ frequency-response gap between our converter output and DAX3 (>5 kHz; ~28 dB at 19.7 kHz) #12 — that's LTI; this is nonlinear).
  • Listening test would pick this up before measurement does — bass-heavy content on the EE chain "feels thinner" than on DAX, even when the spectrum-flat region matches.

Open questions

  1. Is reading (1) or (2) correct? Needs a corpus check: are there XMLs where mode != 0, and if so, do any of the supporting parameters change in a way that would help us infer the mapping?
  2. If reading (2) is right, what's the mapping from virtual-bass-mix-freqs / subgains / src-freqs to Calf BassEnhancer parameters (amount, scope, harmonics, blend)? The Calf plugin's parameter space is much smaller than DAX's; the mapping may not be one-to-one.
  3. A second-device DAX capture comparing two devices with different virtual-bass-* parameters would help validate any mapping. Same risk profile as the stress investigation in the Impulse Responses aren't doing anything #11 follow-up — blind re-interpretation without external validation isn't safe to ship as default.

Possible next steps

  • Cheap: corpus sweep for virtual-bass-* value distribution. Are these values constant across the corpus (like regulator-overdrive/relaxation) or do they vary?
  • Medium: enable Calf BassEnhancer for HDA devices behind an opt-in flag, with the cutoff derived from the chain HP and amount mapped from virtual-bass-overall-gain (or similar). Listening test on bass-heavy content. Risk: Calf BassEnhancer's harmonic profile likely doesn't match DAX's exactly, so this might "add some VBE" without "matching DAX's VBE".
  • Speculative: capture DAX response on multiple devices with varying virtual-bass-* values to actually pin down the schema interpretation.

Tracked separately from the EQ-curve gap in #12 — that's a frequency-domain LTI question; this is a missing nonlinear processing stage.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    fidelity gapAudio fidelity gap compared to original Windows profile

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions