drum-sample-extractor / web /index.html
ChatGPT
fix: make upload and fallback robust
5a90820
Raw
History Blame Contribute Delete
16.1 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sample Extractor</title>
<link rel="stylesheet" href="/web/styles.css" />
</head>
<body>
<div id="globalDropOverlay" class="global-drop-overlay" aria-hidden="true">
<div>
<strong>Drop audio to extract samples</strong>
<span>The waveform appears immediately and processing starts automatically.</span>
</div>
</div>
<div id="errorBanner" class="error-banner" role="alert" hidden>
<div class="error-copy">
<strong id="errorTitle">Request failed</strong>
<span id="errorMessage">Something went wrong.</span>
<small id="errorDetail"></small>
</div>
<button id="dismissErrorButton" class="icon-button" type="button" aria-label="Dismiss error">×</button>
</div>
<div class="app-shell">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>
<strong>Sample Extractor</strong>
<span class="beta-pill">BETA</span>
</div>
<div id="dropzone" class="file-picker" title="Choose audio, or drop audio anywhere" role="button" tabindex="0">
<input id="fileInput" type="file" accept="audio/*,.wav,.mp3,.flac,.aiff,.ogg,.m4a,.aac" aria-label="Choose audio file" />
<button id="chooseAudioButton" class="file-picker-button" type="button">Choose audio</button>
<span class="file-picker-copy"><span id="dropTitle">Drop audio anywhere</span><small id="dropMeta">Processing starts automatically</small></span>
<span aria-hidden="true"></span>
</div>
<div class="top-actions">
<div class="backend-pill" aria-live="polite">
<span class="status-dot" id="healthDot"></span>
<span><strong id="healthText">Connecting</strong><small id="healthSubtext">FastAPI backend</small></span>
</div>
<button id="runButton" class="secondary-action" type="button" disabled>↻ Reprocess</button>
<button id="exportAllButton" class="secondary-action" type="button" disabled>⇧ Export All</button>
<button id="exportSelectedButton" class="primary-action" type="button" disabled>⇄ Export Selected <span id="selectedCountTop">(0)</span></button>
</div>
</header>
<main class="main-grid">
<section class="canvas-pane" aria-label="Waveform and samples">
<section class="wave-panel" aria-label="Waveform overview">
<div class="wave-toolbar">
<div class="zoom-tools" aria-label="Waveform zoom controls">
<button id="zoomInspectButton" class="tool-button" type="button" title="Zoom with pinch or wheel"></button>
<button id="zoomOutButton" class="tool-button" type="button" title="Zoom out"></button>
<button id="zoomInButton" class="tool-button" type="button" title="Zoom in">+</button>
<button id="zoomFitButton" class="tool-button fit" type="button" title="Fit waveform">Fit</button>
</div>
<div class="wave-status">
<strong id="waveTitle">Load audio</strong>
<span id="waveProgressText">Drop a file anywhere. Extraction starts automatically.</span>
</div>
<time id="transportTime" class="top-time">0:00 / 0:00</time>
</div>
<canvas id="waveform" class="waveform" height="360"></canvas>
<div class="transport-row" aria-label="Preview transport">
<button id="transportPlayButton" class="round-play" type="button" aria-label="Play preview"></button>
<input id="transportSeek" class="transport-seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek preview" />
<div class="preview-tabs" aria-label="Preview source">
<button id="previewSourceButton" class="preview-tab active" type="button" data-preview-mode="source">Source</button>
<button id="previewStemButton" class="preview-tab" type="button" data-preview-mode="stem">Stem</button>
<button id="previewReproductionButton" class="preview-tab" type="button" data-preview-mode="reproduction">Reproduced</button>
</div>
</div>
<div class="hidden-audio-bank" aria-hidden="true">
<audio id="sourcePreview"></audio>
<audio id="stemAudio"></audio>
<audio id="reconAudio"></audio>
<audio id="hitAudio"></audio>
<audio id="sampleAudio"></audio>
</div>
</section>
<section class="sample-board" aria-label="Extracted samples">
<div class="board-header">
<h2>Extracted Samples <span id="sampleCountLabel">(0)</span></h2>
<span id="resultSummary">Drop audio. The app uploads, separates stems, tunes parameters, and deals sample cards.</span>
</div>
<div id="samplesGrid" class="sample-grid"></div>
</section>
</section>
<aside class="settings-pane" aria-label="Settings">
<header class="settings-head">
<strong>Advanced Settings</strong>
<button class="icon-button" type="button" aria-label="Hide settings">×</button>
</header>
<section class="settings-section simple-section">
<h3>Detection</h3>
<label class="control-line">Stem
<select id="stem"></select>
</label>
<label class="range-line">Sensitivity
<span class="range-wrap"><input id="onset_delta" type="range" min="0.01" max="0.35" step="0.005" /><output id="sensitivityOutput">Auto</output></span>
</label>
<label class="range-line">Minimum interval
<span class="range-wrap"><input id="min_gap" type="range" min="0.001" max="1" step="0.005" /><output id="minGapOutput">Auto</output></span>
</label>
<label class="range-line">Minimum amplitude
<span class="range-wrap"><input id="energy_threshold_db" type="range" min="-100" max="0" step="1" /><output id="energyOutput">Auto</output></span>
</label>
</section>
<section class="settings-section simple-section">
<h3>Grouping</h3>
<label class="toggle-line"><span>Group Similar Hits</span><input id="groupSimilarToggle" type="checkbox" checked /></label>
<label class="range-line">Max groups
<span class="range-wrap"><input id="target_max" type="range" min="1" max="64" step="1" /><output id="targetMaxOutput">Auto</output></span>
</label>
<label class="range-line">Max group distance
<span class="range-wrap"><input id="ncc_threshold" type="range" min="0" max="1" step="0.01" /><output id="nccOutput">Auto</output></span>
</label>
</section>
<section class="settings-section simple-section">
<h3>Waveform</h3>
<label class="control-line">Resolution
<select id="waveformResolution"><option>High</option><option>Medium</option><option>Low</option></select>
</label>
<label class="control-line">Channels
<select id="waveformChannels"><option>Left & Right</option><option>Mono</option></select>
</label>
</section>
<details class="settings-section advanced-fold">
<summary>Expert pipeline controls</summary>
<div class="expert-grid">
<label>Separation engine<select id="separation_backend"><option value="spleeter">Spleeter (default)</option><option value="demucs">Demucs</option><option value="none">No separation / full mix</option></select></label>
<label>Spleeter model<select id="spleeter_model"></select></label>
<label>Demucs model<select id="demucs_model"></select></label>
<label>Clustering mode<select id="clustering_mode"><option value="batch_quality">batch quality</option><option value="online_preview">online preview</option></select></label>
<label>Shifts<input id="demucs_shifts" type="number" min="0" max="8" step="1" /></label>
<label>Overlap<input id="demucs_overlap" type="number" min="0" max="0.9" step="0.05" /></label>
<label>Onset mode<select id="onset_mode"><option value="auto">auto / multiband</option><option value="percussive">percussive</option><option value="harmonic">harmonic</option><option value="broadband">broadband</option></select></label>
<label>Pre-pad<input id="pre_pad" type="number" min="0" max="0.25" step="0.001" /></label>
<label>Min duration<input id="min_dur" type="number" min="0.001" max="10" step="0.005" /></label>
<label>Max duration<input id="max_dur" type="number" min="0.01" max="10" step="0.1" /></label>
<label>Target min<input id="target_min" type="number" min="0" max="256" step="1" /></label>
<label>Attack ms<input id="attack_ms" type="number" min="1" max="250" step="1" /></label>
<label>Mel prefilter<input id="mel_threshold" type="number" min="0" max="1" step="0.01" /></label>
<label>Linkage<select id="linkage"><option value="average">average</option><option value="complete">complete</option><option value="single">single</option></select></label>
<label>MIDI grid<select id="subdivision"><option value="4">4th</option><option value="8">8th</option><option value="16">16th</option><option value="32">32nd</option><option value="64">64th</option></select></label>
</div>
<div class="toggles">
<label><input id="synthesize" type="checkbox" /> synthesize alternates</label>
<label><input id="quantize_midi" type="checkbox" /> quantize MIDI</label>
<label><input id="auto_tune" type="checkbox" checked /> automatic parameter tuning</label>
<label><input id="use_disk_cache" type="checkbox" /> disk cache stems/source loads</label>
<label><input id="allow_backend_fallback" type="checkbox" /> fallback to full mix if separation is unavailable</label>
</div>
<div class="preset-row">
<button id="usePreviewButton" class="secondary-action" type="button">Fast preview</button>
<button id="useQualityButton" class="secondary-action" type="button">Best quality</button>
<button id="clearCacheButton" class="secondary-action" type="button">Clear cache</button>
</div>
</details>
<button id="resetUiButton" class="reset-button" type="button">Reset to Defaults</button>
</aside>
</main>
<footer class="bottom-bar" aria-label="Selection and tools">
<div class="selection-count"><span class="checked-box"></span><strong id="selectedCountBottom">0 Selected</strong></div>
<div class="bottom-zoom"><button class="tool-button" type="button" data-zoom-command="out">⌕ −</button><input class="mini-slider" type="range" min="1" max="64" value="1" /><button class="tool-button" type="button" data-zoom-command="in">+</button></div>
<div class="bottom-actions"><button id="selectAllSamplesButton" class="secondary-action" type="button">☑ Select All</button><button id="clearSelectionButton" class="secondary-action" type="button">🗑 Clear</button><button id="openToolsButton" class="help-button" type="button">?</button></div>
</footer>
<section id="toolsDrawer" class="tools-drawer" aria-label="Review tools" hidden>
<div class="drawer-grid">
<article>
<h3>Pipeline</h3>
<span id="jobPill" class="job-pill">idle</span>
<div id="stageList" class="stage-list"></div>
<pre id="logs" class="logs" aria-live="polite"></pre>
</article>
<article>
<h3>Exports</h3>
<div id="downloads" class="downloads compact-downloads"></div>
<div id="editedDownloads" class="downloads edited-downloads"></div>
<button id="exportStateButton" class="secondary-action" type="button" disabled>Export edited pack</button>
</article>
<article>
<h3>Selection</h3>
<p id="selectedHitMeta">Click an onset marker or hit row to audition the detected slice.</p>
<p id="selectedSampleMeta">Click a sample card to hear the representative sample.</p>
<div id="supervisionSummary" class="state-summary">No interactive state loaded.</div>
</article>
<article>
<h3>Run history</h3>
<button id="refreshHistoryButton" class="secondary-action" type="button">Refresh history</button>
<div id="historyList" class="history-list"></div>
</article>
</div>
<details class="drawer-advanced">
<summary>Review & edit state</summary>
<div class="supervision-actions">
<button id="refreshStateButton" class="secondary-action" type="button">Refresh state</button>
<button id="forceOnsetButton" class="secondary-action" type="button" disabled>Add-onset mode off</button>
<button id="undoButton" class="secondary-action" type="button" disabled>Undo edit</button>
</div>
<div class="supervision-tools">
<label>Target cluster<select id="targetClusterSelect"></select></label>
<button id="moveHitButton" class="secondary-action" type="button" disabled>Move selected hit</button>
<button id="pullHitButton" class="secondary-action" type="button" disabled>Pull into new cluster</button>
<button id="acceptHitButton" class="secondary-action" type="button" disabled>Accept hit</button>
<button id="favoriteHitButton" class="secondary-action" type="button" disabled>Favorite</button>
<button id="suppressHitButton" class="secondary-action danger" type="button" disabled>Suppress</button>
<button id="restoreHitButton" class="secondary-action" type="button" disabled>Restore</button>
<button id="lockClusterButton" class="secondary-action" type="button" disabled>Lock cluster</button>
<button id="explainClusterButton" class="secondary-action" type="button" disabled>Explain</button>
</div>
<div class="supervision-grid">
<article><h4>Outlier-first review queue</h4><div id="reviewQueue" class="compact-list"></div></article>
<article><h4>Cluster board</h4><div id="clusterBoard" class="compact-list"></div></article>
<article><h4>Suggestion inbox</h4><div id="suggestionInbox" class="compact-list"></div></article>
<article><h4>Constraint / event log</h4><div id="stateLog" class="compact-list"></div></article>
</div>
<pre id="clusterExplanation" class="explanation empty">Select a cluster and click Explain.</pre>
</details>
<details class="drawer-advanced">
<summary>Raw tables</summary>
<div class="result-columns">
<section><h3>Representative samples</h3><div class="table-wrap"><table id="samplesTable"><thead><tr><th>Audition</th><th>Sample</th><th>Class</th><th>Hits</th><th>Score</th><th>Duration</th><th>First hit</th><th>File</th></tr></thead><tbody></tbody></table></div></section>
<section><h3>Detected hit review</h3><div class="table-wrap hit-table-wrap"><table id="hitsTable"><thead><tr><th>Audition</th><th>#</th><th>Label</th><th>Cluster</th><th>Confidence</th><th>Flags</th><th>Onset</th><th>Duration</th><th>Energy</th><th>File</th></tr></thead><tbody></tbody></table></div></section>
</div>
</details>
</section>
</div>
<script type="module" src="/web/app.js"></script>
</body>
</html>