Sort documentation by genre and document the layout
Task procedure lives in agents/skills/ (migrations.md moves there), system-shape reference in docs/ (AUDIO-TUNING.md renamed to match), end-user documentation in manual/. AGENTS.md now states the split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
75b99f7fd4
commit
16c8888f96
@@ -0,0 +1,141 @@
|
||||
# Speaker tunings
|
||||
|
||||
Laptop speakers ship voiced by the vendor's Windows DSP layer, which Linux does
|
||||
not get. A tuning restores that as a PipeWire filter-chain in front of the
|
||||
internal speaker sink: a declarative graph hosted by a small PipeWire client, with
|
||||
no GUI app and no binary blob.
|
||||
|
||||
```
|
||||
default/audio/tunings/<vendor>-<model>/
|
||||
├── tuning.conf # description, match, provenance, measurements
|
||||
└── filter-chain.conf # the graph, with @SPEAKER_SINK@ substituted on install
|
||||
```
|
||||
|
||||
`on` renders the graph into `~/.config/pipewire/omarchy-speaker-tuning.conf.d/`
|
||||
and runs it as its own PipeWire client via `omarchy-speaker-tuning.service`, rather than
|
||||
loading it into the audio daemon. The daemon only reads its own config at startup,
|
||||
so a daemon-loaded tuning could only be switched by restarting PipeWire — which
|
||||
drops every PulseAudio client's connection, and applications that do not reconnect
|
||||
(Spotify) then have to be restarted by hand. Hosting it separately makes switching
|
||||
a start/stop of one small process, and contains failure: a malformed tuning breaks
|
||||
only that service instead of stopping PipeWire from starting at all.
|
||||
|
||||
Tunings apply automatically: `install/hardware/speaker-tuning.sh` installs the
|
||||
LV2 dependency and `install/user/first-run/audio-tuning.sh` applies the tuning,
|
||||
both gated on the match. Machines without a matching tuning are untouched.
|
||||
|
||||
Switching it on happens at first-run, not at finalize-user time, because finalize-user
|
||||
also runs in the ISO chroot where there is no audio server: the sink a tuning has
|
||||
to target does not exist there, so nothing could be written — and nothing would
|
||||
retry, since the finalizer marks all shipped migrations complete on a fresh
|
||||
install. Matching itself deliberately does not consult the audio graph, so the
|
||||
LV2 dependency is still installed in the chroot.
|
||||
|
||||
```bash
|
||||
omarchy audio tuning on # install the matching tuning
|
||||
omarchy audio tuning off # remove it, back to raw speakers
|
||||
omarchy audio tuning status # installed? in use? what matches?
|
||||
```
|
||||
|
||||
`match` and `fronted-sink` are also accepted; they exist for the install hooks
|
||||
and the sink-listing scripts rather than for daily use.
|
||||
|
||||
## Adding a tuning
|
||||
|
||||
Add a directory with a `tuning.conf` and a `filter-chain.conf`. No new command is
|
||||
needed: matching is data. A tuning declares how to recognise its hardware, plus the
|
||||
`sink_pattern` its graph targets:
|
||||
|
||||
| Key | Matches on | Notes |
|
||||
|---|---|---|
|
||||
| `match_sku` | DMI product SKU, whole value | Most precise. Vendors key speaker firmware on it, so it identifies the hardware rather than a marketing name |
|
||||
| `match_dmi` | DMI product name or family, substring | Convenient, but a short string widens fast — `product_family` here is `Dell Laptops` |
|
||||
| `match_command` | Any predicate you name | For hardware needing a sharper test than either |
|
||||
|
||||
`match_sku` and `match_dmi` are lists, so one tuning can cover several models it has
|
||||
been validated on:
|
||||
|
||||
```bash
|
||||
match_sku=("0DB9" "0DBA") # XPS 14 and XPS 16
|
||||
```
|
||||
|
||||
`sink_pattern` is required whichever method you use, since the graph's target sink
|
||||
is substituted from it.
|
||||
|
||||
Gate narrowly and widen as models are validated; a tuning aimed at the wrong
|
||||
drivers can sound worse than none and can stress them. When a tuning covers a model
|
||||
it was not measured on, say so in `tuning.conf` — the provenance fields are there to
|
||||
keep that distinction visible rather than implied.
|
||||
|
||||
Two hard requirements:
|
||||
|
||||
- **End in a limiter.** Peaks must stay under 0 dBFS with headroom.
|
||||
- **Do not boost what the drivers cannot deliver.** The XPS 14 tuning
|
||||
deliberately *cuts* 40 Hz by around 18 dB. Excursion down there buys nothing
|
||||
and costs distortion.
|
||||
|
||||
## Building one
|
||||
|
||||
Measuring a laptop and fitting a filter-chain to it is a separate job with its own
|
||||
tools, in [omarchy-audio-tuner](https://github.com/omacom-io/omarchy-audio-tuner).
|
||||
It is not installed by default — almost nobody authoring a tuning, and it needs
|
||||
python, ffmpeg and mpv.
|
||||
|
||||
```bash
|
||||
omarchy pkg add omarchy-audio-tuner
|
||||
```
|
||||
|
||||
Its README is the walkthrough, and covers both cases: copying a reference that
|
||||
already sounds right (how the XPS 14 tuning was made, no microphone needed), and
|
||||
designing from scratch, which needs a *calibrated* measurement mic and a target
|
||||
curve that measurement alone cannot give you.
|
||||
|
||||
## What a tuning must report
|
||||
|
||||
A tuning is not reviewable on "sounds better to me". Record these, measured, in
|
||||
`tuning.conf`:
|
||||
|
||||
| Field | What it is |
|
||||
|---|---|
|
||||
| `magnitude_rms_db` | Deviation from the reference or target it was fitted to |
|
||||
| `bass_group_delay_swing_ms` | Max minus min group delay, 30–300 Hz |
|
||||
| `limiter_headroom_db` | Worst-case peak against the limiter threshold |
|
||||
| `dynamic_range_delta_lu` | LRA change against the reference |
|
||||
|
||||
Measure electrically by capturing the physical speaker sink's monitor, which sits
|
||||
upstream of the volume control, so results are independent of listening level.
|
||||
|
||||
## How this fits the audio graph
|
||||
|
||||
Two things about the surrounding system are worth knowing, because both caused
|
||||
real bugs:
|
||||
|
||||
- **Volume lives downstream of the tuning.** A tuning is a virtual sink that
|
||||
becomes the default output, and changing *its* volume would alter the level
|
||||
going into the processing — moving the display while the speakers stay put, and
|
||||
changing the tone of anything with a compressor or limiter in it.
|
||||
`omarchy-audio-output-sink` is the single definition of "which sink does this
|
||||
output's volume really use": it resolves a sink through any DSP sink to the
|
||||
physical one, and with no argument resolves the current default output. The
|
||||
volume keys, the output switcher's OSD and the audio panel all use it, so they
|
||||
cannot disagree. Resolving the *current default* rather than "whatever a tuning
|
||||
fronts" is what keeps it correct when headphones or HDMI are selected while a
|
||||
tuning still exists.
|
||||
- **The fronted sink is hidden.** The tuning and the physical speakers both exist
|
||||
in the graph, and selecting the physical one would only bypass the tuning. So
|
||||
`omarchy-audio-sink-availability` reports it unavailable and
|
||||
`omarchy-audio-output-switch` skips it, leaving one speaker entry in the panel.
|
||||
A WirePlumber smart filter would remove the need for both — it leaves the real
|
||||
device as the default output — but on PipeWire 1.6.8 / WirePlumber 0.5.15 the
|
||||
graph loads and links correctly as a smart filter and then passes audio through
|
||||
unprocessed. Revisit when that is understood.
|
||||
- **EasyEffects cannot coexist with a tuning.** It moves any stream that follows
|
||||
the default sink to its own sink, so it would grab audio back from a
|
||||
filter-chain. `on` refuses while it is running rather than installing a graph
|
||||
that would be bypassed.
|
||||
- **The tuning's own output must not be moved.** A filter-chain's output is a
|
||||
playback stream like any other, so anything rerouting "all streams" to a newly
|
||||
selected output would drag the processing with it — onto headphones, or into the
|
||||
tuning's own sink, which is a cycle. The tuning sets `node.dont-move`, and
|
||||
`omarchy-audio-output-set-default` moves only streams that carry an
|
||||
`application.name`.
|
||||
Reference in New Issue
Block a user