169 lines
13 KiB
HTML
169 lines
13 KiB
HTML
<!doctype html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<meta name="description" content="192채널 WebGPU acoustic camera simulator" />
|
||
<title>Acoustic Camera · micarrsim</title>
|
||
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
|
||
<link rel="stylesheet" href="/apps/acoustic-camera/styles.css" />
|
||
</head>
|
||
<body>
|
||
<div class="app-shell camera-shell">
|
||
<header class="app-header camera-header">
|
||
<a class="app-brand" href="/"><strong>micarrsim</strong><span>Acoustic Camera</span></a>
|
||
<div class="header-status">
|
||
<span class="status-chip status-chip--neutral" data-runtime-status>런타임 확인 중…</span>
|
||
<span class="run-status" id="run-status">기본 fixture 준비 중…</span>
|
||
</div>
|
||
<div class="header-actions">
|
||
<button class="button button--ghost is-hidden" id="cancel-button" type="button">계산 취소</button>
|
||
<button class="button button--ghost" id="export-result-button" type="button" disabled>Result JSON</button>
|
||
<button class="button button--primary" id="run-button" type="button" disabled>▶ 기본 예제 실행</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="camera-layout">
|
||
<section class="scene-workspace" aria-label="Acoustic image workspace">
|
||
<div class="workspace-heading">
|
||
<div>
|
||
<p class="eyebrow">Ground truth + acoustic overlay</p>
|
||
<h1 id="workspace-title">음향 장면</h1>
|
||
<p class="workspace-meta" id="scene-meta">장면을 불러오고 있습니다.</p>
|
||
</div>
|
||
<div class="workspace-import">
|
||
<div class="workspace-tools" aria-label="입력 파일">
|
||
<label class="button button--ghost file-button" title="1. Scene JSON을 먼저 선택하세요.">Scene<input id="scene-input" type="file" accept="application/json,.json" /></label>
|
||
<label class="button button--ghost file-button" title="2. 사용자 배열이 있을 때 Array JSON을 선택하세요.">Array<input id="array-input" type="file" accept="application/json,.json" /></label>
|
||
<label class="button button--ghost file-button" title="3. 현재 Scene과 Array를 참조하는 Experiment JSON을 선택하세요.">Experiment<input id="experiment-input" type="file" accept="application/json,.json" /></label>
|
||
<label class="button button--ghost file-button" title="4. Scene에 사용한 원본 파일을 다시 선택하세요.">Assets<input id="asset-input" type="file" accept="audio/*,image/*,.wav,.flac,.mp3,.ogg,.m4a,.png,.jpg,.jpeg,.webp,.svg" multiple /></label>
|
||
</div>
|
||
<p class="import-order">권장 순서 · Scene → Array → Experiment → Assets</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="scene-canvas-card">
|
||
<canvas id="scene-canvas" aria-label="Ground truth와 주파수별 acoustic image"></canvas>
|
||
<div class="canvas-message" id="canvas-message"><strong>기본 synthetic scene이 준비됩니다.</strong><span>실행하면 주파수별 acoustic image가 이 평면에 겹쳐집니다.</span></div>
|
||
<div class="canvas-badge" id="canvas-badge">결과 없음</div>
|
||
</div>
|
||
|
||
<div class="overlay-toolbar">
|
||
<div class="band-legend" id="band-legend" aria-label="표시 주파수 대역"></div>
|
||
<label class="range-field"><span>Overlay</span><input id="overlay-opacity" type="range" min="0" max="1" step="0.05" value="0.72" /></label>
|
||
<label class="check-field" title="점선은 1× RMS, 실선 영역은 2× RMS 빔 분포입니다. 확률 또는 신뢰구간은 아닙니다."><input id="show-beam-spread" type="checkbox" checked /><span>빔 분포</span></label>
|
||
<label class="check-field"><input id="show-truth" type="checkbox" checked /><span>Ground truth</span></label>
|
||
</div>
|
||
|
||
<section class="live-panel" aria-labelledby="live-title">
|
||
<div class="live-heading">
|
||
<div>
|
||
<p class="eyebrow">Moving-source timeline</p>
|
||
<h2 id="live-title">이동 음원 라이브</h2>
|
||
<p id="live-status" aria-live="polite">trajectory가 있는 Scene을 불러오거나 이동 예제를 여세요.</p>
|
||
</div>
|
||
<div class="live-actions">
|
||
<button class="button button--ghost" id="load-moving-example-button" type="button">이동 예제</button>
|
||
<button class="button button--primary" id="prepare-live-button" type="button" disabled>라이브 준비</button>
|
||
<button class="button button--ghost" id="live-play-button" type="button" disabled>▶ 재생</button>
|
||
</div>
|
||
</div>
|
||
<div class="live-controls">
|
||
<label class="live-scrubber"><span id="live-time-label">0.00 / 0.00 s</span><input id="live-time" type="range" min="0" max="0" step="0.001" value="0" disabled /></label>
|
||
<label class="compact-select"><span>Map rate</span><select id="live-map-rate"><option value="2" selected>2 Hz</option><option value="5">5 Hz</option><option value="8">8 Hz</option></select></label>
|
||
<label class="compact-select"><span>속도</span><select id="live-speed"><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="2">2×</option></select></label>
|
||
</div>
|
||
<p class="live-note">짧은 분석창의 중앙 위치를 사용하는 준정적 미리보기입니다. Heatmap은 계산된 frame 사이를 보간하고 위치 표시는 연속 이동합니다. 시뮬레이션 장면 재생이며 실제 마이크 스트림 입력은 아닙니다.</p>
|
||
</section>
|
||
|
||
<section class="result-section" aria-live="polite">
|
||
<div class="section-heading"><div><p class="eyebrow">Evaluation</p><h2>실행 결과</h2></div><span class="result-chip" id="result-badge">아직 실행하지 않음</span></div>
|
||
<div class="empty-result" id="empty-result">시뮬레이션을 실행하면 위치 오차, energy concentration, sidelobe와 timing을 표시합니다.</div>
|
||
<div class="metric-grid is-hidden" id="metric-grid"></div>
|
||
</section>
|
||
|
||
<section class="history-section">
|
||
<div class="section-heading"><div><p class="eyebrow">Same-scene comparison</p><h2>배열 비교 기록</h2></div><div class="history-actions"><button class="text-button" id="export-history-button" type="button" disabled>전체 기록 JSON</button><button class="text-button" id="clear-history-button" type="button">기록 비우기</button></div></div>
|
||
<p class="section-copy">비교 배열 또는 Array JSON을 바꿔 다시 실행하면 같은 scene·scan·STFT 결과를 이 세션에서 나란히 비교합니다.</p>
|
||
<div class="history-list" id="history-list"></div>
|
||
</section>
|
||
</section>
|
||
|
||
<aside class="control-panel" aria-label="Simulation parameters">
|
||
<section class="array-card">
|
||
<div class="array-heading"><div><p class="eyebrow">Microphone geometry</p><h2 id="array-title">배열</h2></div><strong id="microphone-count">— ch</strong></div>
|
||
<label class="array-preset-field"><span>비교 배열</span><select id="array-preset"><option value="rectangular-16x12">16×12 rectangular · 기준</option><option value="fermat-spiral-192">Fermat spiral · 비교</option><option value="custom">불러온 Array JSON</option></select></label>
|
||
<canvas id="array-canvas" aria-label="마이크 배열 평면도"></canvas>
|
||
<p id="array-meta">배열을 불러오고 있습니다.</p>
|
||
</section>
|
||
|
||
<section class="asset-card">
|
||
<div class="section-title"><span>INPUT</span><h2>장면과 assets</h2></div>
|
||
<div class="source-summary" id="source-summary"></div>
|
||
<div class="asset-list" id="asset-list"></div>
|
||
<a class="builder-link" href="/apps/dataset-builder/">음원 위치·ambient 편집은 Dataset Builder에서 →</a>
|
||
</section>
|
||
|
||
<section class="control-section">
|
||
<div class="section-title"><span>01</span><h2>실험</h2></div>
|
||
<label class="field field--wide"><span>이름</span><input id="experiment-name" type="text" maxlength="120" /></label>
|
||
<label class="field field--wide"><span>Experiment ID</span><input id="experiment-id" type="text" maxlength="120" /></label>
|
||
<div class="field-grid">
|
||
<label class="field"><span>Scene 거리 z (m)</span><input id="scene-distance" type="number" min="0.01" step="0.1" /></label>
|
||
<label class="field"><span>음속 (m/s)</span><input id="speed-of-sound" type="number" min="250" max="400" step="0.1" /></label>
|
||
<label class="field"><span>Seed</span><input id="scene-seed" type="number" min="0" max="4294967295" step="1" /></label>
|
||
<label class="field"><span>Backend</span><select id="backend"><option value="auto">Auto (GPU→CPU)</option><option value="webgpu">WebGPU 강제</option><option value="cpu">CPU 강제</option></select></label>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="control-section">
|
||
<div class="section-title"><span>02</span><h2>Scan grid</h2></div>
|
||
<label class="field field--wide"><span>Preset</span><select id="grid-preset"><option value="50">50×50 · 기준</option><option value="100">100×100 · 4× points</option><option value="200">200×200 · 16× points</option><option value="custom">직접 입력</option></select></label>
|
||
<div class="field-grid">
|
||
<label class="field"><span>Columns</span><input id="scan-columns" type="number" min="8" max="512" step="1" /></label>
|
||
<label class="field"><span>Rows</span><input id="scan-rows" type="number" min="8" max="512" step="1" /></label>
|
||
<label class="field"><span>x min (m)</span><input id="scan-x-min" type="number" step="0.05" /></label>
|
||
<label class="field"><span>x max (m)</span><input id="scan-x-max" type="number" step="0.05" /></label>
|
||
<label class="field"><span>y min (m)</span><input id="scan-y-min" type="number" step="0.05" /></label>
|
||
<label class="field"><span>y max (m)</span><input id="scan-y-max" type="number" step="0.05" /></label>
|
||
</div>
|
||
<p class="workload-summary" id="workload-summary">연산량 계산 중…</p>
|
||
</section>
|
||
|
||
<section class="control-section">
|
||
<div class="section-title"><span>03</span><h2>STFT와 표시</h2></div>
|
||
<div class="field-grid">
|
||
<label class="field"><span>FFT size</span><select id="fft-size"><option>128</option><option>256</option><option selected>512</option><option>1024</option><option>2048</option><option>4096</option></select></label>
|
||
<label class="field"><span>Hop size</span><input id="hop-size" type="number" min="1" max="4096" step="1" /></label>
|
||
<label class="field"><span>Map 정규화</span><select id="normalization"><option value="peak">Peak</option><option value="none">없음</option></select></label>
|
||
<label class="field"><span>Dynamic range (dB)</span><input id="dynamic-range" type="number" min="6" max="120" step="1" /></label>
|
||
<label class="field" title="최강 peak 대비 검출 임계값입니다. 낮추면 약한 음원을 더 찾지만 sidelobe 오검출도 늘 수 있습니다."><span>Peak 검출 임계값 (%)</span><input id="detection-threshold" type="number" min="1" max="100" step="1" /></label>
|
||
<label class="field field--wide"><span>후처리</span><select id="postprocess"><option value="none">없음 · raw DAS</option><option disabled>DAMAS · 후속 milestone</option><option disabled>CLEAN-SC · 후속 milestone</option></select></label>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="control-section">
|
||
<div class="section-title section-title--spread"><div><span>04</span><h2>주파수 대역</h2></div><button class="mini-button" id="add-band-button" type="button">+ 대역</button></div>
|
||
<div class="band-editor-list" id="band-list"></div>
|
||
</section>
|
||
|
||
<section class="output-card">
|
||
<div class="output-actions">
|
||
<button class="button button--ghost" id="export-scene-button" type="button">Scene JSON 저장</button>
|
||
<button class="button button--ghost" id="export-experiment-button" type="button">Experiment JSON 저장</button>
|
||
</div>
|
||
<p class="output-save-state" id="output-save-state">저장하지 않은 변경 없음</p>
|
||
</section>
|
||
|
||
<section class="validation-card" aria-live="polite">
|
||
<div class="validation-dot" id="validation-dot"></div>
|
||
<div><strong id="validation-title">검증 대기</strong><p id="validation-message">입력을 준비하고 있습니다.</p></div>
|
||
</section>
|
||
</aside>
|
||
</main>
|
||
</div>
|
||
<script type="module" src="/packages/ui/runtime-status.js"></script>
|
||
<script type="module" src="/apps/acoustic-camera/main.js"></script>
|
||
</body>
|
||
</html>
|