feat: add interactive microphone array builder
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
<!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="마이크 위치를 직접 편집하고 격자·선형·원형·나선·랜덤 배열을 생성하는 micarrsim Array Builder"
|
||||
/>
|
||||
<title>Array Builder · micarrsim</title>
|
||||
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
|
||||
<link rel="stylesheet" href="/apps/array-builder/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell array-builder-shell">
|
||||
<header class="app-header array-builder-header">
|
||||
<a class="app-brand" href="/">
|
||||
<strong>micarrsim</strong>
|
||||
<span>Array Builder</span>
|
||||
</a>
|
||||
|
||||
<nav class="array-workflow" aria-label="마이크 어레이 제작 순서">
|
||||
<ol>
|
||||
<li><span>1</span>크기·형상</li>
|
||||
<li><span>2</span>생성·편집</li>
|
||||
<li><span>3</span>검증</li>
|
||||
<li><span>4</span>저장</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="header-actions">
|
||||
<label class="button button--ghost file-button">
|
||||
JSON 열기
|
||||
<input id="array-input" type="file" accept="application/json,.json" />
|
||||
</label>
|
||||
<a class="button button--ghost" href="/apps/acoustic-camera/">Camera 열기</a>
|
||||
<button class="button button--primary" id="export-button" type="button">Array JSON 저장</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="array-builder-layout" id="array-builder-layout" data-split-layout>
|
||||
<aside
|
||||
class="array-controls"
|
||||
id="array-builder-controls"
|
||||
data-split-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" role="status">기본 배열</span>
|
||||
</div>
|
||||
|
||||
<section class="validation-card" aria-live="polite" aria-atomic="true">
|
||||
<span class="validation-dot" id="validation-dot" aria-hidden="true"></span>
|
||||
<div>
|
||||
<strong id="validation-title">검증 대기</strong>
|
||||
<p id="validation-message">마이크 배열을 준비하고 있습니다.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="control-section">
|
||||
<div class="section-title"><span>01</span><h2>배열 정보</h2></div>
|
||||
<label class="field field--wide">
|
||||
<span>이름</span>
|
||||
<input id="array-name" type="text" maxlength="120" autocomplete="off" />
|
||||
</label>
|
||||
<label class="field field--wide">
|
||||
<span>Array ID</span>
|
||||
<input
|
||||
id="array-id"
|
||||
type="text"
|
||||
maxlength="120"
|
||||
pattern="[a-z0-9][a-z0-9._-]*"
|
||||
title="영문 소문자나 숫자로 시작하고 소문자, 숫자, 점, 밑줄, 하이픈만 사용할 수 있습니다."
|
||||
autocomplete="off"
|
||||
/>
|
||||
</label>
|
||||
<label class="field field--wide">
|
||||
<span>설명 <small>선택 사항</small></span>
|
||||
<textarea id="array-description" rows="2" maxlength="1000"></textarea>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="control-section control-section--primary">
|
||||
<div class="section-title"><span>02</span><h2>크기와 형상</h2></div>
|
||||
<div class="field-grid">
|
||||
<label class="field">
|
||||
<span>너비 (m)</span>
|
||||
<input id="aperture-width" type="number" min="0.001" max="20" step="0.01" value="0.45" inputmode="decimal" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>높이 (m)</span>
|
||||
<input id="aperture-height" type="number" min="0.001" max="20" step="0.01" value="0.45" inputmode="decimal" />
|
||||
</label>
|
||||
</div>
|
||||
<label class="field field--wide">
|
||||
<span>생성 경계</span>
|
||||
<select id="aperture-shape">
|
||||
<option value="rectangle">사각형</option>
|
||||
<option value="ellipse">타원</option>
|
||||
</select>
|
||||
</label>
|
||||
<p class="section-note">크기를 바꿔도 기존 마이크 좌표는 자동으로 늘어나거나 줄어들지 않습니다.</p>
|
||||
<div class="button-grid button-grid--two">
|
||||
<button class="button button--ghost" id="center-microphones-button" type="button">좌표 중앙 정렬</button>
|
||||
<button class="button button--ghost" id="fit-aperture-button" type="button">좌표에 크기 맞춤</button>
|
||||
<button class="button button--ghost button-grid__wide" id="clamp-microphones-button" type="button">경계 밖 마이크를 안으로 이동</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="control-section">
|
||||
<div class="section-title"><span>03</span><h2>프리셋 생성</h2></div>
|
||||
<label class="field field--wide">
|
||||
<span>배치 방식</span>
|
||||
<select id="preset-type">
|
||||
<option value="grid">격자</option>
|
||||
<option value="linear">선형</option>
|
||||
<option value="ring">원형</option>
|
||||
<option value="spiral">나선</option>
|
||||
<option value="random">균일 랜덤</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="preset-grid">
|
||||
<label class="field" id="preset-count-field">
|
||||
<span>마이크 수</span>
|
||||
<input id="preset-count" type="number" min="1" max="1024" step="1" value="16" inputmode="numeric" />
|
||||
</label>
|
||||
<label class="field" id="preset-axis-field">
|
||||
<span>선형 축</span>
|
||||
<select id="preset-axis">
|
||||
<option value="x">가로 (x)</option>
|
||||
<option value="y">세로 (y)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field" id="preset-columns-field">
|
||||
<span>열</span>
|
||||
<input id="preset-columns" type="number" min="1" max="1024" step="1" value="4" inputmode="numeric" />
|
||||
</label>
|
||||
<label class="field" id="preset-rows-field">
|
||||
<span>행</span>
|
||||
<input id="preset-rows" type="number" min="1" max="1024" step="1" value="3" inputmode="numeric" />
|
||||
</label>
|
||||
<label class="field" id="preset-seed-field">
|
||||
<span>Random seed</span>
|
||||
<input id="preset-seed" type="number" min="0" max="4294967295" step="1" value="1" inputmode="numeric" />
|
||||
</label>
|
||||
<label class="field" id="preset-minimum-spacing-field">
|
||||
<span>최소 간격 (m)</span>
|
||||
<input id="preset-minimum-spacing" type="number" min="0" max="20" step="0.001" value="0.015" inputmode="decimal" />
|
||||
</label>
|
||||
<label class="field preset-grid__wide" id="preset-edge-inset-field">
|
||||
<span>경계 여백 (m)</span>
|
||||
<input id="preset-edge-inset" type="number" min="0" max="10" step="0.001" value="0.01" inputmode="decimal" />
|
||||
</label>
|
||||
</div>
|
||||
<p class="section-note" id="preset-hint">격자는 아래에서 위로, 각 행은 왼쪽에서 오른쪽 순서로 채널을 만듭니다.</p>
|
||||
<button class="button button--primary button--full" id="generate-preset-button" type="button">현재 배열을 프리셋으로 교체</button>
|
||||
</section>
|
||||
|
||||
<section class="control-section microphone-section">
|
||||
<div class="section-title section-title--split">
|
||||
<div><span>04</span><h2>마이크 편집</h2></div>
|
||||
<button class="mini-button" id="add-microphone-button" type="button">+ 마이크</button>
|
||||
</div>
|
||||
|
||||
<label class="field field--wide search-field">
|
||||
<span>목록 검색</span>
|
||||
<input id="microphone-search" type="search" placeholder="ID로 찾기" autocomplete="off" />
|
||||
</label>
|
||||
<div class="microphone-list" id="microphone-list" role="listbox" aria-label="마이크 채널 목록">
|
||||
<p class="list-empty">마이크 목록을 준비하고 있습니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="selected-microphone-editor">
|
||||
<p class="selection-empty" id="microphone-selection-empty">canvas나 목록에서 마이크를 선택하세요.</p>
|
||||
<div id="microphone-fields">
|
||||
<label class="field field--wide">
|
||||
<span>마이크 ID</span>
|
||||
<input id="microphone-id" type="text" maxlength="64" autocomplete="off" disabled />
|
||||
</label>
|
||||
<div class="field-grid field-grid--three">
|
||||
<label class="field"><span>x (m)</span><input id="microphone-x" type="number" step="0.001" inputmode="decimal" disabled /></label>
|
||||
<label class="field"><span>y (m)</span><input id="microphone-y" type="number" step="0.001" inputmode="decimal" disabled /></label>
|
||||
<label class="field"><span>z (m)</span><input id="microphone-z" type="number" step="0.001" inputmode="decimal" disabled /></label>
|
||||
</div>
|
||||
|
||||
<details class="inline-details">
|
||||
<summary>보정값</summary>
|
||||
<div class="field-grid calibration-fields">
|
||||
<label class="field"><span>Sensitivity (dB)</span><input id="microphone-sensitivity" type="number" min="-120" max="120" step="0.1" inputmode="decimal" disabled /></label>
|
||||
<label class="field"><span>Phase (deg)</span><input id="microphone-phase" type="number" min="-180" max="180" step="0.1" inputmode="decimal" disabled /></label>
|
||||
</div>
|
||||
<p class="section-note" id="frequency-response-summary">주파수 응답 없음</p>
|
||||
</details>
|
||||
|
||||
<div class="button-grid reorder-actions">
|
||||
<button class="button button--ghost" id="move-microphone-up-button" type="button" disabled>채널 앞으로</button>
|
||||
<button class="button button--ghost" id="move-microphone-down-button" type="button" disabled>채널 뒤로</button>
|
||||
<button class="button button--danger button-grid__wide" id="remove-microphone-button" type="button" disabled>선택 마이크 삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<div
|
||||
class="split-handle"
|
||||
id="array-builder-split-handle"
|
||||
role="separator"
|
||||
tabindex="0"
|
||||
aria-label="배열 설정 패널 너비 조절"
|
||||
aria-controls="array-builder-controls"
|
||||
aria-orientation="vertical"
|
||||
data-split-resizer
|
||||
data-storage-key="micarrsim.array-builder.control-panel-width"
|
||||
title="드래그하거나 방향키로 너비 조절 · Enter/더블클릭으로 초기화"
|
||||
></div>
|
||||
|
||||
<section class="array-workspace" id="array-builder-workspace" aria-label="마이크 배열 편집 화면">
|
||||
<div class="workspace-heading">
|
||||
<div>
|
||||
<p class="eyebrow">마이크 배치</p>
|
||||
<h1>Array 평면 편집</h1>
|
||||
</div>
|
||||
<p class="workspace-summary" id="workspace-summary">0.45 × 0.45 m · 12 channels</p>
|
||||
</div>
|
||||
|
||||
<div class="workspace-toolbar" role="toolbar" aria-label="배열 편집 도구">
|
||||
<div class="toolbar-group" aria-label="편집 도구">
|
||||
<button class="tool-button is-active" id="select-tool-button" type="button" aria-pressed="true" aria-keyshortcuts="V">선택 <kbd>V</kbd></button>
|
||||
<button class="tool-button" id="add-tool-button" type="button" aria-pressed="false" aria-keyshortcuts="A">추가 <kbd>A</kbd></button>
|
||||
</div>
|
||||
<div class="toolbar-group" aria-label="실행 취소와 다시 실행">
|
||||
<button class="tool-button" id="undo-button" type="button" aria-keyshortcuts="Control+Z" disabled>실행 취소</button>
|
||||
<button class="tool-button" id="redo-button" type="button" aria-keyshortcuts="Control+Shift+Z" disabled>다시 실행</button>
|
||||
</div>
|
||||
<div class="toolbar-group zoom-tools" aria-label="화면 확대와 맞춤">
|
||||
<button class="icon-button" id="zoom-out-button" type="button" aria-label="축소" title="축소">−</button>
|
||||
<button class="tool-button" id="fit-view-button" type="button">화면 맞춤</button>
|
||||
<button class="icon-button" id="zoom-in-button" type="button" aria-label="확대" title="확대">+</button>
|
||||
</div>
|
||||
<div class="snap-controls">
|
||||
<label class="check-field"><input id="snap-enabled" type="checkbox" checked /><span>격자 스냅</span></label>
|
||||
<label class="compact-field"><span>간격</span><input id="snap-step" type="number" min="0.1" max="1000" step="0.1" value="5" inputmode="decimal" /><small>mm</small></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="array-canvas-card">
|
||||
<canvas
|
||||
id="array-canvas"
|
||||
tabindex="0"
|
||||
aria-label="마이크 어레이 평면 편집 canvas"
|
||||
aria-describedby="canvas-instructions"
|
||||
></canvas>
|
||||
<div class="canvas-instructions" id="canvas-instructions">
|
||||
<strong>마이크를 클릭하거나 드래그해 위치를 편집하세요.</strong>
|
||||
<span>추가 도구에서는 빈 공간을 클릭해 새 채널을 만듭니다.</span>
|
||||
</div>
|
||||
<div class="selection-status" id="selection-status" role="status" aria-live="polite">선택된 마이크 없음</div>
|
||||
</div>
|
||||
|
||||
<section class="array-statistics" aria-label="배열 요약">
|
||||
<article class="metric-card"><span>채널</span><strong id="microphone-count">12</strong><small>microphones</small></article>
|
||||
<article class="metric-card"><span>최소 간격</span><strong id="minimum-spacing">—</strong><small>nearest pair</small></article>
|
||||
<article class="metric-card"><span>무게중심</span><strong id="array-centroid">—</strong><small>x, y, z</small></article>
|
||||
<article class="metric-card"><span>보정 채널</span><strong id="calibration-count">0</strong><small>sensitivity / phase / response</small></article>
|
||||
</section>
|
||||
|
||||
<details class="shortcut-help">
|
||||
<summary>빠른 편집 키</summary>
|
||||
<div>
|
||||
<p><kbd>V</kbd> 선택 · <kbd>A</kbd> 추가 · <kbd>Delete</kbd> 선택 삭제</p>
|
||||
<p><kbd>← ↑ ↓ →</kbd> 위치 미세 조정 · <kbd>Shift</kbd>를 함께 누르면 10배 이동</p>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/packages/ui/runtime-status.js"></script>
|
||||
<script type="module" src="/apps/array-builder/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user