Repository navigation
EasyEffects
EasyEffects is the PipeWire audio-effects engine (equalizer, compressor, limiter, noise reduction, reverb…). ii-p3drovfx does not ask you to open its own window: the shell controls the running EasyEffects instance, switches its presets per output device, shows the state everywhere (bar, island, OSD, quick toggle, desktop widget) and ships its own Material 3 Expressive EasyEffects app — presets, a full effect editor with a live frequency-response curve, and per-device defaults.
EasyEffects itself must be installed — the distro package (the optional easyeffects feature: ii-p3drovfx deps add easyeffects, see Installer and CLI), the Flatpak, or both. Without it every EasyEffects surface in the shell hides itself.
- Install EasyEffects. The shell starts it in the background at login (
--hide-window --service-mode); nothing appears on screen. - Super + Ctrl + E opens the app (a toggle). Pick a preset on the Presets tab — it loads at once.
- Flip the effects off and on without losing the chain with the EasyEffects quick toggle in the Sidebar Dashboard, a middle click on the bar pill, or the bypass shortcut.
Only the app toggle has a default key. Three more Quickshell global shortcuts are registered and can be bound to any key in your Hyprland config, for example hl.bind("SUPER + CTRL + N", hl.dsp.global("quickshell:easyEffectsNextPreset")): easyEffectsNextPreset, easyEffectsPreviousPreset (cycle the presets for the current output device) and easyEffectsBypassToggle.
From a terminal:
qs -c ii ipc call easyeffects toggle # open/close the app
qs -c ii ipc call easyeffects openTab effects # presets | effects | devices
qs -c ii ipc call easyeffects preset "A50 · Music"
qs -c ii ipc call easyeffects inputPreset "Voice"
qs -c ii ipc call easyeffects next | previous
qs -c ii ipc call easyeffects bypass on|off|toggle # "on" bypasses (effects off)
qs -c ii ipc call easyeffects status # JSON: running, bypassed, presets, device…A floating window titled ii EasyEffects (Hyprland floats and centres it), built from the same frame as the Clock App — rail, top bar, side sheet — and opened at the same size as the Clock, Usage and Modes apps. The window is built asynchronously when opened, kept warm for 30 seconds, then destroyed with everything in it.
- A hero card shows the loaded preset: its name, family, the devices it plays through, whether the effects are Playing, Bypassed or off, and the device default pill ("Device default", or "Default: X" when the device normally starts with another preset — click to load it). Behind it, a live contour-line texture (topography shader) drifts, and reacts to the music when Follow the music is on: it swells with the volume and kicks on bass hits. It animates only while the window is visible and animations are not reduced.
- A grid of preset cards with a mini frequency curve each, filter chips by family (with counts), an Output / Input switch (EasyEffects runs two pipelines: output processes what you hear, input processes your microphone), and an extended New preset button. A card shows In use and the Default star on the preset the device starts with; on hover it offers edit, make default and a menu — Rename, Duplicate, Delete. Renaming a preset makes device defaults follow the new name.
-
Import a
.jsonpreset file (a native file chooser throughkdialogorzenity). -
Families come from the preset names:
"A50 · Music"and"Speakers - Movies"both split on·/-, so the headset's presets stay grouped away from the laptop speakers'.
For the preset that is loaded: the chain in the order the sound goes through it, as 68-pixel rows with up/down arrows (reorder), a bypass switch and a trash button; Add effect opens a searchable catalog of all 30+ EasyEffects plugins (Equalizer, Compressor, Limiter, Autogain, Bass Enhancer, Convolver, Crystalizer, Deep Noise Remover, Noise Reduction, Echo Canceller, Gate, Loudness, Maximizer, Multiband Compressor, Pitch, Reverberation, Stereo Tools, Voice Suppressor…).
- A Frequency response curve at the top is the sum of the enabled equalizers.
- Tapping an effect opens a sheet with one tile per control, each with its icon, label, value and slider — ranges, defaults and choices come from a table generated from the EasyEffects source (
services/easyEffects/EasyEffectsPlugins.js, produced byscripts/easyeffects/generate_plugin_table.pyagainst a pinned EasyEffects tag; currently 8.3.0). - The Equalizer sheet adds the response curve, one vertical bar per band (the row scrolls horizontally above ~12 bands), chips for the mode (Parametric, Linear phase, …) and, on tapping a band, that band's own settings (the sheet scrolls to them; band types include bell, low shelf and high shelf).
-
Hear edits at once (
easyEffects.liveApply, on by default): a knob change is sent to the running pipeline immediately over the socket, before you save. The header shows Unsaved · playing now with Revert and Save to preset. If EasyEffects is not running, or the preset belongs to a device that is not playing, edits go to the file only and the page says so.
Two panels (stacked on a narrow window): Output devices and Input devices, each with the connected ones — the device in use is highlighted — and a select for the preset it starts with. A saved default for a device that is not connected stays listed. This is the same autoload table EasyEffects keeps in its own autoload/ folder, so the shell and EasyEffects' own window agree.
When more than one device exists, a strip of device chips sits above the presets. Choosing a device that is not playing changes what the cards do: clicking a card saves it as the preset that device starts with (Starts with this) without touching the audio you hear, and the hero offers Use this device. Editing in that mode changes the saved file only.
The gear opens a page that slides over the tabs. Preset card holds the look of the hero (centrepiece Shape, Tone curve or None, the contour lines and their opacity, Follow the music, and a switch for each part: device, state, device default, wave, family, preset name, effects in the chain, buttons) — the same options drive the desktop widget. Quick switching has Presets to switch between (the device's family, or every output preset), Show the preset on switch, the Island bubble and a reminder of the keybinds. Editor has Hear edits at once and which tab the app Opens on. EasyEffects has Apply the device's preset on start, Switch preset with the device, and the running state (build — native or Flatpak — version, presets folder) with a Start / Quit button.
EasyEffects is supposed to load a device's preset when that device becomes the default. It does so unreliably — Pro Audio sinks, a service without a window, Bluetooth that reconnects. So the shell does it too:
-
On start (
easyEffects.applyDeviceDefaultOnStart): EasyEffects skips autoload on Pro Audio devices (wwmm/easyeffects#5275); the shell loads the saved preset itself. -
On switch (
applyDeviceDefaultOnSwitch): when the default output or input device — or its route — changes,EasyEffects._deviceChangedwaits 1.2 s for things to settle, reads the state, then 0.6 s later applies the saved preset for that device. Only a device change triggers it, never your manual choice; a device with no saved preset keeps the current preset. Loading the same preset twice is harmless.
The device and its route are both part of the key (a laptop's speakers and its headphone jack are one PipeWire node with two routes), so each gets its own preset.
| Surface | What you get |
|---|---|
| Quick toggle (Dashboard, classic and Android layouts) | Effects on/off with a state indicator; its dialog lists Presets for (a click switches; Show all reveals every preset; This device goes back) and Details opens the app |
Bar pill (EasyEffectsIndicator) |
The preset glyph and short name while EasyEffects runs; click or scroll cycles presets, right click holds the popup open with all of them, middle click bypasses |
| Dynamic Island | An island bubble beside the island while EasyEffects runs (dynamicIsland.widgets.easyEffects.enable, side left/right): scroll to switch presets, rest on it for a card with the presets |
| OSD | A pill naming the new preset (Show the preset on switch) and EasyEffects on/off for the bypass shortcut |
| Dashboard button (bar) | An equalizer icon cue; the bars move only on on / off and settle to a still frame (animating five shapes forever raised CPU) |
| Desktop widgets |
EasyEffects Pill (1×0.5 toggle) and EasyEffects Preset in portrait (360×640) and landscape (720×320) — the same hero as the app, with Edit effects and Bypass buttons, a contour texture that pauses when a window covers the monitor's workspace (pauseOnWindows), and a pipeline choice (output or input) |
| Waffle action center | An EasyEffects page in the volume flyout: effects off, or one of the device's presets |
| Modes & Routines | An easyEffects action in the Sound category (on / off, revert supported) |
| Key | Default | Meaning |
|---|---|---|
appEnable |
true |
Gate for the app window only — presets still work from keybinds and scripts when off |
preferFlatpak |
false |
Which build runs when both are installed (also read by the Hyprland autostart) |
startTab |
"last" |
last, presets, effects or devices
|
cycleScope |
"device" |
The quick switchers cycle the device's family (device) or every preset (all) |
osdOnSwitch |
true |
Announce the new preset |
applyDeviceDefaultOnStart / applyDeviceDefaultOnSwitch
|
true / true
|
The device autoload fixes above |
liveApply |
true |
Hear edits before saving |
hero.* |
all on | Parts of the app's preset card: topography, topographyReactive, topographyStrength, art (shape / curve / none), showDevice, showState, showDefault, showWave, showCaption, showName, showEffects, showButtons
|
widget.* |
same | The desktop widget, shared by both shapes, plus pipeline and pauseOnWindows
|
The app's switch lives in Settings → Overlays & OSD → EasyEffects App; the widget has its own page under Desktop Widgets.
-
Install detection. The service finds the native package and the Flatpak and their major versions. EasyEffects 8 keeps presets in the data directory, 7 in the config directory, and the Flatpak keeps them inside
~/.var/app/com.github.wwmm.easyeffects.preferFlatpakpicks one when both exist; otherwise whichever exists is used. -
Running = its sink exists. Whether EasyEffects runs is whether
easyeffects_sinkexists in PipeWire. Nothing polls. -
A local socket. Commands (load preset, bypass, set a parameter, read the state) go over EasyEffects' own socket (
$XDG_RUNTIME_DIR/EasyEffectsServer) — one connection opened on demand and dropped when idle. The Flatpak keeps its/tmpprivate, so the shell points itsTMPDIRat the one folder shared with the host ($XDG_RUNTIME_DIR/app/com.github.wwmm.easyeffects) and looks for the socket there first. Without the socket (EasyEffects 7, a sandbox that hides it) the command line does the same for loading presets and bypass. -
Live state is asked, not watched. Bypass and loaded presets live only inside EasyEffects, so the service asks at the moments they can change: EasyEffects starting, the output device changing, and after every command it sends. A surface that wants fresh state while open (the app, the quick dialog, the island card) holds a refresh (
EasyEffects.hold(key, true)), and only while one is held does a slow re-ask run. -
Presets are files. The folders under
…/easyeffects/{output,input,autoload}/are watched. The service reads, writes, renames, duplicates, deletes and imports them (reading every preset in one process for the library view), and sets a device's autoload entry.
| Piece | Role |
|---|---|
services/EasyEffects.qml |
The singleton: install detection, socket and CLI, presets, device autoload, every action |
services/easyEffects/EasyEffectsLogic.js |
Pure helpers (preset families, device/route keys, chain response, control icons) — covered by scripts/tests/test_easyeffects_logic.cjs
|
services/easyEffects/EasyEffectsPlugins.js |
The generated plugin table |
modules/ii/easyEffects/ |
The app: EasyEffectsApp.qml (keybinds, IPC, lifecycle), EasyEffectsAppContent.qml, EasyEffectsEditor.qml, tabs/, components/ (EasyEffectsStyle design tokens, ResponseCurve, BandSlider, DeviceView, TopographyField + shader) |
modules/ii/background/widgets/system/EasyEffects*Widget.qml |
The desktop widgets |
modules/ii/dynamicIsland/activities/easyEffects/ |
The island activity |
modules/common/quickToggleDialogs/easyEffects/ |
The quick-toggle dialog |
hyprland/execs.lua, hyprland/rules.lua, hyprland/keybinds.lua
|
Autostart (honours preferFlatpak), window rules, Super + Ctrl + E
|
- Sidebar Dashboard — the quick toggle
- Bar & Dynamic Island — the bar pill and the island activity
- Desktop Widgets — the preset and pill widgets
- Clock App — the app frame EasyEffects reuses
- System Integration — audio, mono audio and earbuds
- Modes & Routines — the EasyEffects action