eqtui 0.1.1-alpha.4

Terminal-native(TUI) audio effects processor for PipeWire
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# ROADMAP — eqtui

> A terminal-native audio effects processor for PipeWire, inspired by EasyEffects and powered by AutoEQ.

---

## 1. Vision

**EasyEffects** is a comprehensive Linux audio effects suite. However, it is integrated with the Qt6/QML and GTK desktop ecosystems. `eqtui` aims to provide a lightweight, keyboard-driven alternative for terminal environments and tiled window managers.

**eqtui** is that toolbox. Same PipeWire pipeline architecture, same effect chain, but rendered entirely in the terminal with `ratatui`. No Qt, no GTK, no DE dependency. Add AutoEQ integration as a first-class feature (import headphone correction profiles with one keypress).

### Principles

- **Keyboard-first** — every action reachable without a mouse
- **Lightweight** — binary under 10MB, sub-100MB RAM at idle
- **PipeWire-native** — no PulseAudio compatibility layer, no JACK bridge
- **AutoEQ-native** — import, preview, and apply correction profiles directly
- **Composable** — effect chains are just TOML/JSON files

---

## 2. Architecture Overview

```
┌─────────────────────────────────────────────┐
│                  TUI Layer                    │
│  ratatui + crossterm                         │
│  ┌─────────┐ ┌──────────┐ ┌──────────────┐  │
│  │ Node    │ │ Effect   │ │ Spectrum /   │  │
│  │ Picker  │ │ Chain    │ │ Level Meters │  │
│  └─────────┘ └──────────┘ └──────────────┘  │
├─────────────────────────────────────────────┤
│              Application Core                │
│  ┌──────────────┐ ┌──────────────────────┐  │
│  │ Preset Store  │ │  AutoEQ Importer     │  │
│  │ (TOML/JSON)  │ │  (parse PEQ output)  │  │
│  └──────────────┘ └──────────────────────┘  │
│  ┌──────────────────────────────────────┐   │
│  │         Effect Engine                 │   │
│  │  EQ │ Compressor │ Gate │ Reverb ...  │   │
│  │  (trait-based plugin system)          │   │
│  └──────────────────────────────────────┘   │
├─────────────────────────────────────────────┤
│           PipeWire Integration               │
│  pipewire crate (v0.9.x)                    │
│  ┌──────────┐ ┌──────────┐ ┌────────────┐  │
│  │ pw_main  │ │ pw_filter│ │ pw_context │  │
│  │ _loop    │ │ (per fx) │ │ (nodes)    │  │
│  └──────────┘ └──────────┘ └────────────┘  │
└─────────────────────────────────────────────┘
```

### Crate Stack

| Crate | Version | Phase | Purpose |
|-------|---------|-------|---------|
| `pipewire` | 0.9.x | 0 | PipeWire client bindings |
| `ratatui` | 0.30 | 0 | Terminal UI framework |
| `crossterm` | 0.29 | 0 | Terminal input/raw mode (with `event-stream` feature) |
| `color-eyre` | 0.6 | 0 | Error reporting |
| `dasp` | 0.11 | 1 | DSP primitives (biquad filters, envelopes) |
| `serde` + `serde_json` | 1.x | 1 | Config deserialization, preset serialization |
| `toml` | 0.8 | 1 | Config/preset file format |
| `dirs` | 6 | 1 | XDG config directory (`~/.config/eqtui/`) |
| `tui-input` | 0.15 | 1 | Text input widget for value editing in Insert mode |
| `csv` | 1.3 | 3 | Parse frequency response CSVs (Path B: auto-fit) |
| `rustfft` | 6.x | 4 | FFT for spectrum analyzer |
| `clap` | 4.x | 4 | CLI argument parsing |

**Linting:** Enable `clippy::pedantic` from Phase 1 (pattern: bluetui). Catch issues early.

### Plugin Trait (analogous to EasyEffects `PluginBase`)

```rust
trait EffectPlugin {
    fn name(&self) -> &str;
    fn setup(&mut self, rate: u32, channels: u16);
    fn process(&mut self, input: &[f32], output: &mut [f32]);
    fn bypass(&self) -> bool;
    fn set_bypass(&mut self, bypass: bool);
    fn latency_samples(&self) -> u32;
    fn reset(&mut self);
}
```

### Key Architectural Difference from EasyEffects

EasyEffects uses a **flat map of individually-wrapped PipeWire filters**, each getting connected as a separate PW node. For eqtui, we have two options:

1. **Mirror EasyEffects** — one `pw_filter` per effect. Pros: modular, matches upstream architecture. Cons: more PW overhead, sync complexity.

2. **Single filter, internal chain** — one `pw_filter`, chain effects internally. Pros: simpler, lower latency. Cons: harder to reorder effects at runtime.

**Decision: Start with Option 2** (single filter, internal chain). This approach is simpler to implement and debug. Transitioning to Option 1 can occur as required.

---

## 3. Phased Roadmap

### Phase 0 — Hello PipeWire [x]

**Goal:** Prove the stack works. Connect to PipeWire, list nodes, display in TUI.

**Tasks:**

- [x] Add `pipewire` and `libspa` crate dependencies
- [x] Initialize PW main loop in a background thread
- [x] Enumerate audio sink/source nodes
- [x] Build TUI screen with:
  - Node list (main panel)
  - Detail panel (selected node info)
  - Status bar (PW connection state, node count)
- [x] Handle terminal resize and graceful shutdown

**Deliverable:** Binary that opens a TUI, lists your speakers and microphone, exits cleanly on `q`.

**Files:** `src/main.rs`, `src/pw.rs`, `src/state.rs`, `src/tui.rs`

**Notes:**

- Architecture: PW thread communicates with TUI thread via `pipewire::channel` (TUI→PW) + `std::sync::mpsc` (PW→TUI)
- Uses `MainLoopRc`/`ContextRc`/`CoreRc` (reference-counted) for closure-friendly ownership
- Node hotplug (add/remove) support is scaffolded in `PwEvent` enum but not yet wired

---

### Phase 1 — Your First Effect (Equalizer) ✅

**Goal:** Create a working audio pipeline with one effect. The equalizer is the killer feature — it directly enables AutoEQ import.

**New dependencies:** `dasp`, `serde`, `serde_json`, `toml`, `dirs`, `tui-input`

**Status:** Complete. 20 files, 0 warnings, 20 passing tests.

**Architecture (adopted):**

- **Event-driven main loop**`EventHandler` merges crossterm events + tick timer into single `Event` enum
- **`FocusedBlock` dispatching** — Devices | Pipeline | CommandBar, routes keys per-panel
- **Panic hook** — restores terminal on crash
- **Centered layout** — max 140 columns with margin, focused panel gets thick green border (bluetui pattern)
- **Table-based panels** — both Devices and EQ use `Table` + `TableState` for uniform look
- **Column navigation**`h/l` or `←/→` to select Freq/Gain/Q/Type column
- **Text-based value editing** — Insert mode uses `tui-input` crate; type exact values, Enter to commit and clamp, Esc to cancel
- **Context-sensitive status bar** — per-mode keybinding hints, command input display
- **FFI bindings**`pw_filter` C API (~110 lines unsafe), `pw_filter_ffi.rs`
- **SPA format negotiation** — F32LE, 48000Hz, 2ch via PodSerializer, passed to `pw_filter_connect()`
- **State logging** — filter state transitions printed to stderr (UNCONNECTED → CONNECTING → STREAMING)

**File structure:**

```
src/
├── lib.rs             — module declarations, AppResult<T>
├── main.rs            — event loop, Pipeline creation, TUI init
├── app.rs             — App state (focused_block, mode, eq_bands, cell_input, pipeline)
├── config.rs          — TOML config (~/.config/eqtui/config.toml)
├── event.rs           — Event enum + EventHandler (crossterm + tick thread)
├── pipeline.rs        — Pipeline { eq, bypass(AtomicBool) }
├── pw.rs              — PW thread + pw_filter integration + SPA format negotiation
├── pw_filter_ffi.rs   — FFI bindings for pw_filter C API
├── state.rs           — NodeInfo, EqBand, FilterType, PwEvent/PwCommand
├── effects/
│   ├── mod.rs         — EffectPlugin trait
│   └── equalizer.rs   — RBJ biquad EQ (Peak, LowShelf, HighShelf), 7 tests
├── handler/
│   ├── mod.rs         — dispatcher: Mode → sub-handler
│   ├── normal.rs      — j/k row, h/l col, a add, dd delete, g g, i insert, v visual, : command, b bypass, r/R reset, 2 tests
│   ├── insert.rs      — tui-input text entry, Enter commit+clamp, Esc cancel, 6 tests
│   ├── visual.rs      — j/k extend selection, d delete batch
│   └── command.rs     — :q, :flat, :add, :bypass
└── tui/
    ├── mod.rs         — Tui struct + centered render routing (Max 140)
    ├── devices.rs     — device Table with focused/unfocused borders
    ├── eq_table.rs    — band Table with column highlighting + cell_input display
    └── status.rs      — centered per-mode keybinding hints
```

**Tasks (all complete):**

- [x] Write FFI bindings for pw_filter
- [x] EventHandler pattern + Event enum
- [x] Tui struct with panic hook
- [x] Config system (TOML)
- [x] Filter integration: pw_filter with stereo ports, SPA format negotiation, RT_PROCESS
- [x] EffectPlugin trait + RBJ biquad EQ (3 filter types)
- [x] Pipeline chain with AtomicBool bypass
- [x] EQ band Table with column selection + focused/unfocused border styling
- [x] Centered layout (Max 140) with margin
- [x] Per-mode handlers (Normal, Insert with tui-input, Visual, Command)
- [x] Status bar with per-mode context hints
- [x] FocusedBlock toggle (Tab cycles Devices → Pipeline → back)
- [x] Pipeline wired to TUI: band edits sync to biquad coefficients in real time
- [x] 20 unit tests (7 EQ + 3 pipeline + 2 normal + 6 insert + 1 app + 1 init)
- [x] EQ curve graph deferred to Phase 4

---

### Phase 2 — Virtual Sink + Daemon + Simple PEQ Import

**Goal:** Make eqtui appear as a selectable output device in system settings, split into daemon/client architecture so EQ persists when TUI is closed, then add simple PEQ file import.

**New dependencies:** none

**Phase 2A — Virtual Null Sink:** ✅

Currently the pw_filter processes audio but is invisible to users — they can't select it as an output device. We solve this by creating a **null audio sink** that appears in GNOME/KDE sound settings as "eqtui Equalizer".

```
[Apps] → PipeWire → [eqtui Null Sink] → [our pw_filter] → [Real Speakers]
                 Selectable in system settings
```

- [x] Load PipeWire `module-null-sink` at startup to create virtual sink
- [x] Name it "eqtui Equalizer" (appears in volume control)
- [x] Set properties: `media.class=Audio/Sink`, `node.description=eqtui Equalizer`
- [x] Connect pw_filter to the null sink (capture from monitor, output to default sink)
- [x] Auto-remove null sink on shutdown

**How it works for users:**

1. Open GNOME/KDE sound settings
2. Select "eqtui Equalizer" as output
3. All system audio routes through eqtui's EQ

**Phase 2B — Daemon Architecture:**

Currently eqtui runs as a single process — closing the TUI destroys the PipeWire filter and EQ stops. Phase 2B splits the binary into daemon and client modes using a Unix socket for IPC.

```
┌─────────────────────────────────────────────────────────┐
│  eqtui daemon  (headless, owns PipeWire + EQ engine)    │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │ Unix socket  │  │ PW Thread    │  │ EQ Pipeline  │   │
│  │ listener     │  │ (null sink + │  │ (biquad DSP) │   │
│  │              │  │  pw_filter)  │  │              │   │
│  └──────┬───────┘  └──────────────┘  └──────────────┘   │
└─────────┼───────────────────────────────────────────────┘
          │ $XDG_RUNTIME_DIR/eqtui.sock
          │ JSON-line protocol
     ┌────┴────┐
     │         │
┌────┴───┐ ┌───┴──────┐
│eqtui   │ │eqtui load│
│attach  │ │peq.txt   │
│(TUI)   │ │(CLI)     │
└────────┘ └──────────┘
```

**Design decisions:**

- **Same binary, different mode**`eqtui daemon` starts the background process, `eqtui attach` starts the TUI client. No separate daemon binary needed.
- **Auto-launch on attach** — if no daemon is running, `eqtui attach` spawns one automatically (fork + exec).
- **Unix socket at `$XDG_RUNTIME_DIR/eqtui.sock`** — standard location, auto-cleaned on logout.
- **JSON-line protocol** — each message is a JSON object on one line, easy to debug with `nc -U`:
  ```json
  {"cmd":"set_bands","bands":[{"freq":1000,"gain":6.0,"q":1.0,"type":"Peak"}]}
  {"cmd":"get_status"}
  {"cmd":"set_preamp","gain":-6.0}
  {"cmd":"set_bypass","bypass":true}
  ```
- **Stateless protocol** — daemon is the source of truth. Clients query state, send commands.
- **Concurrent clients** — multiple TUI/CLI clients can connect simultaneously (last write wins for conflicting changes).
- **No async runtime needed**`std::os::unix::net::UnixListener` with one thread per client is sufficient given low concurrency (1-2 clients typical).

**Tasks:**

- [ ] Add subcommand dispatch to `main.rs` (`daemon` | `attach` | `load` | `status` | `stop`)
- [ ] Extract PipeWire thread + Pipeline into `Daemon` struct that runs without TUI
- [ ] Implement Unix socket listener in daemon (`std::os::unix::net::UnixListener`)
- [ ] Define JSON protocol messages (`src/protocol.rs`): `set_bands`, `set_preamp`, `set_bypass`, `get_status`, `connect_device`, etc.
- [ ] Implement client-side Unix socket connection for TUI (`eqtui attach`)
- [ ] Auto-launch daemon from TUI if not running (fork + exec `$0 daemon`)
- [ ] CLI command: `eqtui status` — query daemon for current EQ state
- [ ] CLI command: `eqtui stop` — tell daemon to shut down gracefully
- [ ] Graceful shutdown: daemon cleans up null sink + pw_filter on SIGTERM/SIGINT
- [ ] Lock file at `$XDG_RUNTIME_DIR/eqtui.lock` to prevent duplicate daemons

**Deliverable:** Start `eqtui daemon`, then `eqtui attach` → TUI opens, EQ works, close TUI → audio stays EQ'd. Re-attach later → see same state.

**Files:** `src/daemon.rs`, `src/protocol.rs`, `src/cli.rs`

---

**Phase 2C — Simple PEQ Import:**

User provides a pre-computed PEQ file (from AutoEQ CLI, Squiglink export, etc.). eqtui parses it and populates the EQ band list. No subprocess calls, no headphone database, no fuzzy search — the user is responsible for obtaining the file.

**Supported format** (standard AutoEQ PEQ output):
```
Preamp: -6.0 dB
Filter 1: ON PK Fc 32 Hz Gain 2.5 dB Q 0.71
Filter 2: ON LSC Fc 105 Hz Gain 5.5 dB Q 0.71
Filter 3: ON HSC Fc 10000 Hz Gain -2.0 dB Q 0.70
```

**Tasks:**

- [ ] PEQ file parser (`src/autoeq/parser.rs`) — regex/string-based, no external deps
- [ ] Map parsed filters to `EqBand` structs (PK→Peak, LSC→LowShelf, HSC→HighShelf)
- [ ] `:load <path>` command in TUI (sends PEQ to daemon via Unix socket)
- [ ] `eqtui load <path>` CLI command (connects to daemon, pushes PEQ)
- [ ] Handle malformed files gracefully (error notification to user)
- [ ] Export: `:save <path>` writes current bands as PEQ file

**Deliverable:** Download a PEQ file from Squiglink, run `eqtui load hd600.txt`, EQ applied immediately.

**Files:** `src/autoeq/mod.rs`, `src/autoeq/parser.rs`

**Deferred (Phase 3):**
- AutoEQ browser TUI (`Ctrl+a`) with fuzzy-search by headphone name
- AutoEQ subprocess integration (auto-fit from FR curve)
- CSV loader for frequency response curves
- Headphone/IEM model database

---

### Phase 3 — Preset System + More Effects

**Goal:** Complete the effect roster and make configurations persistent.

**Tasks:**

- [ ] Preset system (TOML files in `~/.config/eqtui/presets/`)
  - Per-effect presets
  - Full pipeline presets (chain of multiple effects)
  - Import/export to EasyEffects JSON format for compatibility
- [ ] **AutoEQ Browser**`Ctrl+a` opens fuzzy-search picker for headphone/IEM targets
  - Scan `../AutoEq/results/` directory for available targets
  - Fuzzy-search by model name
  - Preview EQ curve before applying
  - AutoEQ subprocess integration for auto-fit from FR curve
  - Headphone/IEM model database (deferred from Phase 2C)
- [ ] **Notification system** (bluetui pattern) — transient status messages with TTL-based expiry
  - "Preset saved", "AutoEQ imported", "Error: no PW connection", etc.
- [ ] **Spinner widget** (bluetui pattern) — visual feedback during async operations
  - AutoEQ subprocess, preset loading, FFT computation
- [ ] **Compressor** — threshold, ratio, attack, release, knee, makeup gain
- [ ] **Gate / Expander** — threshold, range, attack, release
- [ ] **Bass Enhancer** — harmonic synthesis
- [ ] **Stereo Tools** — balance, width, phase invert
- [ ] **Delay** — time, feedback, mix
- [ ] **Reverb** — room size, damping, width, mix (basic Schroeder reverberator)
- [ ] Reorderable effect chain in TUI (`Ctrl+Up`/`Ctrl+Down` to move effects)
- [ ] Per-effect bypass toggle and solo

**Deliverable:** Multiple effects in a reorderable chain, saved/loaded as presets, with status notifications.

**Files:** `src/notification.rs`, `src/spinner.rs`, `src/presets.rs`, per-effect files under `src/effects/`

---

### Phase 4 — Visualization, Polish & Power-User Features

**Goal:** Make it look and feel like a professional audio tool. Text-mode graphs, spectrum, CLI control.

**New dependencies:** `rustfft` (FFT), `clap` (CLI)

**Visualization suite:**

- [ ] **EQ curve graph** — real-time text graph of the EQ frequency response

  ```
  +10 ┤         ▄▄▄
   +5 ┤  ▄▄▄▄▄▄▄ ▀▀▀▀▄▄▄▄         ▄▄▄▄▄▄
    0 ┤▀▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
   -5 ┤                   ▀▀▀▀
  -10 ┤
       ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──
       32  64 125 250 500 1k  2k  4k  8k 16k
  ```

  - Unicode block characters (▀▄█▌) for smooth-looking curve
  - Updates live as band parameters change
  - Dual overlay: pre-EQ vs post-EQ (when spectrum added)

- [ ] **Spectrum analyzer** — real-time FFT of audio signal
  - Pre- and post-EQ spectrum (dual color overlay)
  - Configurable smoothing, decay, bar/line/block mode
  - Frequency axis labels (log scale)

- [ ] **Level meters** — per-effect input/output

  ```
  Output L ████████░░ -12 dB
  Output R ████████░░ -14 dB
  ```

  - Peak hold with configurable decay
  - Color gradient: green → yellow → red

- [ ] **Graphic EQ mode** — switch between parametric and graphic EQ
  - 15/31 fixed-frequency bands mapped from imported CSV curve
  - Toggle: `:graphic-eq` / `:parametric-eq`

**Power-user features:**

- [ ] **Limiter** — brickwall peak protection (sits last in chain)
- [ ] **Loudness normalization** — EBU R128 / ITU-R BS.1770
- [ ] **Noise reduction** — basic spectral gate (simpler than RNNoise)
- [ ] **CLI mode** — apply preset from command line (`eqtui apply hd600 --pipeline output`)
- [ ] **Daemon mode** — run headless, controlled via CLI or Unix socket
- [ ] Config file — `~/.config/eqtui/config.toml`
  - Theme (colors, graph style, block vs line characters)
  - Default pipeline, default AutoEQ target directory
  - Keybinding overrides

**Files:** `src/visuals/`, `src/visuals/curve.rs`, `src/visuals/spectrum.rs`, `src/visuals/meters.rs`, `src/cli.rs`, `src/daemon.rs`

---

### Phase 5 — Packaging & Distribution

**Goal:** Get it into users' hands.

**Tasks:**

- [ ] CI/CD — GitHub Actions for build + test + lint
- [ ] Binary releases — `.tar.gz` for Linux x86_64
- [ ] AUR package — `eqtui-bin` and `eqtui-git`
- [ ] Nix flake
- [ ] Cargo crate publish
- [ ] Man page
- [ ] README with screenshots and quickstart
- [ ] Shell completions (bash, zsh, fish)

---

## 4. Technical Decisions

### Why Rust (not C++, not Python)

| Factor | C++ (EasyEffects) | Python | Rust |
|--------|-------------------|--------|------|
| DSP performance | Excellent | Poor (GIL, interpreted) | Excellent |
| PW bindings | Native headers | ctypes/ffi | First-class crate |
| TUI ecosystem | ncurses (painful) | Textual/rich | ratatui (best-in-class) |
| Binary distribution | Shared lib hell | Needs Python + deps | Single static binary |
| Safety | Manual memory mgmt | GC'd but slow | Borrow checker |

Rust gives us C++-level performance with Python-level ergonomics for the high-level parts, and a single static binary for distribution.

### Why ratatui (not tui-rs, not Textual)

`ratatui` is the community-maintained fork of `tui-rs` and the only actively developed Rust TUI framework. It has:

- Immediate-mode rendering (no retained widget tree)
- Flexbox-like layout system
- Styling via `Span`/`Line` combinators
- Active ecosystem (crossterm, termion, termwiz backends)

### What Is Not Being Ported (and Why)

| EasyEffects Feature | Reason to Skip |
|---------------------|----------------|
| LV2 plugin host | High complexity. Native DSP implementation is more portable. |
| RNNoise / DeepFilterNet | ML inference engines are outside the current scope. |
| Convolution reverb | Requires impulse response management and partitioned convolution. |
| QML/Kirigami UI | Primary goal is to avoid graphical toolkit dependencies. |
| DBus API | Headless control is provided via CLI and Unix sockets. |
| Flatpak sandboxing | Standard package managers (AUR/Nix/Cargo) are prioritized. |

---

## 5. Risk Log

| Risk | Impact | Mitigation |
|------|--------|------------|
| `pipewire` crate is incomplete | Connection failure | Early testing (Phase 0). Fallback: raw FFI bindings for required subsets. |
| DSP implementation issues | Audio quality | Implementation of standard algorithms (biquad EQ). Use of validated DSP primitives. Alignment with industry-standard DSP parameters. |
| Terminal rendering limitations | Visual quality | Use of block characters (▄▀█) for spectrum and curves within terminal constraints. |
| PipeWire callback latency | Audio artifacts | Maintenance of lock-free `process()` callbacks and use of ring buffers. Profiling with `perf`. |
| Feature creep | Project delay | Adherence to phased development. Prioritization of core functionality. |

---

## 6. Current State

- **Rust toolchain:** 1.95.0 (2026-04-14)
- **Phase 0:** ✅ Complete — PipeWire connection, node enumeration, TUI display
- **Phase 1:** ✅ Complete — EQ engine + vim-mode TUI, 20 tests, centered layout, column nav, tui-input, SPA format negotiation
- **Phase 2A:** ✅ Complete — Virtual null-audio-sink with media.class=Audio/Sink for wiremix compatibility, proxy bound listener, filter wired to null sink monitor ports, correct teardown order
- **Crates in use:** ratatui 0.30, crossterm 0.29, pipewire 0.10, pipewire-sys 0.10, libspa-sys 0.10, color-eyre 0.6, serde 1.x, toml 1.1, dirs 6, tui-input 0.15
- **Next action:** Phase 2B — Daemon architecture (Unix socket IPC, daemon/client split)

---

*Last updated: 2026-05-21*