feat: real-time dB level meters per node

Backend:
- One pw_stream (INPUT, F32) per audio node; for sinks use
  stream.capture.sink=true to read the monitor port
- RT process callback computes instantaneous linear peak with no
  allocations; stored in std::atomic<float>
- Meter streams are filtered from the registry so they never appear
  in the graph or trigger recursive meter creation
- Meter streams are created on first node-ready event, destroyed on
  node removal, and cleaned up on engine close
- GET /api/peaks → {node_id: linear_peak} (polled by frontend)

Frontend:
- peaks store polled at 100 ms via setInterval; starts/stops with
  initGraph/destroyGraph
- Each node card grows 8 px and shows a 3 px meter bar at the bottom
  (green below -12 dB, yellow -12 to -3 dB, red above -3 dB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-04-02 19:48:02 +02:00
parent b6d6ad970a
commit cbc5083490
5 changed files with 232 additions and 4 deletions

View File

@@ -302,6 +302,21 @@ void WebServer::setupRoutes() {
res.set_header("Access-Control-Allow-Origin", "*");
});
// Level meters: GET /api/peaks → {"<node_id>": <linear_peak_0_1>, ...}
m_http.Get("/api/peaks", [this](const httplib::Request &, httplib::Response &res) {
auto peaks = m_engine.getPeaks();
std::string json = "{";
bool first = true;
for (auto &[id, peak] : peaks) {
if (!first) json += ",";
json += "\"" + std::to_string(id) + "\":" + std::to_string(peak);
first = false;
}
json += "}";
res.set_content(json, "application/json");
res.set_header("Access-Control-Allow-Origin", "*");
});
// REST API: POST /api/connect
m_http.Post("/api/connect", [this](const httplib::Request &req, httplib::Response &res) {
uint32_t out_id = 0, in_id = 0;