viewers: default mic to dB(L) + add Mic-unit toggle (dBL ↔ psi)
The sidecar-modal waveform plot was rendering mic in raw psi, while the
rest of SFM (history table column, peaks block, live-device chart,
event detail modal mic field) had already converted to dB(L) — matching
the BW Event Report convention. Unifying.
Both viewers now:
- Default mic chart values + axis title + peak label to dB(L)
- Provide a header toggle ("Mic: dBL" pill) to flip to psi
- Persist the preference via localStorage (sfm_mic_unit)
- Re-render the open chart immediately on toggle
Conversion: dBL = 20 * log10(psi / 2.9e-9), where 2.9e-9 psi is the
20 µPa reference pressure already defined for the rest of the webapp.
Non-positive psi samples (log undefined) render as null; Chart.js
handles them as gaps in line mode and missing bars in histogram mode.
Also fixes event_browser.html's stats table — the MicL row was
hard-coding "<value> psi"; now honors the same toggle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+50
-5
@@ -818,6 +818,12 @@
|
||||
<span class="ft-dot"></span>
|
||||
<span>Force refresh</span>
|
||||
</label>
|
||||
<div class="hdr-sep"></div>
|
||||
<button id="mic-unit-toggle" class="section-btn"
|
||||
onclick="_setMicUnit(_getMicUnit() === 'dBL' ? 'psi' : 'dBL')"
|
||||
title="Toggle microphone display unit (dBL ↔ psi) for waveform plots. Affects all mic charts; persists across page loads.">
|
||||
Mic: dBL
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════════
|
||||
@@ -2560,6 +2566,29 @@ const _SC_CHANNEL_COLORS = {
|
||||
const _SC_CHANNEL_ORDER = ['MicL', 'Long', 'Vert', 'Tran'];
|
||||
let _scCharts = {};
|
||||
|
||||
// User preference for how mic is displayed in plots — dBL (default,
|
||||
// matches BW printout convention + the rest of SFM) or psi (the raw
|
||||
// sample unit). Toggleable via the header pill; persists in localStorage.
|
||||
function _getMicUnit() {
|
||||
return localStorage.getItem('sfm_mic_unit') === 'psi' ? 'psi' : 'dBL';
|
||||
}
|
||||
function _setMicUnit(u) {
|
||||
localStorage.setItem('sfm_mic_unit', u === 'psi' ? 'psi' : 'dBL');
|
||||
_refreshMicUnitToggleLabel();
|
||||
// Re-render the open modal so the change is immediately visible.
|
||||
if (_scCurrentEventId) openSidecarModal(_scCurrentEventId);
|
||||
}
|
||||
function _refreshMicUnitToggleLabel() {
|
||||
const b = document.getElementById('mic-unit-toggle');
|
||||
if (b) b.textContent = `Mic: ${_getMicUnit()}`;
|
||||
}
|
||||
// Convert a psi value to dB(L). Returns null for non-positive values
|
||||
// (log of zero is undefined) — Chart.js handles null as a gap in the line.
|
||||
function _psiToDbl(psi) {
|
||||
if (psi == null || !(psi > 0)) return null;
|
||||
return 20 * Math.log10(psi / DBL_REF);
|
||||
}
|
||||
|
||||
// Adaptive decimal formatter — scientific notation is reserved for truly
|
||||
// extreme values (10000+ or sub-0.0001). Normal-range values (most peaks
|
||||
// fall here) render as decimals with sensible precision. Replaces the
|
||||
@@ -2610,17 +2639,30 @@ function _renderScWaveform(data) {
|
||||
);
|
||||
const lastCh = withData[withData.length - 1];
|
||||
|
||||
const micUnit = _getMicUnit(); // user preference: 'dBL' or 'psi'
|
||||
|
||||
for (const ch of _SC_CHANNEL_ORDER) {
|
||||
const chData = channels[ch];
|
||||
if (!chData) continue;
|
||||
const values = chData.values || [];
|
||||
let values = chData.values || [];
|
||||
let chUnit = chData.unit || '';
|
||||
let chPeak = chData.peak;
|
||||
|
||||
// Mic channel: convert from raw psi to dB(L) when user prefers dBL
|
||||
// (default). Mic samples that are zero/negative become null (Chart.js
|
||||
// renders them as gaps in line mode, zero-height bars in histogram mode).
|
||||
if (ch === 'MicL' && chUnit === 'psi' && micUnit === 'dBL') {
|
||||
values = values.map(_psiToDbl);
|
||||
chPeak = _psiToDbl(chPeak);
|
||||
chUnit = 'dB(L)';
|
||||
}
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.style.cssText = 'background:var(--surface);border:1px solid var(--border2);border-radius:6px;padding:6px 30px 4px 10px';
|
||||
const lbl = document.createElement('div');
|
||||
lbl.style.cssText = `font-size:10px;font-weight:600;letter-spacing:0.05em;text-transform:uppercase;margin-bottom:2px;color:${_SC_CHANNEL_COLORS[ch]};display:flex;justify-content:space-between`;
|
||||
const peakStr = chData.peak != null
|
||||
? `peak ${_fmtPeak(chData.peak, chData.unit)}`
|
||||
const peakStr = chPeak != null
|
||||
? `peak ${_fmtPeak(chPeak, chUnit)}`
|
||||
: '';
|
||||
lbl.innerHTML = `<span>${ch}</span><span style="color:var(--text-dim);font-weight:normal">${peakStr}</span>`;
|
||||
wrap.appendChild(lbl);
|
||||
@@ -2716,7 +2758,7 @@ function _renderScWaveform(data) {
|
||||
title: items => isHistogram
|
||||
? `interval ${items[0].label}`
|
||||
: `t = ${items[0].label} ms`,
|
||||
label: item => `${ch}: ${_fmtPeak(item.raw, chData.unit)}`,
|
||||
label: item => `${ch}: ${_fmtPeak(item.raw, chUnit)}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2730,7 +2772,7 @@ function _renderScWaveform(data) {
|
||||
...yBounds,
|
||||
ticks: { color: '#484f58', maxTicksLimit: 4 },
|
||||
grid: { color: '#21262d' },
|
||||
title: { display: true, text: chData.unit || '', color: '#484f58', font: { size: 9 } },
|
||||
title: { display: true, text: chUnit, color: '#484f58', font: { size: 9 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3058,6 +3100,9 @@ document.addEventListener('keydown', e => {
|
||||
// hit localhost:8200, 10.0.0.44:8200, or anything else.
|
||||
document.getElementById('api-base').value = window.location.origin;
|
||||
|
||||
// Reflect any persisted mic-unit preference in the header pill on load
|
||||
_refreshMicUnitToggleLabel();
|
||||
|
||||
// We default to Database view → trigger initial history + units load
|
||||
// (switchSection handles this when clicked, but we never click on first paint).
|
||||
if (currentSection === 'db') {
|
||||
|
||||
Reference in New Issue
Block a user