diff --git a/modules/spl-meter/client.js b/modules/spl-meter/client.js index 59e3765..b865899 100644 --- a/modules/spl-meter/client.js +++ b/modules/spl-meter/client.js @@ -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 = '' + + ''; + 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); } diff --git a/modules/spl-meter/module.json b/modules/spl-meter/module.json index f35af77..e469e98 100644 --- a/modules/spl-meter/module.json +++ b/modules/spl-meter/module.json @@ -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": [ diff --git a/modules/spl-meter/server.js b/modules/spl-meter/server.js index 68fed3a..d3609a4 100644 --- a/modules/spl-meter/server.js +++ b/modules/spl-meter/server.js @@ -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 }, }, },