`headroom monitor` becomes a full-screen ratatui TUI by default;
the previous behaviour (line-delimited JSON, useful for scripts and
tests) is preserved behind --json.
5 — Monitor TUI
New `crates/headroom-cli/src/tui.rs` (~700 lines incl. tests).
Main thread does subscribe + initial status() + route_list() before
entering raw mode, so connect errors surface as clean stderr
messages instead of corrupting the terminal. A reader thread owns
the headroom_client::Client and forwards each subscription event
through a crossbeam channel; an input thread blocks on
event::read() and forwards keys (q / Esc / Ctrl-C) through a
second channel; the main thread `select!`s both plus a 10 Hz
ticker (so uptime + staleness display advance even when no
events are flowing). On quit the OS reaps the reader; a CLI tool
doesn't need a graceful UnixStream shutdown.
Layout: outer block carries the profile / version / uptime in the
top-right title and a footer with subscribed topics + an overflow /
error / disconnected banner when relevant. Inside: bus DSP gauges
(AGC target, compressor GR, limiter GR, true peak), a loudness
panel (momentary / short-term / integrated, greyed when stale),
and a streams table with route + Layer A reduction column.
Wire types caught up to the daemon
`headroom-ipc::RoutingEvent` gained `StreamRemoved`,
`LayerAAttached`, `LayerADetached` variants — these are events the
daemon already publishes (registry.rs §pw) but that
weren't typed in the proto. Without `StreamRemoved` the TUI would
accumulate departed streams forever; without the Layer A pair the
per-stream column couldn't track tap state.
New `LayerALevel` struct types the `meters/layer_a_level` payload
(node_id, app, volume_lin, reduction_db).
`headroom_core::agc::LOUDNESS_FLOOR_LUFS` is now `pub` — it's
published as-is in MeterTick.*_lufs fields when ebur128 has no
useful measurement yet, so clients need it to render "no
measurement" without hard-coding `-200.0`.
Toolchain notes
ratatui and crossterm pinned to =0.28.1. Newer ratatui pulls in
`instability` 0.3.12 + `darling` 0.23 which need rustc 1.88+; the
project pins 1.86 via rust-toolchain.toml. Lockfile also pins
`instability` to 0.3.7 and `darling` to 0.20.10 (older patches that
still build on 1.86).
Verified
185 tests passing (was 178: +5 for TUI event mapping +
fmt_uptime, +2 for stream_removed / layer_a_level handling).
Clippy clean at -D warnings --all-targets.
Live smoke: daemon emits routing/{stream_routed, stream_removed,
layer_a_attached, layer_a_detached} and meters/{tick, layer_a_level}
in shapes that round-trip cleanly through the new typed enums.
TUI binary survives raw-mode init + initial RPCs + subscription
against a live daemon.
Known unrelated daemon gap (to be fixed next): pre-existing streams
aren't actually re-linked when the daemon writes target.object —
WirePlumber updates metadata but doesn't tear the old link down or
create a new one into the processed sink. Bus DSP path therefore
sees silence even when status reports route=processed. Not Phase 5;
addressed separately.
94 lines
2.4 KiB
TOML
94 lines
2.4 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
members = [
|
|
"crates/headroom-dsp",
|
|
"crates/headroom-ipc",
|
|
"crates/headroom-client",
|
|
"crates/headroom-core",
|
|
"crates/headroom-cli",
|
|
]
|
|
|
|
[workspace.package]
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
rust-version = "1.86"
|
|
license = "GPL-3.0-or-later"
|
|
homepage = "https://github.com/amaanq/headroom"
|
|
repository = "https://github.com/amaanq/headroom"
|
|
authors = ["Headroom contributors"]
|
|
|
|
[workspace.dependencies]
|
|
# Internal crates
|
|
headroom-dsp = { path = "crates/headroom-dsp", version = "0.1.0" }
|
|
headroom-ipc = { path = "crates/headroom-ipc", version = "0.1.0" }
|
|
headroom-client = { path = "crates/headroom-client", version = "0.1.0" }
|
|
headroom-core = { path = "crates/headroom-core", version = "0.1.0" }
|
|
|
|
# Serde / JSON / TOML
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
serde_json = "1.0"
|
|
toml = "0.8"
|
|
|
|
# Errors / logging
|
|
thiserror = "2.0"
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
|
|
|
# CLI
|
|
clap = { version = "4.5", features = ["derive"] }
|
|
|
|
# TUI (monitor). Pinned to versions whose transitive deps still build
|
|
# on the project's pinned rustc 1.86 (newer ratatui pulls
|
|
# `instability` 0.3.12 + `darling` 0.23 which need 1.88+).
|
|
ratatui = "=0.28.1"
|
|
crossterm = "=0.28.1"
|
|
|
|
# Concurrency / control plane
|
|
crossbeam-channel = "0.5"
|
|
parking_lot = "0.12"
|
|
signal-hook = "0.3"
|
|
nix = { version = "0.27", features = ["signal"] }
|
|
|
|
# Realtime audio
|
|
rtrb = "0.3"
|
|
basedrop = "0.1"
|
|
assert_no_alloc = "1.1"
|
|
|
|
# DSP
|
|
ebur128 = "0.1"
|
|
fundsp = "0.20"
|
|
|
|
# PipeWire. `v0_3_44` exposes target.object key + related modern APIs.
|
|
pipewire = { version = "0.8", features = ["v0_3_44"] }
|
|
libspa = "0.8"
|
|
libspa-sys = "0.8"
|
|
|
|
# Safe byte<->POD casts for audio buffers.
|
|
bytemuck = "1.18"
|
|
|
|
# Profile hot-reload
|
|
notify = "6.1"
|
|
notify-debouncer-mini = "0.4"
|
|
|
|
# Benchmarking — dev-dep only.
|
|
criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] }
|
|
|
|
# Logging — journald optional
|
|
tracing-journald = "0.3"
|
|
|
|
[profile.release]
|
|
opt-level = 3
|
|
lto = "thin"
|
|
codegen-units = 1
|
|
debug = "line-tables-only"
|
|
overflow-checks = false
|
|
panic = "abort"
|
|
|
|
[profile.dev]
|
|
opt-level = 1 # audio code is unusable at -O0
|
|
debug = true
|
|
overflow-checks = true
|
|
|
|
[profile.bench]
|
|
inherits = "release"
|
|
debug = "full"
|