Built-in equalizer #96

Closed
opened 2026-10-19 20:23:10 +02:00 by SBDeveloper · 1 comment
Owner

A built-in equalizer for everything the app plays. Design decided in a grilling session; see ADR 0023 and the Equalizer / Equalizer preset terms in GLOSSARY.md.

Why built in, not a plugin

process runs on the decoder thread, which buffers about 8 s ahead of the Output, so a slider move would be heard up to 8 s late. The filters must sit after the buffer, in the host; a plugin would only draw sliders.

Behaviour

  • Filters run in Player::render, after the buffer and before volume. Every Output (local, VBAN, stream listeners) and the Visualizer tap (ADR 0016) see the shaped audio. Spotify via Soloist is shaped.
  • Ten graphic bands plus a preamp, up to 12 dB cut or boost per band.
  • One global setting. Persists across restarts like the Crossfade length.
  • UI in Playback settings next to Crossfade: preset picker, 10 vertical sliders, preamp, reset.
  • Presets: Flat (default), a few built-ins, user-saved ones (save, rename, delete). Editing a band after choosing a preset makes it Custom until saved.
  • Flat means all gains 0: the host skips the stage. No separate enable switch.
  • Clipping: the host lowers gain by the largest boost; the user may raise the preamp by hand. No limiter.
  • No clicks: filter state carries across Track boundaries and crossfades; changed gains ramp over a few tens of ms.
  • Video mode (ADR 0022) is not shaped; the UI says why while a video plays (as #93 does for the Visualizer).

Out of scope

Per-Output, per-Source or per-Playlist curves; parametric bands; a limiter.

Notes for the implementer

  • UiNode (plugin UI) has no slider and does not need one: this is core Svelte UI, ui/src/lib/Playback.svelte already uses <input type="range"> for Crossfade.
  • Update the ADR status from "not yet implemented" when done.
A built-in equalizer for everything the app plays. Design decided in a grilling session; see ADR 0023 and the **Equalizer** / **Equalizer preset** terms in `GLOSSARY.md`. ## Why built in, not a plugin `process` runs on the decoder thread, which buffers about 8 s ahead of the Output, so a slider move would be heard up to 8 s late. The filters must sit after the buffer, in the host; a plugin would only draw sliders. ## Behaviour - Filters run in `Player::render`, after the buffer and before volume. Every Output (local, VBAN, stream listeners) and the Visualizer tap (ADR 0016) see the shaped audio. Spotify via Soloist is shaped. - Ten graphic bands plus a preamp, up to 12 dB cut or boost per band. - One global setting. Persists across restarts like the Crossfade length. - UI in Playback settings next to Crossfade: preset picker, 10 vertical sliders, preamp, reset. - Presets: Flat (default), a few built-ins, user-saved ones (save, rename, delete). Editing a band after choosing a preset makes it Custom until saved. - Flat means all gains 0: the host skips the stage. No separate enable switch. - Clipping: the host lowers gain by the largest boost; the user may raise the preamp by hand. No limiter. - No clicks: filter state carries across Track boundaries and crossfades; changed gains ramp over a few tens of ms. - Video mode (ADR 0022) is not shaped; the UI says why while a video plays (as #93 does for the Visualizer). ## Out of scope Per-Output, per-Source or per-Playlist curves; parametric bands; a limiter. ## Notes for the implementer - `UiNode` (plugin UI) has no slider and does not need one: this is core Svelte UI, `ui/src/lib/Playback.svelte` already uses `<input type="range">` for Crossfade. - Update the ADR status from "not yet implemented" when done.
SBDeveloper changed title from Equalizer plugin to Built-in equalizer 2026-10-21 20:30:49 +02:00
Author
Owner

Implemented on main (commits daa5584, e8c747a, 72776fc, e5e9183, b3d7f89; not pushed when this was written).

Built in rather than a plugin (ADR 0023): ten graphic bands and a preamp in Player::render, presets (built-in and user-saved), AutoEq import (FixedBandEQ exact, ParametricEQ kept as an Imported curve, GraphicEQ fitted to the sliders), a response graph, and the Playback settings card with two paths: Adjust yourself and Tuned for your headphones.

Scope added during the work and not in the original body: the AutoEq import, the response graph and the two-path card.

Not verified: the full core test suite (the one failure seen, low_delay a_receiver_that_is_not_set_up..., also fails at HEAD without this work) and the UI in the running app.

Implemented on main (commits daa5584, e8c747a, 72776fc, e5e9183, b3d7f89; not pushed when this was written). Built in rather than a plugin (ADR 0023): ten graphic bands and a preamp in Player::render, presets (built-in and user-saved), AutoEq import (FixedBandEQ exact, ParametricEQ kept as an Imported curve, GraphicEQ fitted to the sliders), a response graph, and the Playback settings card with two paths: Adjust yourself and Tuned for your headphones. Scope added during the work and not in the original body: the AutoEq import, the response graph and the two-path card. Not verified: the full core test suite (the one failure seen, low_delay `a_receiver_that_is_not_set_up...`, also fails at HEAD without this work) and the UI in the running app.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
SBDeveloper/medley#96
No description provided.