0013 — Latency telemetry is measured in the audio engine

Status: Accepted (built — 2026-07-29 note below) Date: 2026-06-29

Context

A live operator needs to know the exact latency each step and each plugin adds, in real time — to manage delay compensation, to align the PA against the stage, to spot a plugin that just blew the monitor-path budget. Latency could be estimated in the UI from plugin nameplate figures, but that is a guess: the real numbers depend on the live graph — the quantum, the sample rate, each plugin's reported latency port, and how many async links a path crosses.

The audio engine is the only component that owns the graph and can read those numbers directly.

Decision

Measure latency in the audio engine, at its source, and stream it out — do not estimate it elsewhere.

  • Per plugin — read the LV2 lv2:latency-designated output port where the plugin reports it (mod-host exposes the port value). Where a plugin does not report, mark it 0/unknown — do not guess.
  • Per step/node — PipeWire node processing latency, plus the base buffering latency quantum / sampleRate per link.
  • Per path — sum input → inserts → bus → output in milliseconds, with the per-element breakdown retained.
  • Health — xrun/underrun counter, current quantum and sample rate.

The engine exposes getLatencyReport(); the server polls it a few times a second and broadcasts telemetry.*; the UI shows a per-channel total badge that expands to the per-plugin/per-step breakdown, with the mains/monitor path shown prominently and a live xrun counter.

Consequences

  • The numbers are real, not nameplate estimates, because they come from the live graph.
  • It is the natural place for delay compensation to live too: the engine that measures the per-path latency is the one that inserts delay nodes where parallel paths reconverge.
  • Telemetry is a low-rate, separate stream (a few Hz), kept off the per-control message path — meters and latency do not bloat every fader frame.
  • It depends on plugins honestly designating their latency port; unknown is surfaced as unknown, which is the honest failure mode.
  • Verifying real PipeWire/plugin latency values needs the actual mixing host; this is a documented test gate, not something CI can assert without hardware.

Note — 2026-07-29: built

getLatencyReport() is a real contract (the LatencyReportSource interface in packages/core/src/telemetry.ts) with a real implementation in the engine (SoftwareMixer.getLatencyReport in packages/audio-engine/src/software-mixer.ts), and the periodic report is polled by TelemetryBroadcaster (packages/server/src/telemetry.ts) and served at GET /telemetry/latency?watch=1. Measurement stays in the engine, as decided.