# eqtui Architecture
> Terminal-native audio effects processor for PipeWire
```mermaid
graph TB
User((User))
subgraph "CLI Layer"
Main["main.rs<br/>CLI entry point"]
CLI["cli.rs<br/>stop / restart / load"]
end
subgraph "TUI Process (attach)"
TUI["TUI Manager<br/>(ratatui)"]
Event["event.rs<br/>Event Handler"]
App["app.rs<br/>Application State"]
Client["client.rs<br/>Daemon IPC Client"]
subgraph "Handlers"
Normal["handler/normal.rs<br/>Normal Mode"]
Insert["handler/insert.rs<br/>Insert Mode"]
Command["handler/command.rs<br/>Command Mode"]
Visual["handler/visual.rs<br/>Visual Mode"]
end
subgraph "TUI Widgets"
Devices["tui/devices.rs<br/>Device List"]
EQTable["tui/eq_table.rs<br/>EQ Band Table"]
Graph["tui/graph.rs<br/>EQ Curve Graph"]
Status["tui/status.rs<br/>Monitoring Panel"]
end
end
subgraph "Daemon Process"
DAEMON["daemon.rs<br/>Daemon Core"]
State["daemon.rs<br/>DaemonState<br/>(9× Mutex)"]
Protocol["protocol.rs<br/>JSON-line IPC"]
subgraph "Daemon Threads"
AcceptLoop["Accept Loop<br/>(main thread)"]
Bridge["pw-bridge Thread<br/>PW event → State"]
PeakBroadcast["peak-broadcast<br/>66ms timer"]
SignalWatcher["signal-watcher<br/>100ms poll"]
ClientThread["client-N Thread<br/>(per connection)"]
end
subgraph "PipeWire Threads"
PWMainloop["PW Mainloop Thread<br/>pw/run.rs"]
subgraph "PW Sub-threads"
LinkWorker["pw-link-worker<br/>device connect/disconnect"]
NullChecker["null-sink-checker<br/>500ms source poll"]
end
subgraph "Audio Pipeline"
Filter["pw/filter.rs<br/>process_cb (RT)"]
Equalizer["effects/equalizer.rs<br/>AudioEq Biquad Chain"]
Pipeline["pipeline.rs<br/>Pipeline (atomics)"]
end
end
end
subgraph "Shared Components"
Config["config.rs<br/>TOML Config"]
Profiles["profiles.rs<br/>EQ Preset Profiles"]
StateTypes["state.rs<br/>EqBand, FilterState, PwEvent"]
Parser["autoeq/parser.rs<br/>PEQ File Parser"]
end
subgraph "External"
PW["PipeWire Daemon<br/>(pw_filter)"]
PWLink["pw-link CLI<br/>(device routing)"]
end
%% User flow
User --> Main
Main --> CLI
Main --> TUI
%% TUI internals
TUI --> Event
TUI --> App
TUI --> Devices
TUI --> EQTable
TUI --> Graph
TUI --> Status
App --> Normal
App --> Insert
App --> Command
App --> Visual
App --> Client
%% TUI → Daemon IPC
Client -- "Unix socket" --> Protocol
Protocol --> AcceptLoop
AcceptLoop --> ClientThread
ClientThread --> DAEMON
%% Daemon internals
DAEMON --> State
DAEMON --> Bridge
DAEMON --> PeakBroadcast
DAEMON --> SignalWatcher
%% PW mainloop
DAEMON --> PWMainloop
Bridge --- PWMainloop
PWMainloop --> LinkWorker
PWMainloop --> NullChecker
PWMainloop --> Filter
%% Audio processing
Filter -- "RT callback" --> Equalizer
Filter --> Pipeline
Pipeline -- "peak meters" --> PeakBroadcast
%% External services
PWMainloop --> PW
LinkWorker --> PWLink
NullChecker --> PWLink
%% Shared
DAEMON --> Config
DAEMON --> Profiles
DAEMON --> StateTypes
DAEMON --> Parser
TUI --> Config
TUI --> Profiles
TUI --> StateTypes
TUI --> Parser
classDef cli fill:#6b8e23,stroke:#556b2f,color:#ffffff
classDef tui fill:#1168bd,stroke:#0b4884,color:#ffffff
classDef daemon fill:#2694ab,stroke:#1a6d7d,color:#ffffff
classDef pw fill:#7b4f9e,stroke:#5a3670,color:#ffffff
classDef shared fill:#cd853f,stroke:#8b6914,color:#ffffff
classDef external fill:#999999,stroke:#666666,color:#ffffff
classDef thread fill:#3d7e9a,stroke:#2a5f73,color:#ffffff
class Main,CLI cli
class TUI,Event,App,Client,Normal,Insert,Command,Visual,Devices,EQTable,Graph,Status tui
class DAEMON,State,Protocol,Bridge,PeakBroadcast,SignalWatcher,AcceptLoop,ClientThread daemon
class PWMainloop,LinkWorker,NullChecker,Filter,Equalizer,Pipeline pw
class Config,Profiles,StateTypes,Parser shared
class PW,PWLink external
class Bridge,PeakBroadcast,SignalWatcher,ClientThread,LinkWorker,NullChecker,AcceptLoop thread
```
## Process Model
eqtui uses a **two-process architecture** with a background daemon and an optional TUI client:
### Daemon Process
- Always running — survives TUI restarts
- Owns the PipeWire audio pipeline
- Listens on a Unix socket (`$XDG_RUNTIME_DIR/eqtui.sock`)
- Persists EQ state to `$XDG_DATA_HOME/eqtui/state.toml`
- Thread-per-client for up to 10 concurrent connections
### TUI Process
- Attaches to the daemon via Unix socket
- Auto-launches the daemon if not running
- Ratatui-based interface with keyboard-driven navigation
- Receives push events (peak meters, state changes) from daemon
## Audio Pipeline
The critical audio path runs on the **PipeWire RT thread** with zero lock acquisitions:
1. `pw/filter.rs::process_cb` — C callback called by PipeWire per audio buffer
2. `effects/equalizer.rs::AudioEq::process` — Biquad filter chain
3. `pipeline.rs::Pipeline` — Atomic peak meter, preamp, bypass storage
EQ parameter changes (`set_bands`, `set_preamp`) are sent through a `PwCommand` channel to the PW mainloop thread, keeping the RT path lock-free.
## Threading Model (Daemon)
| Accept Loop (main) | Listens on Unix socket, spawns client handlers | Process lifetime |
| `pw` (mainloop) | PipeWire event loop + audio processing | Process lifetime |
| `pw-bridge` | Translates PW events → `DaemonState` | Process lifetime |
| `peak-broadcast` | Pushes peak meters to clients every 66ms | Process lifetime |
| `signal-watcher` | Monitors SIGTERM/SIGINT, unblocks accept loop | Process lifetime |
| `null-sink-checker` | Polls `pw-link -I` every 500ms for source status | Process lifetime |
| `pw-link-worker` | Executes `pw-link` connect/disconnect commands | Process lifetime |
| `client-N` | Handles one client connection's request/response | Per-connection |
## IPC Protocol
JSON-line protocol over Unix domain sockets:
- **Requests** (TUI → Daemon): `{ "cmd": "SetBands", "bands": [...] }`
- **Responses** (Daemon → TUI): `{ "ok": true, "status": {...} }`
- **Push Events** (Daemon → TUI): `{ "event": "PeakUpdate", "l": -12.3, "r": -8.1 }`
Peer credential verification (`SO_PEERCRED`) ensures only the same UID can connect.
## Key Files
| `src/main.rs` | CLI entry point, mode dispatch |
| `src/daemon.rs` | Daemon core: state, IPC, thread orchestration |
| `src/app.rs` | TUI application state machine |
| `src/client.rs` | Daemon IPC client with auto-launch |
| `src/pw/run.rs` | PipeWire mainloop thread, null sink creation |
| `src/pw/filter.rs` | PipeWire filter (RT callback + state callbacks) |
| `src/pw/links.rs` | `pw-link` subprocess wrappers |
| `src/effects/equalizer.rs` | Biquad EQ filter chain (AudioEq) |
| `src/pipeline.rs` | Atomic pipeline: preamp, bypass, peak meters |
| `src/protocol.rs` | JSON-line IPC types (Request, Response, PushEvent) |
| `src/profiles.rs` | EQ preset profile management |
| `src/autoeq/parser.rs` | AutoEQ / Peace PEQ file parser |