tcdweb/index.html
Armin e159b76e0d Add peak hold decay rate selector
Introduce a new dropdown to control how fast the peak-hold line
decays on the live spectrum. Options:
  Off   — peaks never decay (manual reset only)
  Slow  — slow fade (0.005/frame)
  Medium — moderate fade (0.015/frame, matches previous fixed rate)
  Fast   — quick fade (0.03/frame)

Previously the decay rate was hardcoded at 0.015/frame.
2026-07-06 18:09:41 +02:00

164 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tcd - Audio Transcode Detector</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<input type="file" id="fileInput" accept=".mp3,.flac,.wav,.aiff,.aif,.ogg,.opus,.m4a,.wma,.ac3,.eac3,.aac,.alac,.wv,.mp2,.mp1,.ape,.dsf,.dff,audio/*" hidden>
<div id="errorMsg"></div>
<div id="results">
<section id="playerSection">
<div class="section-header">
<h2>Player &amp; Realtime Analysis</h2>
<div class="section-brand"><span class="logo">tcd</span> <span class="subtitle">Audio Authenticity Analyzer</span><br><span class="tagline">Psychoacoustic transcode detection via spectral analysis</span></div>
</div>
<div class="player-controls">
<button id="loadBtn" class="player-btn load-btn" title="Load Audio File">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
Load File
</button>
<button id="playBtn" class="player-btn" title="Play/Pause"></button>
<button id="stopBtn" class="player-btn" title="Stop"></button>
<button id="holdBtn" class="player-btn" title="Hold Max Cutoff"></button>
<button id="resetBtn" class="player-btn" title="Reset Peaks"></button>
<span id="playTime" class="player-time">0:00.0 / 0:00.0</span>
<select id="windowSelect" class="window-select" title="Spectrum window function"><option value="none">None</option><option value="hanning" selected>Hanning</option><option value="hamming">Hamming</option><option value="blackman">Blackman-Harris</option><option value="bartlett">Bartlett</option><option value="gaussian">Gaussian</option><option value="kaiser">Kaiser</option><option value="flatTop">Flat Top</option></select>
<select id="peakHoldDecay" class="window-select" title="Peak hold decay rate">
<option value="off">Hold: Off</option>
<option value="slow">Hold: Slow</option>
<option value="medium" selected>Hold: Medium</option>
<option value="fast">Hold: Fast</option>
</select>
</div>
<div class="chart-container" style="position:relative">
<canvas id="waveformChart"></canvas>
<div id="loadingOverlay" class="loading-overlay">
<div class="spinner"></div>
<div id="loadingMsg">Processing...</div>
</div>
</div>
<div class="chart-container" style="margin-top:10px;position:relative">
<canvas id="liveSpectrum"></canvas>
<canvas id="liveSpectrumOverlay" style="position:absolute;top:0;left:0;pointer-events:none"></canvas>
</div>
<div class="metrics-grid" style="margin-top:10px">
<div class="metric-card">
<div class="metric-label">Live Peak</div>
<div class="metric-value" id="livePeak">-</div>
</div>
<div class="metric-card">
<div class="metric-label">Live Cutoff</div>
<div class="metric-value" id="liveCutoff">-</div>
</div>
</div>
</section>
<section id="fileInfo">
<h2>File Information</h2>
<div class="info-grid">
<div class="info-item fullwidth"><span class="info-label">Filename</span><span id="fileName" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Format</span><span id="fileFormat" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Sample Rate</span><span id="fileSampleRate" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Channels</span><span class="info-value"><span id="fileChannels">-</span> <span id="fileStereoMode" class="info-value-tag"></span></span></div>
<div class="info-item"><span class="info-label">Duration</span><span id="fileDuration" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Bitrate</span><span class="info-value"><span id="fileBitrate">-</span> <span id="fileBitrateMode" class="info-value-tag"></span></span></div>
<div class="info-item"><span class="info-label">File Size</span><span id="fileSize" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">BPM</span><span class="info-value bpm-value"><span id="fileBPM">-</span><span class="bpm-buttons"><button class="bpm-btn" id="bpmHalf" title="Halve">÷2</button><button class="bpm-btn" id="bpmDouble" title="Double">×2</button></span></span></div>
</div>
</section>
<section id="metadataSection">
<h2>Metadata Tags</h2>
<div id="metadataGrid" class="metadata-grid"></div>
<div id="metadataEmpty" class="metadata-empty">No metadata tags found</div>
</section>
<section id="spectrumSection">
<h2>Power Spectrum</h2>
<div class="chart-container">
<canvas id="spectrumChart"></canvas>
</div>
</section>
<section id="metricsSection">
<h2>Key Metrics</h2>
<div class="metrics-grid">
<div class="metric-card" title="Peak Magnitude - highest spectral amplitude in dBFS (0 dBFS = full scale).&#10;&#10;How to read:&#10;• > -1 dBFS: Heavily limited/mastered, extremely loud&#10;• -6 to -1 dBFS: Typical modern mastering&#10;• -12 to -6 dBFS: Conservative, good headroom&#10;• < -12 dBFS: Quiet or attenuated&#10;&#10;Significance: Combined with noise floor, indicates the effective dynamic range of the audio.">
<div class="metric-label">Peak</div>
<div class="metric-value" id="mPeak">-</div>
</div>
<div class="metric-card" title="Spectral Cutoff - highest frequency bin with significant energy above floor, as Hz and % of Nyquist (sample rate / 2).&#10;&#10;How to read:&#10;• ≥ 20 kHz (≥ 90%): Full bandwidth - lossless or high-bitrate lossy (320k MP3, 256k AAC)&#10;• 16-19 kHz (70-85%): ~192k MP3 / ~128k AAC&#10;• 14-16 kHz (60-70%): ~128k MP3&#10;• 10-14 kHz (45-60%): ~64-96k lossy&#10;• < 10 kHz (< 45%): Very low bitrate (< 64k)&#10;&#10;Significance: The single strongest indicator of lossy compression. Lower cutoff = more aggressive compression.">
<div class="metric-label">Cutoff</div>
<div class="metric-value" id="mCutoff">-</div>
</div>
<div class="metric-card" title="Transition Bandwidth (Steepness) - frequency gap between the -20 dBFS and -60 dBFS points. Measures how abruptly high-frequencies drop off.&#10;&#10;How to read:&#10;• < 500 Hz: Brick-wall cutoff - transcode or low-bitrate lossy&#10;• 500-1500 Hz: Moderately sharp - suspicious, may be higher-bitrate lossy&#10;• 1500-3000 Hz: Gradual rolloff - typical of genuine lossless&#10;• > 3000 Hz: Very gradual - natural rolloff from analog sources&#10;&#10;Significance: The strongest differentiator between genuine lossless and transcodes. Lossy encoders produce artificially steep lowpass filters.">
<div class="metric-label">Steepness</div>
<div class="metric-value" id="mSteepness">-</div>
</div>
<div class="metric-card" title="Noise Floor - average magnitude in the top quarter of the spectrum (> 3/4 Nyquist), in dB relative to peak.&#10;&#10;How to read:&#10;• < -90 dB: Excellent - extremely clean, high dynamic range&#10;• -80 to -90 dB: Very good - typical of quality lossless&#10;• -70 to -80 dB: Moderate - some quantization noise present&#10;• -60 to -70 dB: Elevated - suggests lossy compression or analog hiss&#10;• > -60 dB: High - almost certainly lossy compressed&#10;&#10;Significance: Lossy codecs raise the noise floor through quantization. High noise floor + sharp cutoff = strong transcode indicator.">
<div class="metric-label">Noise Floor</div>
<div class="metric-value" id="mNoise">-</div>
</div>
<div class="metric-card" title="Spectral Roughness (CoV) - coefficient of variation (σ/μ) of spectral magnitudes in the 60-95% region below cutoff. Measures how uneven/scalloped the spectrum is.&#10;&#10;How to read:&#10;• > 0.40: Strong spectral scalloping - clear lossy encoding signature&#10;• 0.30-0.40: Moderate scalloping - suspicious, may be high-bitrate lossy&#10;• 0.15-0.30: Mild unevenness - needs band-ratio correlation&#10;• < 0.15: Smooth spectrum - typical of genuine lossless&#10;&#10;Significance: Lossy codecs create a comb-filter pattern via frequency-bin quantization. High roughness = high probability of lossy encoding.">
<div class="metric-label">Roughness</div>
<div class="metric-value" id="mRoughness">-</div>
</div>
<div class="metric-card" title="Band Ratio - ratio of average spectral energy in 16-20 kHz band to 12-16 kHz band. Detects unnatural high-frequency tilt.&#10;&#10;How to read:&#10;• < 0.5: Normal natural rolloff in top octave&#10;• 0.5-0.9: Typical for most recordings&#10;• 0.9-1.0: Flat HF - possible spectral reshaping from lossy encoding&#10;• > 1.0: Inverted tilt (more energy at 16-20 kHz than below) - strong transcode signature&#10;&#10;Significance: Used as secondary criterion when cutoff is high but roughness is ambiguous. Transcodes often show unnatural energy distribution in the top octaves.">
<div class="metric-label">Band Ratio</div>
<div class="metric-value" id="mBandRatio">-</div>
</div>
</div>
<div class="analysis-info">
<span><strong>Windows:</strong> <span id="windowsCount">-</span></span>
<span><strong>FFT:</strong> <span id="fftSize">-</span></span>
</div>
</section>
<section id="decisionSection" title="Verdict - the overall classification of the audio file based on spectral analysis.&#10;&#10;NATIVE: File matches its declared lossy format at a consistent bitrate (single encode).&#10;UPSCALED: Lossy file re-encoded to a higher bitrate than the original source.&#10;GENUINE: File appears to be native lossless (no transcode artifacts detected).&#10;TRANSCODE: Lossy source re-encoded to a lossless format (e.g. 128k MP3 → FLAC).&#10;&#10;The verdict is decided by: cutoff ratio, transition bandwidth (steepness), roughness (CoV), and band-ratio analysis against adaptive thresholds.">
<h2>Decision</h2>
<div id="decision" class="decision-text">-</div>
<div class="confidence-container" title="Validity Confidence - how strongly the evidence supports the verdict, from 0% (random chance) to 100% (certain).&#10;&#10;85-100%: Very strong evidence - multiple criteria agree decisively.&#10;70-84%: Strong evidence - most criteria support the verdict.&#10;50-69%: Moderate evidence - some criteria triggered but not all.&#10;30-49%: Weak evidence - marginal, borderline detection.&#10;0-29%: Inconclusive - possible false positive, or audio is ambiguous.&#10;&#10;For lossy files: confidence is based on how far below the expected cutoff ratio the file sits.&#10;For lossless candidates: confidence blends cutoff ratio margin, steepness margin, and (above 80% Nyquist) secondary roughness + band-ratio criteria.">
<div class="confidence-label">Validity Confidence</div>
<div class="confidence-bar-bg">
<div id="confidenceBar" class="confidence-bar-fill" style="width:0%">0%</div>
</div>
<div id="confidenceLabel" class="confidence-label-small">-</div>
</div>
</section>
<section id="factors" title="Factor Analysis - step-by-step breakdown of the detection criteria used to reach the verdict.&#10;&#10;① PRIMARY criteria (applied to ALL files):&#10; Compares transition bandwidth (steepness) to an adaptive threshold based on cutoff ratio.&#10; If steepness is BELOW the threshold, the spectrum has an artificially sharp cutoff → transcode flagged.&#10; Files with cutoff_ratio ≥ 0.99 (full spectrum) bypass this check.&#10;&#10;② SECONDARY criteria (applied when cutoff_ratio > 0.80):&#10; Checks spectral roughness (CoV) and band ratio for additional transcode signatures.&#10; High roughness (> 0.40) alone is sufficient to confirm. Moderate roughness needs band-ratio corroboration.&#10;&#10;Lossy files: checked only for cutoff ratio vs expected range for the bitrate.&#10;✓ = criterion met (supports transcode/upscaled), ✗ = criterion not met (supports genuine/native).">
<section id="dataSection">
<h2>Analysis Data</h2>
<div class="table-container">
<table id="dataTable">
<thead>
<tr><th>Parameter</th><th>Value</th></tr>
</thead>
<tbody></tbody>
</table>
</div>
</section>
</div>
<footer>
<p>Based on <a href="https://codeberg.org/armin/tcd" target="_blank">tcd</a> - Psychoacoustic audio authenticity analysis</p>
</footer>
</div>
<script src="js/metadata.js"></script>
<script src="js/analyzer.js"></script>
<script src="js/app.js"></script>
</body>
</html>