Files
micarrsim/apps/array-builder/index.html
T

284 lines
15 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="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>