e8da88b6c1
The FastAPI backend kept one analysis session shared by every visitor and spooled each upload to the server's /tmp, and the browser copied audio into persistent OPFS storage. None of that fits a tool for unreleased masters, so the web UI is now a static site that decodes and analyses in the tab: - web/decode.js: WAV/AIFF PCM and float read directly with libsndfile's scaling (bit-identical to librosa.load; Chromium's WAV decoder scales positive int16 by 1/32767), other formats via decodeAudioData on an OfflineAudioContext at the header-sniffed native rate. ID3 song names. - web/dsp.js: ports of every Metric.compute with the same numerics (pyloudnorm K-weighting via lfilter's DF2T recurrence, BS.1770 gating, EBU LRA, resample_poly's Kaiser filter for true peak, librosa.stft and amplitude_to_db), streaming passes with no signal-length intermediates. - web/metrics.js: build_spec ports producing the existing plot payload. - web/analysis-worker.js + app.js: per-tab track list and result cache, a worker job queue where the visible metric jumps background prefetch, and the renderer, which now also draws the metrics' own reference lines. Deployment is nginx-unprivileged serving web/ read-only with Content-Security-Policy connect-src 'none', so the page cannot make network requests at all, and no access log. No secure context is needed, so plain http on a LAN works. The Python web backend, its smoke test, the web extra and the OPFS store are removed; the desktop app is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015UXA39Sh9coAx4FhRkd8R2
99 lines
3.6 KiB
HTML
99 lines
3.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Audio Mastering Analysis Toolkit</title>
|
|
<link rel="icon" href="data:,">
|
|
<link rel="stylesheet" href="vendor/uPlot.min.css">
|
|
<link rel="stylesheet" href="app.css">
|
|
</head>
|
|
<body>
|
|
|
|
<header class="topbar">
|
|
<div class="brand">
|
|
<span class="logo">◉</span>
|
|
<span class="brand-name">uj-mastering-master</span>
|
|
<span class="brand-sub">web</span>
|
|
</div>
|
|
<div class="topbar-controls">
|
|
<label class="field">
|
|
<span>Metric</span>
|
|
<select id="metric-select" title="Which metric to display"></select>
|
|
</label>
|
|
<label class="field">
|
|
<span>X axis</span>
|
|
<select id="x-mode-select" title="Absolute time, or each track normalised to its own length">
|
|
<option value="absolute">Absolute (s)</option>
|
|
<option value="relative">Relative (%)</option>
|
|
</select>
|
|
</label>
|
|
<button id="toggle-y-log" class="toggle" type="button">Y: log</button>
|
|
<button id="refresh-plot" class="btn" type="button" title="Redraw from the computed results">Refresh</button>
|
|
<div class="separator"></div>
|
|
<button id="pick-files" class="btn primary" type="button">Add audio…</button>
|
|
<input id="file-input" type="file" accept=".mp3,.wav,.flac,.ogg,.oga,.m4a,.aif,.aiff,.opus" multiple hidden>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="layout">
|
|
|
|
<aside class="sidebar">
|
|
<section class="panel">
|
|
<h2>Files <span id="file-count" class="badge">0</span></h2>
|
|
<ul id="file-list" class="file-list"></ul>
|
|
<p id="file-empty" class="empty">Drop audio files here, or use <em>Add audio…</em>.
|
|
They are analysed in this tab and never uploaded.</p>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<h2>Metadata</h2>
|
|
<dl id="metadata" class="metadata">
|
|
<dt>Track</dt><dd class="muted">nothing selected</dd>
|
|
</dl>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<h2>Reference lines <span id="ref-count" class="badge">0</span></h2>
|
|
<ul id="ref-list" class="ref-list"></ul>
|
|
<div class="ref-actions">
|
|
<button id="add-ref" class="btn small" type="button">Add at centre</button>
|
|
<button id="clear-refs" class="btn small" type="button">Clear</button>
|
|
</div>
|
|
<p class="hint">Drag a line on the plot to move it. Sets are kept per metric.</p>
|
|
</section>
|
|
</aside>
|
|
|
|
<section class="stage">
|
|
<div class="plot-head">
|
|
<h1 id="plot-title">No track loaded</h1>
|
|
<div id="legend" class="legend"></div>
|
|
</div>
|
|
|
|
<div id="plot-host" class="plot-host">
|
|
<div id="placeholder" class="placeholder">
|
|
<p class="ph-title">Audio Mastering Analysis Toolkit</p>
|
|
<p class="ph-body">Drop <code>.wav</code>, <code>.flac</code> or <code>.mp3</code> files to analyse.
|
|
Decoding and every metric run inside this tab: the audio is never uploaded
|
|
or saved anywhere (this page isn't even permitted to make network requests),
|
|
and closing the tab discards it. Analysis runs at the file's native sample rate.</p>
|
|
</div>
|
|
<canvas id="heatmap" class="heatmap" hidden></canvas>
|
|
</div>
|
|
|
|
<div class="statusbar">
|
|
<span id="status" class="status">Ready</span>
|
|
<span id="timing" class="timing"></span>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<div id="drop-overlay" class="drop-overlay" hidden><span>Drop to analyse</span></div>
|
|
|
|
<script src="vendor/uPlot.iife.min.js"></script>
|
|
<!-- app.js is an ES module: it decodes with decode.js and runs the metrics
|
|
from dsp.js in analysis-worker.js. -->
|
|
<script type="module" src="app.js"></script>
|
|
</body>
|
|
</html>
|