A sound level meter that lives in ProdDash rather than beside it. Takes an audio input the way the Timers module's LTC reader does, and offers four tiles: the level as a big number, per-channel bars with peak hold, a third-octave RTA, and a scrolling spectrogram. The measurement happens in spl-capture.swift, not in Node and not in the browser. LTC gets away with piping raw samples upstream because the decode is cheap and single-channel; sound level is filtering and FFTs over every channel at the device's rate, so the child does the work and only the answers cross the pipe. That costs nothing in dependencies — Accelerate ships with macOS, and the whole thing builds in two seconds with a plain swiftc, exactly like ltc-capture. The weighting filters are the part worth checking rather than trusting, so `spl-capture --selftest` prints them against the IEC 61672 table. A- and C-weighting land within 0.15 dB from 31.5 Hz to 4 kHz and exactly 0 dB at 1 kHz. The top octave is where the bilinear transform runs out of room: at 48 kHz the 16 kHz point sits about 3 dB low even with the pole pair prewarped, and at 96 kHz that falls to 0.6 dB. It is a rounding error on a broadband number and a real limit all the same, which is why the selftest prints both rates instead of a pass mark. Cross-checked against REW on the same microphone: after removing the fixed calibration offset the two agree to a standard deviation of 0.5 dB on live room noise. Uncalibrated is a state the module says out loud — every tile reads dBFS and labels it, so nobody mistakes a relative number for sound pressure. The /measure route averages the calibrator tone in the power domain and hands back the offset to paste into settings; a module cannot write its own admin config, so that last step stays manual on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
3.6 KiB
JSON
108 lines
3.6 KiB
JSON
{
|
|
"id": "spl-meter",
|
|
"name": "SPL Meter",
|
|
"version": "0.1.0",
|
|
"proddash": ">=1.5.0",
|
|
"description": "Calibrated sound level, Leq and RTA from an audio input on this machine",
|
|
"permissions": [
|
|
{ "kind": "microphone", "reason": "Measuring sound level from an audio input on this machine" }
|
|
],
|
|
"client": "client.js",
|
|
"server": "server.js",
|
|
"style": "style.css",
|
|
"minSize": { "w": 2, "h": 1 },
|
|
"defaultSize": { "w": 3, "h": 2 },
|
|
"configGroups": {
|
|
"Input": { "toggle": "enabled" },
|
|
"Measurement": { "columns": 2 },
|
|
"Calibration": {
|
|
"collapsed": "whenSet",
|
|
"help": "A sound level calibrator fits over the microphone capsule and emits a known level, almost always 94 dB at 1 kHz. Put it on, press Measure beside the channel, and copy the offset it reports into the box. Without this the meter still works, but it reads in dB relative to full scale and every tile says so."
|
|
}
|
|
},
|
|
"configSchema": {
|
|
"enabled": {
|
|
"type": "switch",
|
|
"label": "Sound level measurement",
|
|
"group": "Input",
|
|
"default": false
|
|
},
|
|
"device": {
|
|
"type": "select",
|
|
"label": "Audio device",
|
|
"group": "Input",
|
|
"showWhen": "enabled",
|
|
"default": "",
|
|
"optionsRoute": "/devices"
|
|
},
|
|
"channels": {
|
|
"type": "string",
|
|
"label": "Channels on that device (1-based, comma separated)",
|
|
"group": "Input",
|
|
"showWhen": "enabled",
|
|
"default": "1",
|
|
"help": "One meter per channel — \"1\" for a single measurement mic, \"1,2\" for a stereo pair."
|
|
},
|
|
"weighting": {
|
|
"type": "select",
|
|
"label": "Weighting",
|
|
"group": "Measurement",
|
|
"default": "A",
|
|
"options": [
|
|
{ "value": "A", "label": "A — how loud it sounds" },
|
|
{ "value": "C", "label": "C — keeps the low end" },
|
|
{ "value": "Z", "label": "Z — no weighting at all" }
|
|
]
|
|
},
|
|
"filter": {
|
|
"type": "select",
|
|
"label": "Time constant",
|
|
"group": "Measurement",
|
|
"default": "fast",
|
|
"options": [
|
|
{ "value": "fast", "label": "Fast — 125 ms" },
|
|
{ "value": "slow", "label": "Slow — 1 s" }
|
|
]
|
|
},
|
|
"bands": {
|
|
"type": "switch",
|
|
"label": "Spectrum analysis",
|
|
"group": "Measurement",
|
|
"default": true,
|
|
"help": "Third-octave bands for the RTA and spectrograph tiles. Turn it off if you only ever show the number."
|
|
},
|
|
"calibration": {
|
|
"type": "string",
|
|
"label": "Offsets in dB, one per channel",
|
|
"group": "Calibration",
|
|
"default": "",
|
|
"help": "Leave empty and every reading is dB relative to full scale, labelled dBFS so nobody mistakes it for real SPL."
|
|
}
|
|
},
|
|
"instanceGroups": {
|
|
"Thresholds": { "columns": 2, "help": "The readout turns amber then red as it crosses these. Set them to whatever this room has agreed is loud, not to a legal limit — this is not a compliance meter." }
|
|
},
|
|
"instanceSchema": {
|
|
"channel": {
|
|
"type": "select",
|
|
"label": "Channel",
|
|
"default": "",
|
|
"optionsRoute": "/channels"
|
|
},
|
|
"metric": {
|
|
"type": "select",
|
|
"label": "Reading",
|
|
"default": "spl",
|
|
"options": [
|
|
{ "value": "spl", "label": "SPL — right now" },
|
|
{ "value": "leq", "label": "Leq — average since reset" },
|
|
{ "value": "leq1m", "label": "Leq 1 min" },
|
|
{ "value": "leq10m", "label": "Leq 10 min" },
|
|
{ "value": "peakC", "label": "Peak, C-weighted" }
|
|
]
|
|
},
|
|
"warnAt": { "type": "number", "label": "Amber above", "group": "Thresholds", "default": 95 },
|
|
"alarmAt": { "type": "number", "label": "Red above", "group": "Thresholds", "default": 100 }
|
|
}
|
|
}
|