SPL Meter: measure the room, not just watch it #10
+23
-15
@@ -210,7 +210,7 @@ function readoutView(root, moduleApi) {
|
||||
*/
|
||||
function fit() {
|
||||
const w = root.clientWidth, h = root.clientHeight;
|
||||
const key = `${w}x${h}:${num.textContent.length}:${unit.textContent}`;
|
||||
const key = `${w}x${h}:${num.textContent.length}:${unit.textContent}:${sub.hidden}`;
|
||||
if (key === fitKey || w < 2 || h < 2) return;
|
||||
fitKey = key;
|
||||
|
||||
@@ -257,6 +257,7 @@ function readoutView(root, moduleApi) {
|
||||
const parts = [`${payload.weighting || '?'} · ${label}`, `ch ${ch.i}`];
|
||||
if (ch.clip) parts.push('CLIP');
|
||||
if (!payload.calibrated) parts.push('uncalibrated');
|
||||
sub.hidden = s.caption === false;
|
||||
sub.textContent = parts.join(' · ');
|
||||
fit();
|
||||
},
|
||||
@@ -331,18 +332,22 @@ function spectrumView(root, moduleApi, scrolling) {
|
||||
}
|
||||
|
||||
if (scrolling) {
|
||||
// A title-bar toggle so the axis can be flipped while watching, without
|
||||
// opening the gear. It writes the same setting the gear does, so the two
|
||||
// never disagree — but saveInstanceSettings deliberately does not remount
|
||||
// us, so the new value is held here too and read back by axis().
|
||||
// The axis lives here and nowhere else (rule 11): a tile does not offer
|
||||
// the same action twice. Icon only, tooltip carries the words (rule 9).
|
||||
// saveInstanceSettings deliberately does not remount us, so the new
|
||||
// value is held here as well and read back by axis().
|
||||
const swapIcon = '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" '
|
||||
+ 'stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">'
|
||||
+ '<path d="M2 5h9M8.5 2 11.5 5 8.5 8"/><path d="M14 11H5M7.5 8 4.5 11 7.5 14"/></svg>';
|
||||
const describe = () => (axis() === 'x' ? 'Time left to right' : 'Time top to bottom');
|
||||
axisBtn = moduleApi.header.addButton({
|
||||
label: '⇆',
|
||||
title: 'Swap axes',
|
||||
icon: swapIcon,
|
||||
title: describe(),
|
||||
onClick() {
|
||||
axisOverride = axis() === 'x' ? 'y' : 'x';
|
||||
moduleApi.saveInstanceSettings({ axis: axisOverride });
|
||||
painted = false; // the picture means nothing in the other orientation
|
||||
axisBtn.title = axis() === 'x' ? 'Time left to right' : 'Time top to bottom';
|
||||
axisBtn.title = describe();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -403,7 +408,8 @@ function spectrumView(root, moduleApi, scrolling) {
|
||||
const freqs = folded.freq;
|
||||
let values = folded.db;
|
||||
const grid = s.gridlines === true;
|
||||
const axisPad = 13;
|
||||
const showLabels = s.labels !== false;
|
||||
const axisPad = showLabels || grid ? 13 : 0;
|
||||
|
||||
if (!scrolling) {
|
||||
// Damp the bars toward the measurement. Cosmetic: `values` is what
|
||||
@@ -450,15 +456,17 @@ function spectrumView(root, moduleApi, scrolling) {
|
||||
ctx.fillRect(i * bw, plotH - bh, Math.max(1, bw - (bw > 3 ? 1 : 0)), bh);
|
||||
}
|
||||
|
||||
ctx.fillStyle = muted; ctx.font = '9px system-ui'; ctx.textAlign = 'center';
|
||||
for (const l of labels) ctx.fillText(hzLabel(l.f), l.pos, h - 3);
|
||||
if (showLabels) {
|
||||
ctx.fillStyle = muted; ctx.font = '9px system-ui'; ctx.textAlign = 'center';
|
||||
for (const l of labels) ctx.fillText(hzLabel(l.f), l.pos, h - 3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ── spectrogram ──
|
||||
const timeX = axis() === 'x';
|
||||
const colW = 2;
|
||||
const gutter = grid ? axisPad : 0;
|
||||
const gutter = showLabels ? axisPad : 0;
|
||||
// Frequency occupies whichever axis time does not.
|
||||
const freqExtent = timeX ? h - gutter : w - gutter;
|
||||
const timeExtent = timeX ? w : h;
|
||||
@@ -489,7 +497,7 @@ function spectrumView(root, moduleApi, scrolling) {
|
||||
}
|
||||
}
|
||||
|
||||
if (grid) {
|
||||
if (grid || showLabels) {
|
||||
const posOf = (f) => {
|
||||
const t = Math.log2(f / freqs[0]) / Math.log2(freqs[freqs.length - 1] / freqs[0]);
|
||||
return timeX ? freqExtent - t * freqExtent : gutter + t * freqExtent;
|
||||
@@ -499,7 +507,7 @@ function spectrumView(root, moduleApi, scrolling) {
|
||||
if (timeX) ctx.fillRect(0, h - gutter, w, gutter);
|
||||
else ctx.fillRect(0, 0, gutter, h);
|
||||
ctx.fillStyle = muted; ctx.font = '9px system-ui';
|
||||
ctx.strokeStyle = border; ctx.globalAlpha = 0.35; ctx.beginPath();
|
||||
ctx.strokeStyle = border; ctx.globalAlpha = grid ? 0.35 : 0; ctx.beginPath();
|
||||
for (const l of labels) {
|
||||
if (timeX) {
|
||||
const y = Math.round(l.pos) + 0.5;
|
||||
@@ -511,7 +519,7 @@ function spectrumView(root, moduleApi, scrolling) {
|
||||
}
|
||||
ctx.stroke(); ctx.globalAlpha = 1;
|
||||
ctx.textAlign = timeX ? 'left' : 'center';
|
||||
for (const l of labels) {
|
||||
for (const l of showLabels ? labels : []) {
|
||||
if (timeX) ctx.fillText(hzLabel(l.f), 2, Math.min(h - gutter - 2, l.pos + 3));
|
||||
else ctx.fillText(hzLabel(l.f), l.pos, h - 3);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "spl-meter",
|
||||
"name": "SPL Meter",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"proddash": ">=1.5.0",
|
||||
"description": "Calibrated sound level, Leq and RTA from an audio input on this machine",
|
||||
"permissions": [
|
||||
|
||||
+11
-20
@@ -48,10 +48,10 @@ function averagingField() {
|
||||
return {
|
||||
type: 'select', label: 'Averaging', default: 'fast',
|
||||
options: [
|
||||
{ value: 'fast', label: 'Fast — 125 ms' },
|
||||
{ value: 'slow', label: 'Slow — 1 s' },
|
||||
{ value: 'inf', label: 'Inf — since reset' },
|
||||
{ value: 'none', label: 'None — no averaging' },
|
||||
{ value: 'fast', label: 'Fast · 125 ms' },
|
||||
{ value: 'slow', label: 'Slow · 1 s' },
|
||||
{ value: 'inf', label: 'Inf · since reset' },
|
||||
{ value: 'none', label: 'None' },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -63,9 +63,6 @@ function averagingField() {
|
||||
*/
|
||||
const BANDING_FIELD = {
|
||||
type: 'select', label: 'Banding', default: '3',
|
||||
help: 'Finer bandings need a wide tile to be worth drawing, and below '
|
||||
+ 'roughly 200 Hz the window length limits real resolution however fine '
|
||||
+ 'this is set.',
|
||||
options: [
|
||||
{ value: '3', label: '1/3 octave' },
|
||||
{ value: '6', label: '1/6 octave' },
|
||||
@@ -193,11 +190,12 @@ module.exports = {
|
||||
type: 'select', label: 'Show', default: 'level',
|
||||
options: [
|
||||
{ value: 'level', label: 'Level' },
|
||||
{ value: 'leq1m', label: 'Leq, last minute' },
|
||||
{ value: 'leq10m', label: 'Leq, last 10 minutes' },
|
||||
{ value: 'peakC', label: 'Peak, C-weighted' },
|
||||
{ value: 'leq1m', label: 'Leq 1 min' },
|
||||
{ value: 'leq10m', label: 'Leq 10 min' },
|
||||
{ value: 'peakC', label: 'Peak C' },
|
||||
],
|
||||
},
|
||||
caption: { type: 'switch', label: 'Caption', default: true },
|
||||
warnAt: { type: 'number', label: 'Amber above', group: 'Thresholds', default: 95 },
|
||||
alarmAt: { type: 'number', label: 'Red above', group: 'Thresholds', default: 100 },
|
||||
},
|
||||
@@ -222,15 +220,14 @@ module.exports = {
|
||||
banding: BANDING_FIELD,
|
||||
smoothing: {
|
||||
type: 'select', label: 'Smoothing', default: 'medium',
|
||||
help: 'How hard the bars are damped between frames. Cosmetic only — '
|
||||
+ 'it changes the animation, never the measurement.',
|
||||
options: [
|
||||
{ value: 'off', label: 'Off — every frame as measured' },
|
||||
{ value: 'off', label: 'Off' },
|
||||
{ value: 'light', label: 'Light' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'heavy', label: 'Heavy' },
|
||||
],
|
||||
},
|
||||
labels: { type: 'switch', label: 'Labels', default: true },
|
||||
gridlines: { type: 'switch', label: 'Gridlines', default: false },
|
||||
},
|
||||
},
|
||||
@@ -240,13 +237,7 @@ module.exports = {
|
||||
instanceSchema: {
|
||||
channel: CHANNEL_FIELD,
|
||||
banding: BANDING_FIELD,
|
||||
axis: {
|
||||
type: 'select', label: 'Time runs', default: 'x',
|
||||
options: [
|
||||
{ value: 'x', label: 'Left to right' },
|
||||
{ value: 'y', label: 'Top to bottom' },
|
||||
],
|
||||
},
|
||||
labels: { type: 'switch', label: 'Labels', default: true },
|
||||
gridlines: { type: 'switch', label: 'Gridlines', default: false },
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user