diff --git a/bin/omarchy-hw-fingerprint b/bin/omarchy-hw-fingerprint index c5f119d9cd..337c4bfd10 100755 --- a/bin/omarchy-hw-fingerprint +++ b/bin/omarchy-hw-fingerprint @@ -30,7 +30,10 @@ for dev in /sys/bus/usb/devices/*; do if [[ -r $dev/product ]]; then product=$(<"$dev/product") product=${product,,} - [[ $product == *fingerprint* || $product == *biometric* ]] && exit 0 + # Elan's match-on-chip readers report "ELAN:ARM-M4", the family name + # rather than the function. Elan is left out of the vendor list below + # on purpose, so without this they match nothing. + [[ $product == *fingerprint* || $product == *biometric* || $product == *elan:arm-m4* ]] && exit 0 fi if [[ -r $dev/idVendor ]]; then diff --git a/default/audio/tunings/hp-elitebook-x-g2i/README.md b/default/audio/tunings/hp-elitebook-x-g2i/README.md new file mode 100644 index 0000000000..790fc2f893 --- /dev/null +++ b/default/audio/tunings/hp-elitebook-x-g2i/README.md @@ -0,0 +1,101 @@ +# HP EliteBook X G2i speaker tuning (Omarchy) + +Omarchy `omarchy-audio-tuning` profile for the HP EliteBook X G2i +(DMI product SKU `D4DU1UT#ABA`, HP subsystem ID `8E86103C`), whose four +TAS2783 smart amps sound boxy on Linux because the DSP voicing that Windows +applies in the DTS:X Ultra APO is absent. + +This tuning carries **HP's own factory EQ, bit-exact** — not a re-creation by +ear. Every biquad coefficient comes straight out of the per-board DTS tuning +data extracted from this machine's Windows partition: + + extracted-windows/audio-dts-tuning-8E86/HPCMIT8E86103C/ + +## What was decoded + +`dts_apo4_oem_config_8E86103C.xml` (plain text) declares, for the internal +stereo speakers, that the default content mode is `APO4-Music` +(`APO4-ContentMode-Default`), and that this mode enables: + +| DTS stage | Setting | Carried here as | +|---|---|---| +| `EFX:Eagle-HPF` | Enable=1, Freq=100, Order=2 | `bq_highpass` 100 Hz, Q 0.7071 (Butterworth 2nd order) | +| `EFX:Eagle-AEQ` / GPEQ | blobs `dts_gpeq_mode1_{48k,44k}` , In/Out gain 0 dB | 13 `bq_raw` sections, coefficients verbatim | +| `EFX:Eagle-MBHL` | multiband protection/loudness (envelope mode) | LSP lookahead limiter (Omarchy standard), not a multiband recreation | +| `SFX:Eagle-TBHDX` | bass enhancer, **Enable=0** in Music mode | omitted (disabled at the factory too) | + +Mode-to-blob map from the XML (internal speakers): Music→mode1, Movie→mode2, +Voice→mode3 (HPF 160 Hz), Game1/2/3→mode4/5/6, Custom→mode7. Music being the +default for media/notifications, mode1 is what Windows plays music through. + +## GPEQ blob format + +Established by decoding both sample-rate variants of every mode to mutually +consistent curves (see `../decode_dts_gpeq.py`): + +- int32 LE words; the 4720-byte file holds **two identical banks** (L/R), + the second starting at the exact midpoint. +- Bank: `word0` = section count (24), `word1` = 0, then per section six words + `[qflag, b0, b1, b2, a1, a2]`. +- `qflag` = 0 marks an unused section; otherwise coefficients are + **Q(32-qflag) fixed point** (`value / 2^(32-qflag)`; qflag 2 → Q2.30 for + the peaking sections, qflag 1 → Q1.31 for the HF tilt sections). +- Feedback convention `y[n] = Σ b_i·x[n-i] + a1·y[n-1] + a2·y[n-2]`, i.e. + `H(z) = (b0 + b1·z⁻¹ + b2·z⁻²) / (1 − a1·z⁻¹ − a2·z⁻²)`; a1/a2 are negated + in `filter-chain.conf` for PipeWire's RBJ convention. + +The decode is self-validating: the 44.1 kHz and 48 kHz banks produce the same +analog-domain curve, and each section lands on a tuning-engineer-round value: + +| # | Type | fc | Gain | Q | +|---|---|---|---|---| +| 0 | peaking | 118 Hz | +2.0 dB | 2.8 | +| 1 | peaking | 158 Hz | −3.0 dB | 3.3 | +| 2 | peaking | 254 Hz | −4.5 dB | 2.3 | +| 3 | peaking | 368 Hz | −3.5 dB | 2.0 | +| 4 | peaking | 624 Hz | −6.0 dB | 1.7 | +| 5 | peaking | 848 Hz | −5.0 dB | 1.8 | +| 6 | peaking | 1.37 kHz | −7.0 dB | 2.3 | +| 7 | peaking | 1.78 kHz | +3.0 dB | 3.6 | +| 8 | peaking | 2.90 kHz | −5.0 dB | 1.9 | +| 9 | peaking | 3.98 kHz | −3.5 dB | 5.1 | +| 10 | peaking | 5.53 kHz | −2.0 dB | 2.9 | +| 11 | peaking | 13.6 kHz | −5.0 dB | 3.3 | +| 12 | 1st-order HF tilt | — | +3.5 dB LF → −6 dB @ Nyquist | — | + +The deep 600 Hz–1.4 kHz cuts are exactly the "boxy" band; HP's engineers cut +it 5–7 dB and added presence back at 1.8 kHz. + +Rather than re-fitting these to `Freq/Q/Gain` sections, `filter-chain.conf` +uses PipeWire's `bq_raw` with the decoded coefficients for both 48000 and +44100 Hz, so the EQ stage is bit-identical to Windows' at either rate. (As +shipped by DTS, the 44.1 kHz bank sits ~2.1 dB hotter overall — a quirk of +their tilt section; the limiter absorbs it. The speaker sink runs at 48 kHz.) + +## Headroom / limiter + +The full cascade (high-pass included) peaks at **+3.66 dB at 1.86 kHz** on the +48 kHz bank. Limiter input gain is set to −4.66 dB (`g_in = 0.585`) so a +full-scale sine at the worst frequency lands exactly at the −1 dBFS threshold +(`th = 0.891`), with `alr`/`boost` disabled per Omarchy convention. The +−12 dB hardware safety trim on the `tas2783-N Speaker Volume` controls is +independent of this and stays in place. + +## Not decoded / out of scope + +- `dts_peq_ui_settings_8E86103C.bin` (gzip, 216 KB raw) and + `dts_ctc_ui_settings_8E86103C.bin` decompress to protobuf-style binary UI + state, not tuning curves. +- `dts_ctc_*.bin` — crosstalk cancellation matrices; not reproduced. +- `dts_hpeq_*.bin` — headphone EQ; this tuning only fronts the speaker sink. +- MBHL's program-dependent multiband loudness ("Boost 25", makeup gains) is a + dynamics processor, deliberately not imitated by a static EQ. + +## Install + +``` +sudo cp -r audio-tuning/hp-elitebook-x-g2i /usr/share/omarchy/default/audio/tunings/ +omarchy-audio-tuning on +``` + +Requires `lsp-plugins-lv2` for the limiter. diff --git a/default/audio/tunings/hp-elitebook-x-g2i/filter-chain.conf b/default/audio/tunings/hp-elitebook-x-g2i/filter-chain.conf new file mode 100644 index 0000000000..d6777402cc --- /dev/null +++ b/default/audio/tunings/hp-elitebook-x-g2i/filter-chain.conf @@ -0,0 +1,258 @@ +# HP EliteBook X G2i (SKU D4DU1UT#ABA) internal speaker tuning. +# +# This is HP's own per-board DTS:X Ultra ("Eagle") speaker voicing, carried +# verbatim: the biquad coefficients below are decoded from the factory blob +# dts_gpeq_mode1_{48k,44k}_8E86103C.bin (the graphic/parametric EQ bank the +# Windows DTS APO loads for the default "Music" content mode on the internal +# stereo speakers), preceded by the 100 Hz 2nd-order high-pass the same mode +# enables (EFX:Eagle-HPF Enable=1, Freq=100, Order=2 in +# dts_apo4_oem_config_8E86103C.xml), and followed by the standard Omarchy +# lookahead limiter in place of DTS's MBHL protection stage. +# +# Blob format (verified by decoding both sample-rate variants to the same +# curve): int32 LE words; word0 = section count (24), word1 = 0, then per +# section 6 words: [qflag, b0, b1, b2, a1, a2] with coefficients in +# Q(32-qflag) fixed point and the feedback terms stored for +# y[n] = ... + a1*y[n-1] + a2*y[n-2] (negated here for PipeWire's RBJ +# convention). The bank is duplicated at the file midpoint for L/R; both +# banks are identical, so the same chain serves both channels. Unused +# sections have qflag = 0. See README.md next to this file. +# +# bq_raw is used instead of fitted Freq/Q/Gain sections so the response is +# bit-exact to HP's tuning at 48 kHz and 44.1 kHz, the two rates DTS ships +# coefficients for. (The 44.1 kHz bank sits ~2.1 dB hotter overall as +# shipped by DTS; the limiter absorbs that if the graph ever runs at 44.1 +# kHz. In practice the graph follows the 48 kHz speaker sink.) +# +# Channels are wired explicitly because the limiter is a stereo plugin; a mono +# graph is duplicated per channel and would limit each side independently, +# shifting the stereo image on bass transients. + +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Laptop Speakers" + media.name = "Laptop Speakers" + + filter.graph = { + nodes = [ + # ---- left channel ---- + # DTS Music mode enables a 100 Hz order-2 high-pass ahead of the EQ + # bank; Q 0.7071 is the Butterworth realisation of "order 2". + { type = builtin name = hp_l label = bq_highpass control = { "Freq" = 100.0 "Q" = 0.7071 } } + { type = builtin name = g0_l label = bq_raw # peaking fc 118 Hz +2.0 dB Q 2.8 + config = { coefficients = [ + { rate = 48000 b0 = 1.00072288513 b1 = -1.99416983128 b2 = 0.993692994118 a0 = 1.0 a1 = -1.99416983128 a2 = 0.994415938854 } + { rate = 44100 b0 = 1.0007866621 b1 = -1.99363219738 b2 = 0.993137001991 a0 = 1.0 a1 = -1.99363219738 a2 = 0.993923664093 } + ] } } + { type = builtin name = g1_l label = bq_raw # peaking fc 158 Hz -3.0 dB Q 3.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.999094188213 b1 = -1.99335944653 b2 = 0.994702637196 a0 = 1.0 a1 = -1.99335944653 a2 = 0.993796825409 } + { rate = 44100 b0 = 0.999014377594 b1 = -1.99273216724 b2 = 0.994235873222 a0 = 1.0 a1 = -1.99273216724 a2 = 0.993250250816 } + ] } } + { type = builtin name = g2_l label = bq_raw # peaking fc 254 Hz -4.5 dB Q 2.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.997050523758 b1 = -1.98426103592 b2 = 0.988360345364 a0 = 1.0 a1 = -1.98426103592 a2 = 0.985410928726 } + { rate = 44100 b0 = 0.996791899204 b1 = -1.98277056217 b2 = 0.98733985424 a0 = 1.0 a1 = -1.98277044296 a2 = 0.984131813049 } + ] } } + { type = builtin name = g3_l label = bq_raw # peaking fc 368 Hz -3.5 dB Q 2.0 + config = { coefficients = [ + { rate = 48000 b0 = 0.996014297009 b1 = -1.97352063656 b2 = 0.979950368404 a0 = 1.0 a1 = -1.97352063656 a2 = 0.975964605808 } + { rate = 44100 b0 = 0.995666921139 b1 = -1.9709777832 b2 = 0.978202879429 a0 = 1.0 a1 = -1.9709777832 a2 = 0.973869740963 } + ] } } + { type = builtin name = g4_l label = bq_raw # peaking fc 624 Hz -6.0 dB Q 1.7 + config = { coefficients = [ + { rate = 48000 b0 = 0.988305151463 b1 = -1.9460439682 b2 = 0.964804232121 a0 = 1.0 a1 = -1.9460439682 a2 = 0.953109502792 } + { rate = 44100 b0 = 0.987301409245 b1 = -1.94073450565 b2 = 0.961783409119 a0 = 1.0 a1 = -1.94073450565 a2 = 0.949084877968 } + ] } } + { type = builtin name = g5_l label = bq_raw # peaking fc 848 Hz -5.0 dB Q 1.8 + config = { coefficients = [ + { rate = 48000 b0 = 0.986983001232 b1 = -1.92765474319 b2 = 0.953532099724 a0 = 1.0 a1 = -1.92765474319 a2 = 0.940515041351 } + { rate = 44100 b0 = 0.985877394676 b1 = -1.9202760458 b2 = 0.949585258961 a0 = 1.0 a1 = -1.9202760458 a2 = 0.935462534428 } + ] } } + { type = builtin name = g6_l label = bq_raw # peaking fc 1.37 kHz -7.0 dB Q 2.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.979255020618 b1 = -1.89278125763 b2 = 0.945760846138 a0 = 1.0 a1 = -1.89278125763 a2 = 0.925015866756 } + { rate = 44100 b0 = 0.977528393269 b1 = -1.88076937199 b2 = 0.941246449947 a0 = 1.0 a1 = -1.88076937199 a2 = 0.918774843216 } + ] } } + { type = builtin name = g7_l label = bq_raw # peaking fc 1.78 kHz +3.0 dB Q 3.6 + config = { coefficients = [ + { rate = 48000 b0 = 1.01307702065 b1 = -1.8830935955 b2 = 0.923525154591 a0 = 1.0 a1 = -1.8830935955 a2 = 0.936602115631 } + { rate = 44100 b0 = 1.01415884495 b1 = -1.86829900742 b2 = 0.917198777199 a0 = 1.0 a1 = -1.86829900742 a2 = 0.931357502937 } + ] } } + { type = builtin name = g8_l label = bq_raw # peaking fc 2.90 kHz -5.0 dB Q 1.9 + config = { coefficients = [ + { rate = 48000 b0 = 0.959467947483 b1 = -1.67663598061 b2 = 0.855309844017 a0 = 1.0 a1 = -1.67663598061 a2 = 0.8147777915 } + { rate = 44100 b0 = 0.956516981125 b1 = -1.63998699188 b2 = 0.844775497913 a0 = 1.0 a1 = -1.63998699188 a2 = 0.801292479038 } + ] } } + { type = builtin name = g9_l label = bq_raw # peaking fc 3.98 kHz -3.5 dB Q 5.1 + config = { coefficients = [ + { rate = 48000 b0 = 0.983916223049 b1 = -1.64805448055 b2 = 0.919093132019 a0 = 1.0 a1 = -1.64805448055 a2 = 0.903009355068 } + { rate = 44100 b0 = 0.982770562172 b1 = -1.59883213043 b2 = 0.913330078125 a0 = 1.0 a1 = -1.59883213043 a2 = 0.896100640297 } + ] } } + { type = builtin name = g10_l label = bq_raw # peaking fc 5.53 kHz -2.0 dB Q 2.9 + config = { coefficients = [ + { rate = 48000 b0 = 0.977126538754 b1 = -1.32099425793 b2 = 0.800446629524 a0 = 1.0 a1 = -1.32099425793 a2 = 0.777573227882 } + { rate = 44100 b0 = 0.975850343704 b1 = -1.2404794693 b2 = 0.789312839508 a0 = 1.0 a1 = -1.2404794693 a2 = 0.765163242817 } + ] } } + { type = builtin name = g11_l label = bq_raw # peaking fc 13.6 kHz -5.0 dB Q 3.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.909254550934 b1 = 0.309279620647 b2 = 0.676059544086 a0 = 1.0 a1 = 0.309279620647 a2 = 0.58531409502 } + { rate = 44100 b0 = 0.910680770874 b1 = 0.437900751829 b2 = 0.681150853634 a0 = 1.0 a1 = 0.437900751829 a2 = 0.591831684113 } + ] } } + { type = builtin name = g12_l label = bq_raw # first-order HF tilt (+3.5 dB LF shelf falling to -6 dB at Nyquist) + config = { coefficients = [ + { rate = 48000 b0 = 0.999999999534 b1 = 0.500000000931 b2 = 0 a0 = 1.0 a1 = -0 a2 = -0 } + { rate = 44100 b0 = 0.999999999534 b1 = 0.918762207497 b2 = 0 a0 = 1.0 a1 = -0 a2 = -0 } + ] } } + # ---- right channel ---- + # DTS Music mode enables a 100 Hz order-2 high-pass ahead of the EQ + # bank; Q 0.7071 is the Butterworth realisation of "order 2". + { type = builtin name = hp_r label = bq_highpass control = { "Freq" = 100.0 "Q" = 0.7071 } } + { type = builtin name = g0_r label = bq_raw # peaking fc 118 Hz +2.0 dB Q 2.8 + config = { coefficients = [ + { rate = 48000 b0 = 1.00072288513 b1 = -1.99416983128 b2 = 0.993692994118 a0 = 1.0 a1 = -1.99416983128 a2 = 0.994415938854 } + { rate = 44100 b0 = 1.0007866621 b1 = -1.99363219738 b2 = 0.993137001991 a0 = 1.0 a1 = -1.99363219738 a2 = 0.993923664093 } + ] } } + { type = builtin name = g1_r label = bq_raw # peaking fc 158 Hz -3.0 dB Q 3.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.999094188213 b1 = -1.99335944653 b2 = 0.994702637196 a0 = 1.0 a1 = -1.99335944653 a2 = 0.993796825409 } + { rate = 44100 b0 = 0.999014377594 b1 = -1.99273216724 b2 = 0.994235873222 a0 = 1.0 a1 = -1.99273216724 a2 = 0.993250250816 } + ] } } + { type = builtin name = g2_r label = bq_raw # peaking fc 254 Hz -4.5 dB Q 2.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.997050523758 b1 = -1.98426103592 b2 = 0.988360345364 a0 = 1.0 a1 = -1.98426103592 a2 = 0.985410928726 } + { rate = 44100 b0 = 0.996791899204 b1 = -1.98277056217 b2 = 0.98733985424 a0 = 1.0 a1 = -1.98277044296 a2 = 0.984131813049 } + ] } } + { type = builtin name = g3_r label = bq_raw # peaking fc 368 Hz -3.5 dB Q 2.0 + config = { coefficients = [ + { rate = 48000 b0 = 0.996014297009 b1 = -1.97352063656 b2 = 0.979950368404 a0 = 1.0 a1 = -1.97352063656 a2 = 0.975964605808 } + { rate = 44100 b0 = 0.995666921139 b1 = -1.9709777832 b2 = 0.978202879429 a0 = 1.0 a1 = -1.9709777832 a2 = 0.973869740963 } + ] } } + { type = builtin name = g4_r label = bq_raw # peaking fc 624 Hz -6.0 dB Q 1.7 + config = { coefficients = [ + { rate = 48000 b0 = 0.988305151463 b1 = -1.9460439682 b2 = 0.964804232121 a0 = 1.0 a1 = -1.9460439682 a2 = 0.953109502792 } + { rate = 44100 b0 = 0.987301409245 b1 = -1.94073450565 b2 = 0.961783409119 a0 = 1.0 a1 = -1.94073450565 a2 = 0.949084877968 } + ] } } + { type = builtin name = g5_r label = bq_raw # peaking fc 848 Hz -5.0 dB Q 1.8 + config = { coefficients = [ + { rate = 48000 b0 = 0.986983001232 b1 = -1.92765474319 b2 = 0.953532099724 a0 = 1.0 a1 = -1.92765474319 a2 = 0.940515041351 } + { rate = 44100 b0 = 0.985877394676 b1 = -1.9202760458 b2 = 0.949585258961 a0 = 1.0 a1 = -1.9202760458 a2 = 0.935462534428 } + ] } } + { type = builtin name = g6_r label = bq_raw # peaking fc 1.37 kHz -7.0 dB Q 2.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.979255020618 b1 = -1.89278125763 b2 = 0.945760846138 a0 = 1.0 a1 = -1.89278125763 a2 = 0.925015866756 } + { rate = 44100 b0 = 0.977528393269 b1 = -1.88076937199 b2 = 0.941246449947 a0 = 1.0 a1 = -1.88076937199 a2 = 0.918774843216 } + ] } } + { type = builtin name = g7_r label = bq_raw # peaking fc 1.78 kHz +3.0 dB Q 3.6 + config = { coefficients = [ + { rate = 48000 b0 = 1.01307702065 b1 = -1.8830935955 b2 = 0.923525154591 a0 = 1.0 a1 = -1.8830935955 a2 = 0.936602115631 } + { rate = 44100 b0 = 1.01415884495 b1 = -1.86829900742 b2 = 0.917198777199 a0 = 1.0 a1 = -1.86829900742 a2 = 0.931357502937 } + ] } } + { type = builtin name = g8_r label = bq_raw # peaking fc 2.90 kHz -5.0 dB Q 1.9 + config = { coefficients = [ + { rate = 48000 b0 = 0.959467947483 b1 = -1.67663598061 b2 = 0.855309844017 a0 = 1.0 a1 = -1.67663598061 a2 = 0.8147777915 } + { rate = 44100 b0 = 0.956516981125 b1 = -1.63998699188 b2 = 0.844775497913 a0 = 1.0 a1 = -1.63998699188 a2 = 0.801292479038 } + ] } } + { type = builtin name = g9_r label = bq_raw # peaking fc 3.98 kHz -3.5 dB Q 5.1 + config = { coefficients = [ + { rate = 48000 b0 = 0.983916223049 b1 = -1.64805448055 b2 = 0.919093132019 a0 = 1.0 a1 = -1.64805448055 a2 = 0.903009355068 } + { rate = 44100 b0 = 0.982770562172 b1 = -1.59883213043 b2 = 0.913330078125 a0 = 1.0 a1 = -1.59883213043 a2 = 0.896100640297 } + ] } } + { type = builtin name = g10_r label = bq_raw # peaking fc 5.53 kHz -2.0 dB Q 2.9 + config = { coefficients = [ + { rate = 48000 b0 = 0.977126538754 b1 = -1.32099425793 b2 = 0.800446629524 a0 = 1.0 a1 = -1.32099425793 a2 = 0.777573227882 } + { rate = 44100 b0 = 0.975850343704 b1 = -1.2404794693 b2 = 0.789312839508 a0 = 1.0 a1 = -1.2404794693 a2 = 0.765163242817 } + ] } } + { type = builtin name = g11_r label = bq_raw # peaking fc 13.6 kHz -5.0 dB Q 3.3 + config = { coefficients = [ + { rate = 48000 b0 = 0.909254550934 b1 = 0.309279620647 b2 = 0.676059544086 a0 = 1.0 a1 = 0.309279620647 a2 = 0.58531409502 } + { rate = 44100 b0 = 0.910680770874 b1 = 0.437900751829 b2 = 0.681150853634 a0 = 1.0 a1 = 0.437900751829 a2 = 0.591831684113 } + ] } } + { type = builtin name = g12_r label = bq_raw # first-order HF tilt (+3.5 dB LF shelf falling to -6 dB at Nyquist) + config = { coefficients = [ + { rate = 48000 b0 = 0.999999999534 b1 = 0.500000000931 b2 = 0 a0 = 1.0 a1 = -0 a2 = -0 } + { rate = 44100 b0 = 0.999999999534 b1 = 0.918762207497 b2 = 0 a0 = 1.0 a1 = -0 a2 = -0 } + ] } } + { type = lv2 + name = limiter + plugin = "http://lsp-plug.in/plugins/lv2/limiter_stereo" + control = { + # Both default to enabled: "alr" regulates level toward the + # threshold and "boost" normalises the threshold up to full + # scale. A fixed tuning must switch them off or its tone drifts + # with programme level. + "alr" = 0 + "boost" = 0 + # The EQ cascade peaks at +3.66 dB (1.86 kHz, 48 kHz bank, HPF + # included), so -4.66 dB of input gain puts a full-scale sine at + # the worst frequency exactly at the -1 dBFS threshold. + "g_in" = 0.585 + "th" = 0.891 + } + } + ] + + links = [ + { output = "hp_l:Out" input = "g0_l:In" } + { output = "g0_l:Out" input = "g1_l:In" } + { output = "g1_l:Out" input = "g2_l:In" } + { output = "g2_l:Out" input = "g3_l:In" } + { output = "g3_l:Out" input = "g4_l:In" } + { output = "g4_l:Out" input = "g5_l:In" } + { output = "g5_l:Out" input = "g6_l:In" } + { output = "g6_l:Out" input = "g7_l:In" } + { output = "g7_l:Out" input = "g8_l:In" } + { output = "g8_l:Out" input = "g9_l:In" } + { output = "g9_l:Out" input = "g10_l:In" } + { output = "g10_l:Out" input = "g11_l:In" } + { output = "g11_l:Out" input = "g12_l:In" } + { output = "g12_l:Out" input = "limiter:in_l" } + { output = "hp_r:Out" input = "g0_r:In" } + { output = "g0_r:Out" input = "g1_r:In" } + { output = "g1_r:Out" input = "g2_r:In" } + { output = "g2_r:Out" input = "g3_r:In" } + { output = "g3_r:Out" input = "g4_r:In" } + { output = "g4_r:Out" input = "g5_r:In" } + { output = "g5_r:Out" input = "g6_r:In" } + { output = "g6_r:Out" input = "g7_r:In" } + { output = "g7_r:Out" input = "g8_r:In" } + { output = "g8_r:Out" input = "g9_r:In" } + { output = "g9_r:Out" input = "g10_r:In" } + { output = "g10_r:Out" input = "g11_r:In" } + { output = "g11_r:Out" input = "g12_r:In" } + { output = "g12_r:Out" input = "limiter:in_r" } + ] + + inputs = [ "hp_l:In" "hp_r:In" ] + outputs = [ "limiter:out_l" "limiter:out_r" ] + } + + audio.channels = 2 + audio.position = [ FL FR ] + + capture.props = { + node.name = "omarchy_speaker_tuning" + media.class = Audio/Sink + } + playback.props = { + node.name = "omarchy_speaker_tuning_output" + node.passive = true + target.object = "@SPEAKER_SINK@" + # This stream is the filter's output and is a movable sink input like any + # other, so anything that reroutes "all streams" to a newly selected + # output would drag the processing along with it -- onto headphones, or + # into the tuning's own sink, which is a cycle. Pin it. + node.dont-move = true + # If the speaker sink is not present yet -- the tuning host can start + # before the device is discovered -- WirePlumber would otherwise link this + # output to whatever default exists, quietly tuning the wrong device while + # the tuning sink still looks healthy. Wait for the named target instead. + # Both are needed: without linger, WirePlumber destroys the node rather + # than waiting (see its scripts/linking/find-defined-target.lua). + node.dont-fallback = true + node.linger = true + } + } + } +] diff --git a/default/audio/tunings/hp-elitebook-x-g2i/tuning.conf b/default/audio/tunings/hp-elitebook-x-g2i/tuning.conf new file mode 100644 index 0000000000..9835fac51d --- /dev/null +++ b/default/audio/tunings/hp-elitebook-x-g2i/tuning.conf @@ -0,0 +1,36 @@ +## HP EliteBook X G2i internal speakers (quad TAS2783 smart amps). +## +## A 100 Hz order-2 high-pass, thirteen biquads carried bit-exact from HP's +## factory DTS:X Ultra tuning, and a lookahead limiter, applied as a PipeWire +## filter-chain in front of the internal speaker sink. The stock Linux path +## already programs the TAS2783 amps (with the -12 dB safety trim on the +## per-amp Speaker Volume controls); this adds the perceptual voicing the +## Windows DTS APO layer provides and Linux does not. + +description="HP EliteBook X G2i speakers" +## Matched on the DMI product SKU, the narrowest identifier for this board. +## The DTS tuning is keyed per-board by HP (subsystem ID 8E86103C), so this +## must not widen to other EliteBook models whose speakers differ. +## +## D4DU1UT#ABA EliteBook X G2i -- the board the DTS data was extracted from +match_sku=("D4DU1UT#ABA") +## Unescaped dots: this is passed to awk as a string, where a backslash escape +## would be consumed before the regex sees it. +sink_pattern='^alsa_output.*sof_sdw.*HiFi__Speaker__sink$' + +## Provenance. Decoded from HP's own per-board DTS:X Ultra ("Eagle") tuning +## data extracted from this machine's Windows partition: +## extracted-windows/audio-dts-tuning-8E86/HPCMIT8E86103C/. The EQ biquads are +## the factory dts_gpeq_mode1_{48k,44k}_8E86103C.bin banks (the default +## "Music" content mode for the internal stereo speakers, per +## dts_apo4_oem_config_8E86103C.xml), used verbatim rather than re-fitted, so +## the magnitude response is bit-exact to Windows' EQ stage at both shipped +## sample rates. DTS's MBHL protection/loudness stage and TBHDX bass enhancer +## are not reproduced; the Omarchy lookahead limiter stands in for protection. +derived_from="HP/DTS factory tuning blob dts_gpeq_mode1 (SSID 8E86103C), decoded" +validated_hardware="HP EliteBook X G2i (D4DU1UT#ABA, SSID 8E86103C)" + +## Headroom: the EQ cascade (high-pass included) peaks at +3.66 dB at 1.86 kHz +## on the 48 kHz bank. The limiter input gain of -4.66 dB places a full-scale +## sine at that frequency exactly at the -1 dBFS limiter threshold. +limiter_headroom_db="0.0" diff --git a/install/hardware/all.sh b/install/hardware/all.sh index 9a9f1098ce..983cd174ac 100644 --- a/install/hardware/all.sh +++ b/install/hardware/all.sh @@ -32,6 +32,8 @@ run_logged "$OMARCHY_INSTALL/hardware/asus/fix-z13-touchpad.sh" run_logged "$OMARCHY_INSTALL/hardware/framework/qmk-hid.sh" +run_logged "$OMARCHY_INSTALL/hardware/hp/elitebook-x-g2i.sh" + run_logged "$OMARCHY_INSTALL/hardware/apple/fix-spi-keyboard.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-suspend-nvme.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-t2.sh" diff --git a/install/hardware/hp/elitebook-x-g2i.sh b/install/hardware/hp/elitebook-x-g2i.sh new file mode 100644 index 0000000000..6879606b74 --- /dev/null +++ b/install/hardware/hp/elitebook-x-g2i.sh @@ -0,0 +1,31 @@ +# HP EliteBook X G2i (Panther Lake) — speakers, mic and webcam +# +# This board ships hardware that no stock Linux path drives yet: +# +# Audio Quad TAS2783 smart amps + RT712 SDCA. Panther Lake's ACPI match table +# has no entry for this pairing, so the machine falls back to a +# barebones machine driver and NO Speaker device appears at all -- not +# quiet, absent. The package adds the match-table entry via DKMS (so it +# survives kernel upgrades), a UCM profile, and the firmware filename +# links the tas2783 driver asks for but linux-firmware does not ship. +# +# Webcam OmniVision OV05C10 on IPU7. Intel's CamHAL cannot drive it: its graph +# settings binary is built for a 2944-wide readout while the sensor +# exposes 2888x1808, so the ISP receives a negative crop and emits +# black frames. The package bypasses psys/CamHAL and does debayer, auto +# exposure and white balance in userspace instead. See its README for +# the trade-offs -- it is a workaround, not a fix, and the underlying +# bug is filed against intel/ipu7-camera-hal. +# +# Both are gated on the model string so they cannot touch other EliteBooks, +# whose speaker layout and camera module differ. + +if omarchy-hw-match "EliteBook X G2i"; then + omarchy-pkg-add hp-elitebook-x-g2i-audio + + # The camera stack only makes sense where the IPU7 + OV05C10 pairing is + # actually present; the audio fix applies to the whole model line. + if grep -q "OVTI05C1" /sys/bus/acpi/devices/*/hid 2>/dev/null; then + omarchy-pkg-add hp-elitebook-x-g2i-camera + fi +fi