SPL Meter: measure the room, not just watch it #10

Merged
michael merged 7 commits from claude/spl-meter-module into main 2026-09-17 16:18:17 -04:00
2 changed files with 16 additions and 5 deletions
Showing only changes of commit 87929bfd0b - Show all commits
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "spl-meter",
"name": "SPL Meter",
"version": "0.3.0",
"version": "0.3.1",
"proddash": ">=1.5.0",
"description": "Calibrated sound level, Leq and RTA from an audio input on this machine",
"permissions": [
+15 -4
View File
@@ -132,6 +132,7 @@ module.exports = {
moduleDir,
log,
onUpdate(update) {
if (!state) return; // a frame in flight when the module unmounted
if (update.error) {
if (update.error !== state.error) log(update.error);
state.error = update.error;
@@ -146,9 +147,10 @@ module.exports = {
},
});
const mineStreams = state.streams;
state.pinger = setInterval(() => {
for (const res of state.streams) {
try { res.write(': ping\n\n'); } catch { state.streams.delete(res); }
for (const res of mineStreams) {
try { res.write(': ping\n\n'); } catch { mineStreams.delete(res); }
}
}, PING_MS);
@@ -260,8 +262,14 @@ module.exports = {
try {
res.write(`event: state\ndata: ${JSON.stringify(snapshot())}\n\n`);
} catch { return; }
state.streams.add(res);
req.on('close', () => state.streams.delete(res));
// Hold this mount's set, not `state`. teardown() nulls `state` and
// then ends every stream, which fires these close handlers — a
// handler that reached through `state` threw on null and took the
// whole server down with it, on any re-init with a browser
// connected. Deleting from a set nobody reads any more is harmless.
const streams = state.streams;
streams.add(res);
req.on('close', () => streams.delete(res));
},
/** Device list for the admin page. Runs the capture tool's --list,
@@ -311,6 +319,7 @@ module.exports = {
const samples = [];
const started = Date.now();
const tick = setInterval(() => {
if (!state) { clearInterval(tick); return; } // module unmounted mid-measure
const ch = state.frame && state.frame.ch.find((c) => c.i === wanted);
if (ch && Number.isFinite(ch.dbfs) && ch.dbfs > -200) samples.push(ch.dbfs);
if (Date.now() - started < MEASURE_MS) return;
@@ -343,6 +352,7 @@ module.exports = {
/* ── helpers ────────────────────────────────────────────────────────── */
function snapshot() {
if (!state) return { error: 'not measuring', calibrated: false, channels: [], frame: null };
return {
error: state.error,
calibrated: state.calibrated,
@@ -356,6 +366,7 @@ function snapshot() {
}
function broadcast() {
if (!state) return; // a listener callback landing after teardown
const payload = snapshot();
const line = JSON.stringify(payload);
if (line === state.lastSent) return; // nothing moved since last frame