Cover the XPS 16 with the XPS 14 speaker tuning

Reported to suit the XPS 16 as well, so the tuning now matches both machines and
the directory is named for the pair rather than the one model.

Gating moves from the DMI product name to the product SKU, which is what Dell keys
the Cirrus speaker firmware on -- 10280db9 for the XPS 14, 10280dba for the XPS 16
-- so it identifies the speaker hardware itself instead of a marketing name. SKUs
are compared as whole values, not substrings, so this cannot widen to the rest of
the XPS line the way a short product-name match would: product_family on these
machines is "Dell Laptops".

match_sku and match_dmi are now lists, letting one tuning name every model it
covers. A plain string still works as a single entry.

The measurements in tuning.conf were taken on the XPS 14 (0DB9). The XPS 16 is
covered on report rather than measurement, and tuning.conf and the graph both say
so, because the profile's bass lift and high-pass were fitted against one
machine's excursion and distortion limits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-24 18:32:31 -07:00
co-authored by Claude Opus 5
parent aa9f0c54c5
commit a466dcc04f
4 changed files with 70 additions and 16 deletions
@@ -0,0 +1,140 @@
# Dell XPS 14 / XPS 16 (2026) speaker tuning.
#
# Biquad chain fitted to the measured response of the xps-audio-linux EasyEffects
# profile under a dense pink-weighted multitone of 104 bin-aligned tones,
# followed by a lookahead limiter. Measures 1.24 dB RMS against that reference
# (0.97 dB weighted over the fit's own error metric).
#
# Fitted and measured on the XPS 14 (SKU 0DB9); the XPS 16 (0DBA) is covered on
# report that the same profile suits it. See tuning.conf.
#
# Q below 200 Hz is capped at 1.8 on purpose. A closer magnitude fit is possible
# with high-Q sections, but the reference produces its narrow bass features by
# convolution, and reproducing them with high-Q biquads swung group delay 31 ms
# across 63-80 Hz, which smears bass transients. The cap costs 0.33 dB and
# halves the swing.
#
# This is a plain filter-chain sink rather than a WirePlumber smart filter. A
# smart filter is the better shape -- it would leave the real device as the
# default output instead of adding a second one -- but on PipeWire 1.6.8 /
# WirePlumber 0.5.15 this graph loads and links correctly as a smart filter and
# then passes audio through unprocessed: its controls are present and
# mpv -> filter -> sink links are made, yet the filter's input monitor and the
# speaker sink's monitor measure identically. Revisit when that is understood.
#
# 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 = [
{ type = builtin name = s0_l label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s1_l label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s2_l label = bq_peaking control = { "Freq" = 83.4 "Q" = 1.8 "Gain" = -8.0 } }
{ type = builtin name = s3_l label = bq_peaking control = { "Freq" = 100.4 "Q" = 1.59 "Gain" = 7.47 } }
{ type = builtin name = s4_l label = bq_peaking control = { "Freq" = 250.5 "Q" = 2.966 "Gain" = -4.7 } }
{ type = builtin name = s5_l label = bq_peaking control = { "Freq" = 419.8 "Q" = 3.0 "Gain" = -5.83 } }
{ type = builtin name = s6_l label = bq_peaking control = { "Freq" = 631.3 "Q" = 2.515 "Gain" = -10.33 } }
{ type = builtin name = s7_l label = bq_peaking control = { "Freq" = 894.4 "Q" = 4.0 "Gain" = -2.42 } }
{ type = builtin name = s8_l label = bq_peaking control = { "Freq" = 1355.7 "Q" = 2.884 "Gain" = 6.92 } }
{ type = builtin name = s9_l label = bq_peaking control = { "Freq" = 1707.2 "Q" = 1.311 "Gain" = -6.54 } }
{ type = builtin name = s10_l label = bq_peaking control = { "Freq" = 3100.0 "Q" = 0.5 "Gain" = -10.09 } }
{ type = builtin name = s11_l label = bq_peaking control = { "Freq" = 3200.0 "Q" = 1.048 "Gain" = 3.09 } }
{ type = builtin name = s12_l label = bq_highshelf control = { "Freq" = 6015.2 "Q" = 1.5 "Gain" = -1.34 } }
{ type = builtin name = s0_r label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s1_r label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s2_r label = bq_peaking control = { "Freq" = 83.4 "Q" = 1.8 "Gain" = -8.0 } }
{ type = builtin name = s3_r label = bq_peaking control = { "Freq" = 100.4 "Q" = 1.59 "Gain" = 7.47 } }
{ type = builtin name = s4_r label = bq_peaking control = { "Freq" = 250.5 "Q" = 2.966 "Gain" = -4.7 } }
{ type = builtin name = s5_r label = bq_peaking control = { "Freq" = 419.8 "Q" = 3.0 "Gain" = -5.83 } }
{ type = builtin name = s6_r label = bq_peaking control = { "Freq" = 631.3 "Q" = 2.515 "Gain" = -10.33 } }
{ type = builtin name = s7_r label = bq_peaking control = { "Freq" = 894.4 "Q" = 4.0 "Gain" = -2.42 } }
{ type = builtin name = s8_r label = bq_peaking control = { "Freq" = 1355.7 "Q" = 2.884 "Gain" = 6.92 } }
{ type = builtin name = s9_r label = bq_peaking control = { "Freq" = 1707.2 "Q" = 1.311 "Gain" = -6.54 } }
{ type = builtin name = s10_r label = bq_peaking control = { "Freq" = 3100.0 "Q" = 0.5 "Gain" = -10.09 } }
{ type = builtin name = s11_r label = bq_peaking control = { "Freq" = 3200.0 "Q" = 1.048 "Gain" = 3.09 } }
{ type = builtin name = s12_r label = bq_highshelf control = { "Freq" = 6015.2 "Q" = 1.5 "Gain" = -1.34 } }
{ 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
"g_in" = 0.5456
"th" = 0.891
}
}
]
links = [
{ output = "s0_l:Out" input = "s1_l:In" }
{ output = "s1_l:Out" input = "s2_l:In" }
{ output = "s2_l:Out" input = "s3_l:In" }
{ output = "s3_l:Out" input = "s4_l:In" }
{ output = "s4_l:Out" input = "s5_l:In" }
{ output = "s5_l:Out" input = "s6_l:In" }
{ output = "s6_l:Out" input = "s7_l:In" }
{ output = "s7_l:Out" input = "s8_l:In" }
{ output = "s8_l:Out" input = "s9_l:In" }
{ output = "s9_l:Out" input = "s10_l:In" }
{ output = "s10_l:Out" input = "s11_l:In" }
{ output = "s11_l:Out" input = "s12_l:In" }
{ output = "s12_l:Out" input = "limiter:in_l" }
{ output = "s0_r:Out" input = "s1_r:In" }
{ output = "s1_r:Out" input = "s2_r:In" }
{ output = "s2_r:Out" input = "s3_r:In" }
{ output = "s3_r:Out" input = "s4_r:In" }
{ output = "s4_r:Out" input = "s5_r:In" }
{ output = "s5_r:Out" input = "s6_r:In" }
{ output = "s6_r:Out" input = "s7_r:In" }
{ output = "s7_r:Out" input = "s8_r:In" }
{ output = "s8_r:Out" input = "s9_r:In" }
{ output = "s9_r:Out" input = "s10_r:In" }
{ output = "s10_r:Out" input = "s11_r:In" }
{ output = "s11_r:Out" input = "s12_r:In" }
{ output = "s12_r:Out" input = "limiter:in_r" }
]
inputs = [ "s0_l:In" "s0_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
}
}
}
]
@@ -0,0 +1,39 @@
## Dell XPS 14 / XPS 16 (2026) internal speakers.
##
## Thirteen biquads and a lookahead limiter, applied as a PipeWire filter-chain
## in front of the internal speaker sink. The stock Linux path already loads
## Dell's Cirrus smart-amplifier firmware; this adds the perceptual voicing the
## Windows Waves layer provides and Linux does not.
description="Dell XPS 14/16 (2026) speakers"
## Matched on the DMI product SKU, which is what Dell keys the Cirrus speaker
## firmware on -- 10280db9 for the XPS 14 and 10280dba for the XPS 16 -- so it
## identifies the speaker hardware itself rather than a marketing name. Compared as
## whole values, so this cannot widen to the rest of the XPS line.
##
## 0DB9 XPS 14 -- measured here, see below
## 0DBA XPS 16 -- included on report that this profile suits it, not measured
match_sku=("0DB9" "0DBA")
## 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. Derived by measuring the response of the xps-clone EasyEffects
## profile from https://github.com/spencerbull/xps-audio-linux (MIT) and fitting
## a biquad chain to it. No upstream asset is redistributed: the convolution
## impulse response is not carried, so this tuning has no binary blob and is
## sample-rate agnostic.
derived_from="xps-audio-linux xps-clone (MIT, spencerbull)"
validated_by="dhh"
validated_on="2026-07-24"
## The measurements below were taken on the XPS 14 (0DB9). The XPS 16 (0DBA) is
## covered on report rather than measurement; re-measure there before treating
## these figures as describing it.
validated_hardware="XPS 14 DA14260 (0DB9)"
## Measured against that reference under a dense pink-weighted multitone of 104
## bin-aligned tones. See docs/AUDIO-TUNING.md for how to reproduce these.
magnitude_rms_db="1.24"
bass_group_delay_swing_ms="13.2"
limiter_headroom_db="1.6" ## worst-case peak on a hot master vs -1 dBFS
dynamic_range_delta_lu="0.1"