Add the installer, uninstaller, and generated docs

This commit is contained in:
2026-09-20 00:19:38 -04:00
parent 0c422e7345
commit 3e65d176e3
100 changed files with 2585 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# Host config for the Blob speaker tuning.
#
# This exists so the tuning gets its own PipeWire client rather than sharing
# PipeWire's stock filter-chain.conf. That config merges every fragment in
# ~/.config/pipewire/filter-chain.conf.d/, so hosting the tuning there would load
# any unrelated filter a user keeps in that directory -- duplicating filters
# already hosted elsewhere, and stopping them all when the tuning is switched off.
#
# Installed as ~/.config/pipewire/blob-speaker-tuning.conf with the tuning
# graph merged from blob-speaker-tuning.conf.d/, and run with
# pipewire -c blob-speaker-tuning.conf
#
# The contents are the minimum a filter-hosting client needs, taken from
# /usr/share/pipewire/filter-chain.conf.
context.properties = {
log.level = 0
}
context.spa-libs = {
audio.convert.* = audioconvert/libspa-audioconvert
support.* = support/libspa-support
}
context.modules = [
# Boost the audio thread priority.
{ name = libpipewire-module-rt
args = { }
flags = [ ifexists nofail ]
}
# The native communication protocol.
{ name = libpipewire-module-protocol-native }
# Lets this process provide nodes to PipeWire.
{ name = libpipewire-module-client-node }
# Wraps nodes in an adapter with a converter and resampler.
{ name = libpipewire-module-adapter }
]
@@ -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 = "blob_speaker_tuning"
media.class = Audio/Sink
}
playback.props = {
node.name = "blob_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"