Add the installer, uninstaller, and generated docs
This commit is contained in:
@@ -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"
|
||||
Reference in New Issue
Block a user