feat: build reproducible dataset editor

This commit is contained in:
Codex
2026-08-28 10:46:33 +09:00
parent 6a1197d60e
commit 08fcf578de
16 changed files with 2579 additions and 11 deletions
+143 -11
View File
@@ -6,27 +6,159 @@
<meta name="description" content="micarrsim 음향 장면 및 ground truth 생성기" />
<title>Dataset Builder · micarrsim</title>
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
<link rel="stylesheet" href="/apps/dataset-builder/styles.css" />
</head>
<body>
<div class="app-shell">
<header class="app-header">
<div class="app-shell builder-shell">
<header class="app-header builder-header">
<a class="app-brand" href="/">
<strong>micarrsim</strong>
<span>Dataset Builder</span>
</a>
<span class="status-chip status-chip--neutral" data-runtime-status>런타임 확인 중…</span>
<div class="header-actions">
<button class="button button--ghost" id="preview-button" type="button">▶ 미리 듣기</button>
<button class="button button--ghost" id="wav-button" type="button" title="공간정보 없이 모든 음원을 합친 mono 확인용 WAV입니다.">Preview WAV 저장</button>
<button class="button button--primary" id="export-button" type="button">Scene JSON 저장</button>
</div>
</header>
<main class="app-main">
<section class="empty-state">
<p class="eyebrow">Scene authoring</p>
<h1>음향 장면 만들기</h1>
<p>
이 화면에서 기기 이미지에 음원을 배치하고 ambient audio를 혼합해 scene manifest와
ground truth를 만들게 됩니다. 공통 데이터 계약을 확정한 뒤 편집기를 연결합니다.
</p>
<main class="builder-layout">
<section class="scene-workspace" aria-label="장면 편집 화면">
<div class="workspace-heading">
<div>
<p class="eyebrow">Ground-truth canvas</p>
<h1 id="workspace-title">음향 장면</h1>
</div>
<div class="workspace-tools">
<label class="button button--ghost file-button">
배경 이미지
<input id="image-input" type="file" accept="image/*" />
</label>
<button class="button button--danger is-hidden" id="remove-image-button" type="button">이미지 해제</button>
<label class="button button--ghost file-button">
오디오 추가
<input id="audio-input" type="file" accept="audio/*,.wav,.flac,.mp3,.ogg,.m4a" multiple />
</label>
<label class="button button--ghost file-button">
Scene 불러오기
<input id="scene-input" type="file" accept="application/json,.json" />
</label>
</div>
</div>
<div class="canvas-card">
<canvas id="scene-canvas" aria-label="이미지 평면과 음원 ground truth"></canvas>
<div class="canvas-empty" id="canvas-empty">
<strong>이미지를 추가하거나 grid를 그대로 사용하세요.</strong>
<span>선택한 음원을 옮기려면 canvas를 클릭하세요.</span>
</div>
<div class="canvas-badge" id="canvas-badge">x −0.60…0.60 m · y −0.45…0.45 m</div>
</div>
<div class="source-strip-heading">
<div>
<h2>음원 ground truth</h2>
<p>카드를 선택한 뒤 canvas를 클릭해 위치를 지정합니다.</p>
</div>
<button class="button button--primary" id="add-source-button" type="button">+ 음원 추가</button>
</div>
<div class="source-strip" id="source-list" aria-label="음원 목록"></div>
<section class="asset-shelf">
<div>
<h2>로컬 assets</h2>
<p>원본 파일은 업로드되지 않습니다. Camera에서 JSON과 함께 같은 파일을 선택하세요.</p>
</div>
<div class="asset-list" id="asset-list"></div>
</section>
</section>
<aside class="control-panel" aria-label="장면 파라미터">
<div class="runtime-line">
<span class="status-chip status-chip--neutral" data-runtime-status>런타임 확인 중…</span>
<span class="save-state" id="save-state">기본 장면</span>
</div>
<section class="control-section">
<div class="section-title"><span>01</span><h2>장면</h2></div>
<label class="field field--wide">
<span>이름</span>
<input id="scene-name" type="text" maxlength="120" />
</label>
<label class="field field--wide">
<span>Scene ID</span>
<input id="scene-id" type="text" maxlength="120" pattern="[a-z0-9][a-z0-9._-]*" />
</label>
<div class="field-grid">
<label class="field"><span>길이 (s)</span><input id="scene-duration" type="number" min="0.05" max="300" step="0.05" /></label>
<label class="field"><span>Sample rate</span><input id="sample-rate" type="number" min="8000" max="192000" step="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>Mix 정규화</span><select id="mix-normalization"><option value="none">없음</option><option value="peak">Peak</option></select></label>
<label class="field"><span>Target peak</span><input id="target-peak" type="number" min="0.01" max="1" step="0.01" /></label>
</div>
</section>
<section class="control-section">
<div class="section-title"><span>02</span><h2>표시 평면</h2></div>
<div class="field-grid field-grid--compact">
<label class="field"><span>x min</span><input id="plane-x-min" type="number" step="0.05" /></label>
<label class="field"><span>x max</span><input id="plane-x-max" type="number" step="0.05" /></label>
<label class="field"><span>y min</span><input id="plane-y-min" type="number" step="0.05" /></label>
<label class="field"><span>y max</span><input id="plane-y-max" type="number" step="0.05" /></label>
<label class="field"><span>plane z (m)</span><input id="plane-z" type="number" min="0.01" step="0.1" /></label>
</div>
<p class="section-note">x/y는 편집 화면 범위이며, 배경 이미지가 있을 때 image plane으로 JSON에 저장됩니다.</p>
</section>
<section class="control-section" id="source-editor-section">
<div class="section-title"><span>03</span><h2>선택 음원</h2></div>
<div id="no-source-message" class="inline-note">음원을 선택하세요.</div>
<div id="source-fields">
<label class="field field--wide"><span>이름</span><input id="source-name" type="text" maxlength="120" /></label>
<div class="field-grid field-grid--three">
<label class="field"><span>x (m)</span><input id="source-x" type="number" step="0.01" /></label>
<label class="field"><span>y (m)</span><input id="source-y" type="number" step="0.01" /></label>
<label class="field"><span>거리 z (m)</span><input id="source-z" type="number" min="0.01" step="0.1" /></label>
</div>
<div class="field-grid field-grid--three">
<label class="field"><span>Gain (dB)</span><input id="source-gain" type="number" min="-120" max="24" step="1" /></label>
<label class="field"><span>시작 (s)</span><input id="source-start" type="number" min="0" step="0.01" /></label>
<label class="field"><span>길이 (s)</span><input id="source-duration" type="number" min="0.01" step="0.01" /></label>
</div>
<label class="field field--wide"><span>신호 종류</span><select id="source-type"><option value="tone">Tone</option><option value="noise">Noise</option><option value="asset">Audio asset</option></select></label>
<div id="tone-fields"><label class="field field--wide"><span>주파수 (Hz)</span><input id="tone-frequency" type="number" min="1" step="1" /></label></div>
<div id="noise-fields"><label class="field field--wide"><span>Noise color</span><select id="noise-color"><option value="white">White</option><option value="pink">Pink</option></select></label></div>
<div id="asset-fields">
<label class="field field--wide"><span>Audio asset</span><select id="source-asset"></select></label>
<div class="field-grid">
<label class="field"><span>Channel</span><input id="source-channel" type="number" min="0" max="255" step="1" /></label>
<label class="check-field"><input id="source-loop" type="checkbox" /><span>짧은 파일 반복</span></label>
</div>
</div>
<button class="button button--danger button--full" id="remove-source-button" type="button">선택 음원 삭제</button>
</div>
</section>
<section class="control-section">
<div class="section-title"><span>04</span><h2>Ambient</h2></div>
<label class="field field--wide"><span>종류</span><select id="ambient-type"><option value="none">없음</option><option value="white">White noise</option><option value="pink">Pink noise</option><option value="asset">Audio asset</option></select></label>
<label class="field field--wide" id="ambient-asset-field"><span>Audio asset</span><select id="ambient-asset"></select></label>
<div class="field-grid" id="ambient-asset-options">
<label class="field"><span>Channel</span><input id="ambient-channel" type="number" min="0" max="255" step="1" /></label>
<label class="check-field"><input id="ambient-loop" type="checkbox" /><span>짧은 파일 반복</span></label>
</div>
<label class="field field--wide"><span>Gain (dB)</span><input id="ambient-gain" type="number" min="-120" max="24" step="1" /></label>
</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/dataset-builder/main.js"></script>
</body>
</html>