feat: add varied microphone array presets
This commit is contained in:
@@ -201,10 +201,13 @@ noise source는 스펙트럼을 미리 알 수 없으므로 모든 band의 groun
|
|||||||
|
|
||||||
## 배열 예제
|
## 배열 예제
|
||||||
|
|
||||||
- `assets/array-geometries/rectangular-16x12.array.json`: 0.45 m 정사각 aperture의 16×12 기준 배열
|
- **채널 수 비교:** 같은 0.45 m aperture에서 4·12·48·192·384채널 rectangular 배열
|
||||||
- `assets/array-geometries/fermat-spiral-192.array.json`: 0.45 m 원형 aperture의 192채널 Fermat spiral
|
- **물리 크기 비교:** 같은 192채널 16×12 topology에서 0.12·0.45·1.20 m 배열
|
||||||
|
- **배치 비교:** 같은 192채널 규모의 rectangular baseline과 0.45 m Fermat spiral
|
||||||
|
|
||||||
Camera의 **비교 배열** 선택으로 두 fixture를 같은 scene과 experiment에서 연속 실행할 수 있습니다.
|
Camera의 **배열 선택**에서 8개 fixture를 바로 불러와 같은 Scene/Experiment로 연속 실행할 수 있습니다.
|
||||||
|
저채널·대형 배열의 grating lobe나 compact 배열의 넓은 main lobe는 배치 trade-off를 관찰하기 위한
|
||||||
|
의도된 예제 특성입니다.
|
||||||
상세한 생성법은 [`assets/array-geometries/README.md`](assets/array-geometries/README.md)를 참고하세요.
|
상세한 생성법은 [`assets/array-geometries/README.md`](assets/array-geometries/README.md)를 참고하세요.
|
||||||
|
|
||||||
## 테스트와 benchmark
|
## 테스트와 benchmark
|
||||||
@@ -241,7 +244,7 @@ test.cmd
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
apps/ Dataset Builder와 Acoustic Camera UI
|
apps/ Dataset Builder와 Acoustic Camera UI
|
||||||
assets/ 192채널 배열과 synthetic Scene/Experiment fixture
|
assets/ 마이크 배열과 synthetic Scene/Experiment fixture
|
||||||
benchmarks/ 현재 브라우저에서 실행하는 WebGPU scaling 측정
|
benchmarks/ 현재 브라우저에서 실행하는 WebGPU scaling 측정
|
||||||
docs/ 사용자 안내, 데이터 계약, 실행 계획과 기준 실측
|
docs/ 사용자 안내, 데이터 계약, 실행 계획과 기준 실측
|
||||||
packages/ 음향 수치 모델, 계약 검증, UI, WebGPU kernel
|
packages/ 음향 수치 모델, 계약 검증, UI, WebGPU kernel
|
||||||
|
|||||||
@@ -113,8 +113,9 @@ curve는 합성 microphone channel에 항상 적용됩니다. **마이크 주파
|
|||||||
- **Ground truth**를 켜면 source 위치와 검출 peak를 비교할 수 있습니다.
|
- **Ground truth**를 켜면 source 위치와 검출 peak를 비교할 수 있습니다.
|
||||||
- **빔 분포**는 peak 주변 half-power lobe의 power 가중 공분산을 1× RMS 점선과 2× RMS 영역으로
|
- **빔 분포**는 peak 주변 half-power lobe의 power 가중 공분산을 1× RMS 점선과 2× RMS 영역으로
|
||||||
표시합니다. 위치 확률, Gaussian 신뢰구간 또는 측정 불확실성 보정값은 아닙니다.
|
표시합니다. 위치 확률, Gaussian 신뢰구간 또는 측정 불확실성 보정값은 아닙니다.
|
||||||
- rectangular와 Fermat spiral preset을 같은 Scene/Experiment에서 연속 실행하면 **배열 비교 기록**에
|
- **배열 선택**에는 채널 수(4·12·48·192·384), aperture 크기(0.12·0.45·1.20 m), rectangular와
|
||||||
backend, timing과 평가 요약이 남습니다.
|
Fermat spiral 배치를 분리 비교하는 8개 preset이 있습니다. 같은 Scene/Experiment에서 연속 실행하면
|
||||||
|
**배열 비교 기록**에 backend, timing과 평가 요약이 남습니다.
|
||||||
- 입력을 바꾼 뒤 화면에 남은 결과는 이전 실행 snapshot이며 UI가 이전 설정 결과임을 표시합니다.
|
- 입력을 바꾼 뒤 화면에 남은 결과는 이전 실행 snapshot이며 UI가 이전 설정 결과임을 표시합니다.
|
||||||
- Camera에서 거리·음속·seed를 바꾼 Scene과 scan·STFT·band를 바꾼 Experiment는 우측의 각
|
- Camera에서 거리·음속·seed를 바꾼 Scene과 scan·STFT·band를 바꾼 Experiment는 우측의 각
|
||||||
**JSON 저장** 버튼으로 보존할 수 있습니다.
|
**JSON 저장** 버튼으로 보존할 수 있습니다.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="192채널 WebGPU acoustic camera simulator" />
|
<meta name="description" content="다중 채널 CPU·WebGPU acoustic camera simulator" />
|
||||||
<title>Acoustic Camera · micarrsim</title>
|
<title>Acoustic Camera · micarrsim</title>
|
||||||
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
|
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
|
||||||
<link rel="stylesheet" href="/apps/acoustic-camera/styles.css" />
|
<link rel="stylesheet" href="/apps/acoustic-camera/styles.css" />
|
||||||
@@ -52,7 +52,28 @@
|
|||||||
|
|
||||||
<div class="array-card">
|
<div class="array-card">
|
||||||
<div class="array-heading"><div><span class="compact-label">MIC ARRAY</span><h3 id="array-title">배열</h3></div><strong id="microphone-count">— ch</strong></div>
|
<div class="array-heading"><div><span class="compact-label">MIC ARRAY</span><h3 id="array-title">배열</h3></div><strong id="microphone-count">— ch</strong></div>
|
||||||
<label class="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>
|
<label class="field">
|
||||||
|
<span>배열 선택</span>
|
||||||
|
<select id="array-preset">
|
||||||
|
<optgroup label="기준 배열">
|
||||||
|
<option value="rectangular-16x12">16×12 rectangular · 192 ch / 0.45 m</option>
|
||||||
|
<option value="fermat-spiral-192">Fermat spiral · 192 ch / Ø 0.45 m</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="채널 수 비교 · 0.45 m">
|
||||||
|
<option value="rectangular-2x2-450mm">2×2 sparse · 4 ch</option>
|
||||||
|
<option value="rectangular-4x3-450mm">4×3 sparse · 12 ch</option>
|
||||||
|
<option value="rectangular-8x6-450mm">8×6 · 48 ch</option>
|
||||||
|
<option value="rectangular-24x16-450mm">24×16 dense · 384 ch</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="물리 크기 비교 · 192 ch">
|
||||||
|
<option value="rectangular-16x12-compact">16×12 compact · 0.12 m</option>
|
||||||
|
<option value="rectangular-16x12-large">16×12 large · 1.20 m</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="사용자 배열">
|
||||||
|
<option value="custom">불러온 Array JSON</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<details class="inline-details" id="array-details">
|
<details class="inline-details" id="array-details">
|
||||||
<summary>배열 미리보기와 좌표 정보</summary>
|
<summary>배열 미리보기와 좌표 정보</summary>
|
||||||
<canvas id="array-canvas" aria-label="마이크 배열 평면도"></canvas>
|
<canvas id="array-canvas" aria-label="마이크 배열 평면도"></canvas>
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ const MOVING_EXAMPLE_URLS = {
|
|||||||
const ARRAY_PRESET_URLS = new Map([
|
const ARRAY_PRESET_URLS = new Map([
|
||||||
["rectangular-16x12", "/assets/array-geometries/rectangular-16x12.array.json"],
|
["rectangular-16x12", "/assets/array-geometries/rectangular-16x12.array.json"],
|
||||||
["fermat-spiral-192", "/assets/array-geometries/fermat-spiral-192.array.json"],
|
["fermat-spiral-192", "/assets/array-geometries/fermat-spiral-192.array.json"],
|
||||||
|
["rectangular-2x2-450mm", "/assets/array-geometries/rectangular-2x2-450mm.array.json"],
|
||||||
|
["rectangular-4x3-450mm", "/assets/array-geometries/rectangular-4x3-450mm.array.json"],
|
||||||
|
["rectangular-8x6-450mm", "/assets/array-geometries/rectangular-8x6-450mm.array.json"],
|
||||||
|
["rectangular-24x16-450mm", "/assets/array-geometries/rectangular-24x16-450mm.array.json"],
|
||||||
|
["rectangular-16x12-compact", "/assets/array-geometries/rectangular-16x12-compact.array.json"],
|
||||||
|
["rectangular-16x12-large", "/assets/array-geometries/rectangular-16x12-large.array.json"],
|
||||||
]);
|
]);
|
||||||
const BAND_COLORS = ["#65f2bf", "#58c7ff", "#ff7f8f", "#ffc56e", "#b294ff", "#8fe388", "#ff9ee5", "#83a8ff"];
|
const BAND_COLORS = ["#65f2bf", "#58c7ff", "#ff7f8f", "#ffc56e", "#b294ff", "#8fe388", "#ff9ee5", "#83a8ff"];
|
||||||
const MAX_HISTORY_ITEMS = 24;
|
const MAX_HISTORY_ITEMS = 24;
|
||||||
|
|||||||
@@ -1,40 +1,46 @@
|
|||||||
# Array Geometries
|
# Array Geometries
|
||||||
|
|
||||||
Camera에서 즉시 사용하고 테스트에서 재현할 수 있는 계약 버전 `1.0.0`의 192채널 Array JSON
|
Acoustic Camera에서 즉시 선택하고 같은 Scene/Experiment로 비교할 수 있는 Array JSON 예제입니다.
|
||||||
fixture입니다. 좌표 단위는 m이고 모든 microphone은 배열 평면 `z = 0`에 있습니다. `+x`는 오른쪽,
|
모든 파일은 계약 버전 `1.0.0`을 따르며 좌표 단위는 m입니다. microphone은 배열 평면
|
||||||
`+y`는 위, `+z`는 scene 방향입니다.
|
`z = 0`에 있고, `+x`는 오른쪽, `+y`는 위, `+z`는 scene 방향입니다.
|
||||||
|
|
||||||
## 포함된 배열
|
## 비교용 preset
|
||||||
|
|
||||||
### `rectangular-16x12.array.json`
|
| 비교 축 | 파일 | 채널 | aperture | x × y pitch |
|
||||||
|
| --- | --- | ---: | ---: | ---: |
|
||||||
|
| 채널 수 · 최소 | `rectangular-2x2-450mm.array.json` | 4 | 0.45 × 0.45 m | 450 × 450 mm |
|
||||||
|
| 채널 수 · 적음 | `rectangular-4x3-450mm.array.json` | 12 | 0.45 × 0.45 m | 150 × 225 mm |
|
||||||
|
| 채널 수 · 중간 | `rectangular-8x6-450mm.array.json` | 48 | 0.45 × 0.45 m | 64.286 × 90 mm |
|
||||||
|
| 채널 수·크기 기준 | `rectangular-16x12.array.json` | 192 | 0.45 × 0.45 m | 30 × 40.909 mm |
|
||||||
|
| 채널 수 · 많음 | `rectangular-24x16-450mm.array.json` | 384 | 0.45 × 0.45 m | 19.565 × 30 mm |
|
||||||
|
| 물리 크기 · 소형 | `rectangular-16x12-compact.array.json` | 192 | 0.12 × 0.12 m | 8 × 10.909 mm |
|
||||||
|
| 물리 크기 · 대형 | `rectangular-16x12-large.array.json` | 192 | 1.20 × 1.20 m | 80 × 109.091 mm |
|
||||||
|
| 배치 패턴 | `fermat-spiral-192.array.json` | 192 | Ø 0.45 m | 비균일 |
|
||||||
|
|
||||||
- 16 columns × 12 rows = 192 microphones
|
Camera의 **배열 선택**은 비교 목적에 맞춰 preset을 그룹으로 나눕니다.
|
||||||
- 폭과 높이가 각각 0.45 m인 정사각 aperture
|
|
||||||
- x/y 양 끝을 포함해 균일한 간격으로 배치
|
|
||||||
- 기본 single-tone experiment와 CPU/WebGPU 기준 fixture가 참조하는 baseline
|
|
||||||
|
|
||||||
### `fermat-spiral-192.array.json`
|
- 채널 수 효과는 aperture가 모두 0.45 m인 4·12·48·192·384채널 rectangular 배열끼리 비교합니다.
|
||||||
|
- 물리 크기 효과는 topology와 채널 수가 같은 192채널 compact·baseline·large 배열끼리 비교합니다.
|
||||||
|
- 배치 패턴 효과는 192채널 0.45 m rectangular baseline과 Fermat spiral을 비교합니다.
|
||||||
|
|
||||||
- 192 microphones
|
이 preset들은 정확도를 보장하는 제품 사양이 아니라 trade-off를 관찰하기 위한 입력입니다. 채널 간격이
|
||||||
- 반지름 0.225 m, 지름 0.45 m인 원형 aperture
|
넓은 저채널·대형 배열에서 고주파 spatial aliasing과 grating lobe가 나타나거나, compact 배열에서
|
||||||
- golden angle과 면적 균등 반지름으로 만든 Fermat spiral
|
main lobe가 넓어지는 것은 예상 가능한 결과입니다. 384채널 배열은 같은 grid에서 계산량과 메모리 사용량도
|
||||||
- rectangular baseline과 sidelobe·집중도·위치 오차를 비교하기 위한 preset
|
가장 큽니다. 빠른 동작 확인은 4채널이나 12채널부터 시작하세요.
|
||||||
|
|
||||||
Acoustic Camera의 **비교 배열**에서 두 배열을 선택해 같은 Scene/Experiment 결과를 세션 기록에
|
|
||||||
나란히 추가할 수 있습니다.
|
|
||||||
|
|
||||||
## fixture 다시 생성하기
|
## fixture 다시 생성하기
|
||||||
|
|
||||||
저장소 최상위에서 다음 명령을 실행하면 두 JSON을 결정론적으로 다시 생성합니다.
|
저장소 최상위에서 다음 명령을 실행하면 위 8개 JSON을 결정론적으로 다시 생성합니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\generate-array-fixture.ps1
|
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\generate-array-fixture.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
이 스크립트는 기존 `rectangular-16x12.array.json`과 `fermat-spiral-192.array.json`을 덮어씁니다.
|
`ExecutionPolicy Bypass`는 해당 PowerShell 프로세스에만 적용됩니다. 생성 뒤
|
||||||
`ExecutionPolicy Bypass`는 이 PowerShell 프로세스에만 적용됩니다. 생성 뒤
|
`test.cmd`로 schema, 채널 수, 좌표 중복, 평면성, grid 행·열 수와 aperture 경계를 검증할 수
|
||||||
<http://127.0.0.1:4173/tests/>에서 schema와 192채널 배치 검증을 실행하세요.
|
있습니다.
|
||||||
|
|
||||||
사용자 배열은 [`../../schemas/array.schema.json`](../../schemas/array.schema.json) 구조를 따르고
|
사용자 배열은 [array schema](../../schemas/array.schema.json) 구조를 따르고 고유한 `id`,
|
||||||
고유한 `id`, aperture와 microphone별 `[x, y, z]`를 가져야 합니다. 선택적인 `sensitivityDb`와
|
aperture와 microphone별 `[x, y, z]`를 가져야 합니다. 선택적인 `sensitivityDb`와
|
||||||
`phaseOffsetDeg`는 합성 channel 편차와 beamforming 역 calibration에 사용됩니다.
|
`phaseOffsetDeg`는 합성 channel 편차와 beamforming 역 calibration에 사용됩니다. geometry
|
||||||
|
효과와 calibration 효과를 분리하기 위해 기본 preset에는 이 값을 넣지 않았습니다.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "1.0.0",
|
||||||
|
"kind": "micarrsim.array",
|
||||||
|
"id": "rectangular-2x2-450mm",
|
||||||
|
"name": "2x2 sparse rectangular",
|
||||||
|
"description": "4-channel uniform rectangular layout on the 0.45 m reference aperture",
|
||||||
|
"units": "m",
|
||||||
|
"coordinateSystem": {
|
||||||
|
"handedness": "right",
|
||||||
|
"xAxis": "right",
|
||||||
|
"yAxis": "up",
|
||||||
|
"zAxis": "toward-scene"
|
||||||
|
},
|
||||||
|
"aperture": {
|
||||||
|
"width": 0.45,
|
||||||
|
"height": 0.45
|
||||||
|
},
|
||||||
|
"microphones": [
|
||||||
|
{
|
||||||
|
"id": "mic-001",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-002",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-003",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-004",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "1.0.0",
|
||||||
|
"kind": "micarrsim.array",
|
||||||
|
"id": "rectangular-4x3-450mm",
|
||||||
|
"name": "4x3 sparse rectangular",
|
||||||
|
"description": "12-channel uniform rectangular layout on the 0.45 m reference aperture",
|
||||||
|
"units": "m",
|
||||||
|
"coordinateSystem": {
|
||||||
|
"handedness": "right",
|
||||||
|
"xAxis": "right",
|
||||||
|
"yAxis": "up",
|
||||||
|
"zAxis": "toward-scene"
|
||||||
|
},
|
||||||
|
"aperture": {
|
||||||
|
"width": 0.45,
|
||||||
|
"height": 0.45
|
||||||
|
},
|
||||||
|
"microphones": [
|
||||||
|
{
|
||||||
|
"id": "mic-001",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-002",
|
||||||
|
"position": [
|
||||||
|
-0.075,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-003",
|
||||||
|
"position": [
|
||||||
|
0.075,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-004",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-005",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-006",
|
||||||
|
"position": [
|
||||||
|
-0.075,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-007",
|
||||||
|
"position": [
|
||||||
|
0.075,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-008",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-009",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-010",
|
||||||
|
"position": [
|
||||||
|
-0.075,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-011",
|
||||||
|
"position": [
|
||||||
|
0.075,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-012",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "1.0.0",
|
||||||
|
"kind": "micarrsim.array",
|
||||||
|
"id": "rectangular-8x6-450mm",
|
||||||
|
"name": "8x6 rectangular",
|
||||||
|
"description": "48-channel uniform rectangular layout on the 0.45 m reference aperture",
|
||||||
|
"units": "m",
|
||||||
|
"coordinateSystem": {
|
||||||
|
"handedness": "right",
|
||||||
|
"xAxis": "right",
|
||||||
|
"yAxis": "up",
|
||||||
|
"zAxis": "toward-scene"
|
||||||
|
},
|
||||||
|
"aperture": {
|
||||||
|
"width": 0.45,
|
||||||
|
"height": 0.45
|
||||||
|
},
|
||||||
|
"microphones": [
|
||||||
|
{
|
||||||
|
"id": "mic-001",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-002",
|
||||||
|
"position": [
|
||||||
|
-0.160714,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-003",
|
||||||
|
"position": [
|
||||||
|
-0.096429,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-004",
|
||||||
|
"position": [
|
||||||
|
-0.032143,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-005",
|
||||||
|
"position": [
|
||||||
|
0.032143,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-006",
|
||||||
|
"position": [
|
||||||
|
0.096429,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-007",
|
||||||
|
"position": [
|
||||||
|
0.160714,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-008",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
-0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-009",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-010",
|
||||||
|
"position": [
|
||||||
|
-0.160714,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-011",
|
||||||
|
"position": [
|
||||||
|
-0.096429,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-012",
|
||||||
|
"position": [
|
||||||
|
-0.032143,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-013",
|
||||||
|
"position": [
|
||||||
|
0.032143,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-014",
|
||||||
|
"position": [
|
||||||
|
0.096429,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-015",
|
||||||
|
"position": [
|
||||||
|
0.160714,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-016",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
-0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-017",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-018",
|
||||||
|
"position": [
|
||||||
|
-0.160714,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-019",
|
||||||
|
"position": [
|
||||||
|
-0.096429,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-020",
|
||||||
|
"position": [
|
||||||
|
-0.032143,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-021",
|
||||||
|
"position": [
|
||||||
|
0.032143,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-022",
|
||||||
|
"position": [
|
||||||
|
0.096429,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-023",
|
||||||
|
"position": [
|
||||||
|
0.160714,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-024",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
-0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-025",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-026",
|
||||||
|
"position": [
|
||||||
|
-0.160714,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-027",
|
||||||
|
"position": [
|
||||||
|
-0.096429,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-028",
|
||||||
|
"position": [
|
||||||
|
-0.032143,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-029",
|
||||||
|
"position": [
|
||||||
|
0.032143,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-030",
|
||||||
|
"position": [
|
||||||
|
0.096429,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-031",
|
||||||
|
"position": [
|
||||||
|
0.160714,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-032",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
0.045,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-033",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-034",
|
||||||
|
"position": [
|
||||||
|
-0.160714,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-035",
|
||||||
|
"position": [
|
||||||
|
-0.096429,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-036",
|
||||||
|
"position": [
|
||||||
|
-0.032143,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-037",
|
||||||
|
"position": [
|
||||||
|
0.032143,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-038",
|
||||||
|
"position": [
|
||||||
|
0.096429,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-039",
|
||||||
|
"position": [
|
||||||
|
0.160714,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-040",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
0.135,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-041",
|
||||||
|
"position": [
|
||||||
|
-0.225,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-042",
|
||||||
|
"position": [
|
||||||
|
-0.160714,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-043",
|
||||||
|
"position": [
|
||||||
|
-0.096429,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-044",
|
||||||
|
"position": [
|
||||||
|
-0.032143,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-045",
|
||||||
|
"position": [
|
||||||
|
0.032143,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-046",
|
||||||
|
"position": [
|
||||||
|
0.096429,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-047",
|
||||||
|
"position": [
|
||||||
|
0.160714,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mic-048",
|
||||||
|
"position": [
|
||||||
|
0.225,
|
||||||
|
0.225,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,46 +1,132 @@
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$projectRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
$projectRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||||
$outputPath = Join-Path $projectRoot 'assets/array-geometries/rectangular-16x12.array.json'
|
$utf8WithoutBom = [System.Text.UTF8Encoding]::new($false)
|
||||||
$microphones = [System.Collections.Generic.List[object]]::new()
|
|
||||||
$columns = 16
|
|
||||||
$rows = 12
|
|
||||||
$width = 0.45
|
|
||||||
$height = 0.45
|
|
||||||
|
|
||||||
for ($row = 0; $row -lt $rows; $row++) {
|
function New-CoordinateSystem {
|
||||||
for ($column = 0; $column -lt $columns; $column++) {
|
return [ordered]@{
|
||||||
$index = $row * $columns + $column
|
|
||||||
$x = -$width / 2 + $column * $width / ($columns - 1)
|
|
||||||
$y = -$height / 2 + $row * $height / ($rows - 1)
|
|
||||||
$microphones.Add([ordered]@{
|
|
||||||
id = 'mic-{0:D3}' -f ($index + 1)
|
|
||||||
position = @([Math]::Round($x, 6), [Math]::Round($y, 6), 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$arrayDefinition = [ordered]@{
|
|
||||||
schemaVersion = '1.0.0'
|
|
||||||
kind = 'micarrsim.array'
|
|
||||||
id = 'rectangular-16x12'
|
|
||||||
name = '16x12 rectangular baseline'
|
|
||||||
description = '192-channel uniform rectangular layout on a 0.45 m square aperture'
|
|
||||||
units = 'm'
|
|
||||||
coordinateSystem = [ordered]@{
|
|
||||||
handedness = 'right'
|
handedness = 'right'
|
||||||
xAxis = 'right'
|
xAxis = 'right'
|
||||||
yAxis = 'up'
|
yAxis = 'up'
|
||||||
zAxis = 'toward-scene'
|
zAxis = 'toward-scene'
|
||||||
}
|
}
|
||||||
aperture = [ordered]@{ width = $width; height = $height }
|
|
||||||
microphones = $microphones
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$json = $arrayDefinition | ConvertTo-Json -Depth 8
|
function New-RectangularArray {
|
||||||
[System.IO.File]::WriteAllText($outputPath, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
|
param(
|
||||||
Write-Host "Generated $($microphones.Count) microphones: $outputPath"
|
[Parameter(Mandatory = $true)][string]$Id,
|
||||||
|
[Parameter(Mandatory = $true)][string]$Name,
|
||||||
|
[Parameter(Mandatory = $true)][string]$Description,
|
||||||
|
[Parameter(Mandatory = $true)][int]$Columns,
|
||||||
|
[Parameter(Mandatory = $true)][int]$Rows,
|
||||||
|
[Parameter(Mandatory = $true)][double]$Width,
|
||||||
|
[Parameter(Mandatory = $true)][double]$Height
|
||||||
|
)
|
||||||
|
|
||||||
|
$microphones = [System.Collections.Generic.List[object]]::new()
|
||||||
|
for ($row = 0; $row -lt $Rows; $row++) {
|
||||||
|
for ($column = 0; $column -lt $Columns; $column++) {
|
||||||
|
$index = $row * $Columns + $column
|
||||||
|
$x = if ($Columns -eq 1) { 0 } else { -$Width / 2 + $column * $Width / ($Columns - 1) }
|
||||||
|
$y = if ($Rows -eq 1) { 0 } else { -$Height / 2 + $row * $Height / ($Rows - 1) }
|
||||||
|
$microphones.Add([ordered]@{
|
||||||
|
id = 'mic-{0:D3}' -f ($index + 1)
|
||||||
|
position = @([Math]::Round($x, 6), [Math]::Round($y, 6), 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ordered]@{
|
||||||
|
schemaVersion = '1.0.0'
|
||||||
|
kind = 'micarrsim.array'
|
||||||
|
id = $Id
|
||||||
|
name = $Name
|
||||||
|
description = $Description
|
||||||
|
units = 'm'
|
||||||
|
coordinateSystem = New-CoordinateSystem
|
||||||
|
aperture = [ordered]@{ width = $Width; height = $Height }
|
||||||
|
microphones = $microphones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-ArrayFixture {
|
||||||
|
param([Parameter(Mandatory = $true)]$Definition)
|
||||||
|
|
||||||
|
$outputPath = Join-Path $projectRoot "assets/array-geometries/$($Definition.id).array.json"
|
||||||
|
$json = $Definition | ConvertTo-Json -Depth 8
|
||||||
|
[System.IO.File]::WriteAllText($outputPath, $json + [Environment]::NewLine, $utf8WithoutBom)
|
||||||
|
Write-Host "Generated $($Definition.microphones.Count) microphones: $outputPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$rectangularFixtures = @(
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-2x2-450mm'
|
||||||
|
Name = '2x2 sparse rectangular'
|
||||||
|
Description = '4-channel uniform rectangular layout on the 0.45 m reference aperture'
|
||||||
|
Columns = 2
|
||||||
|
Rows = 2
|
||||||
|
Width = 0.45
|
||||||
|
Height = 0.45
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-4x3-450mm'
|
||||||
|
Name = '4x3 sparse rectangular'
|
||||||
|
Description = '12-channel uniform rectangular layout on the 0.45 m reference aperture'
|
||||||
|
Columns = 4
|
||||||
|
Rows = 3
|
||||||
|
Width = 0.45
|
||||||
|
Height = 0.45
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-8x6-450mm'
|
||||||
|
Name = '8x6 rectangular'
|
||||||
|
Description = '48-channel uniform rectangular layout on the 0.45 m reference aperture'
|
||||||
|
Columns = 8
|
||||||
|
Rows = 6
|
||||||
|
Width = 0.45
|
||||||
|
Height = 0.45
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-16x12'
|
||||||
|
Name = '16x12 rectangular baseline'
|
||||||
|
Description = '192-channel uniform rectangular layout on a 0.45 m square aperture'
|
||||||
|
Columns = 16
|
||||||
|
Rows = 12
|
||||||
|
Width = 0.45
|
||||||
|
Height = 0.45
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-24x16-450mm'
|
||||||
|
Name = '24x16 dense rectangular'
|
||||||
|
Description = '384-channel uniform rectangular layout on the 0.45 m reference aperture'
|
||||||
|
Columns = 24
|
||||||
|
Rows = 16
|
||||||
|
Width = 0.45
|
||||||
|
Height = 0.45
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-16x12-compact'
|
||||||
|
Name = '16x12 compact rectangular'
|
||||||
|
Description = '192-channel uniform rectangular layout on a 0.12 m square aperture'
|
||||||
|
Columns = 16
|
||||||
|
Rows = 12
|
||||||
|
Width = 0.12
|
||||||
|
Height = 0.12
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
Id = 'rectangular-16x12-large'
|
||||||
|
Name = '16x12 large rectangular'
|
||||||
|
Description = '192-channel uniform rectangular layout on a 1.20 m square aperture for spatial-aliasing comparison'
|
||||||
|
Columns = 16
|
||||||
|
Rows = 12
|
||||||
|
Width = 1.2
|
||||||
|
Height = 1.2
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($fixture in $rectangularFixtures) {
|
||||||
|
Write-ArrayFixture (New-RectangularArray @fixture)
|
||||||
|
}
|
||||||
|
|
||||||
$spiralOutputPath = Join-Path $projectRoot 'assets/array-geometries/fermat-spiral-192.array.json'
|
|
||||||
$spiralMicrophones = [System.Collections.Generic.List[object]]::new()
|
$spiralMicrophones = [System.Collections.Generic.List[object]]::new()
|
||||||
$microphoneCount = 192
|
$microphoneCount = 192
|
||||||
$radius = 0.225
|
$radius = 0.225
|
||||||
@@ -64,20 +150,9 @@ $spiralDefinition = [ordered]@{
|
|||||||
name = '192-channel Fermat spiral'
|
name = '192-channel Fermat spiral'
|
||||||
description = 'Equal-area Fermat spiral on a 0.45 m circular aperture for comparison with the rectangular baseline'
|
description = 'Equal-area Fermat spiral on a 0.45 m circular aperture for comparison with the rectangular baseline'
|
||||||
units = 'm'
|
units = 'm'
|
||||||
coordinateSystem = [ordered]@{
|
coordinateSystem = New-CoordinateSystem
|
||||||
handedness = 'right'
|
|
||||||
xAxis = 'right'
|
|
||||||
yAxis = 'up'
|
|
||||||
zAxis = 'toward-scene'
|
|
||||||
}
|
|
||||||
aperture = [ordered]@{ width = 0.45; height = 0.45 }
|
aperture = [ordered]@{ width = 0.45; height = 0.45 }
|
||||||
microphones = $spiralMicrophones
|
microphones = $spiralMicrophones
|
||||||
}
|
}
|
||||||
|
|
||||||
$spiralJson = $spiralDefinition | ConvertTo-Json -Depth 8
|
Write-ArrayFixture $spiralDefinition
|
||||||
[System.IO.File]::WriteAllText(
|
|
||||||
$spiralOutputPath,
|
|
||||||
$spiralJson + [Environment]::NewLine,
|
|
||||||
[System.Text.UTF8Encoding]::new($false)
|
|
||||||
)
|
|
||||||
Write-Host "Generated $($spiralMicrophones.Count) microphones: $spiralOutputPath"
|
|
||||||
|
|||||||
@@ -196,6 +196,31 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
|
|||||||
!documentObject.querySelector("#run-button").disabled,
|
!documentObject.querySelector("#run-button").disabled,
|
||||||
"original comparison configuration restore",
|
"original comparison configuration restore",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const comparisonPresets = [
|
||||||
|
["rectangular-2x2-450mm", "4 ch", "0.450 × 0.450 m"],
|
||||||
|
["rectangular-4x3-450mm", "12 ch", "0.450 × 0.450 m"],
|
||||||
|
["rectangular-8x6-450mm", "48 ch", "0.450 × 0.450 m"],
|
||||||
|
["rectangular-24x16-450mm", "384 ch", "0.450 × 0.450 m"],
|
||||||
|
["rectangular-16x12-compact", "192 ch", "0.120 × 0.120 m"],
|
||||||
|
["rectangular-16x12-large", "192 ch", "1.200 × 1.200 m"],
|
||||||
|
];
|
||||||
|
assertEqual(
|
||||||
|
documentObject.querySelectorAll("#array-preset option").length,
|
||||||
|
9,
|
||||||
|
"all built-in and custom array choices are visible",
|
||||||
|
);
|
||||||
|
for (const [presetId, microphoneCount, aperture] of comparisonPresets) {
|
||||||
|
dispatchValue(windowObject, documentObject.querySelector("#array-preset"), presetId);
|
||||||
|
await waitFor(
|
||||||
|
() => documentObject.querySelector("#array-preset").value === presetId &&
|
||||||
|
documentObject.querySelector("#microphone-count").textContent === microphoneCount &&
|
||||||
|
documentObject.querySelector("#array-meta").textContent.includes(aperture) &&
|
||||||
|
documentObject.documentElement.dataset.cameraValid === "true",
|
||||||
|
`${presetId} preset`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
dispatchValue(
|
dispatchValue(
|
||||||
windowObject,
|
windowObject,
|
||||||
documentObject.querySelector("#array-preset"),
|
documentObject.querySelector("#array-preset"),
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ async function loadJson(path) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RECTANGULAR_ARRAY_EXAMPLES = [
|
||||||
|
{ id: "rectangular-2x2-450mm", microphoneCount: 4, columns: 2, rows: 2, width: 0.45, height: 0.45 },
|
||||||
|
{ id: "rectangular-4x3-450mm", microphoneCount: 12, columns: 4, rows: 3, width: 0.45, height: 0.45 },
|
||||||
|
{ id: "rectangular-8x6-450mm", microphoneCount: 48, columns: 8, rows: 6, width: 0.45, height: 0.45 },
|
||||||
|
{ id: "rectangular-16x12", microphoneCount: 192, columns: 16, rows: 12, width: 0.45, height: 0.45 },
|
||||||
|
{ id: "rectangular-24x16-450mm", microphoneCount: 384, columns: 24, rows: 16, width: 0.45, height: 0.45 },
|
||||||
|
{ id: "rectangular-16x12-compact", microphoneCount: 192, columns: 16, rows: 12, width: 0.12, height: 0.12 },
|
||||||
|
{ id: "rectangular-16x12-large", microphoneCount: 192, columns: 16, rows: 12, width: 1.2, height: 1.2 },
|
||||||
|
];
|
||||||
|
|
||||||
test("moving-tone sample Scene and Experiment satisfy their linked contracts", async () => {
|
test("moving-tone sample Scene and Experiment satisfy their linked contracts", async () => {
|
||||||
const [arrayDefinition, scene, experiment] = await Promise.all([
|
const [arrayDefinition, scene, experiment] = await Promise.all([
|
||||||
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
|
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
|
||||||
@@ -36,6 +46,92 @@ test("192채널 기준 배열이 계약을 통과한다", async () => {
|
|||||||
assertEqual((await validateArrayDefinition(arrayDefinition)).length, 0, "array issues");
|
assertEqual((await validateArrayDefinition(arrayDefinition)).length, 0, "array issues");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("rectangular array examples cover low-to-high channel counts and compact-to-wide apertures", async () => {
|
||||||
|
const arrays = await Promise.all(RECTANGULAR_ARRAY_EXAMPLES.map(({ id }) =>
|
||||||
|
loadJson(`/assets/array-geometries/${id}.array.json`)));
|
||||||
|
|
||||||
|
for (let index = 0; index < arrays.length; index += 1) {
|
||||||
|
const arrayDefinition = arrays[index];
|
||||||
|
const expected = RECTANGULAR_ARRAY_EXAMPLES[index];
|
||||||
|
assertEqual(arrayDefinition.id, expected.id, `${expected.id} fixture id`);
|
||||||
|
assertEqual(arrayDefinition.microphones.length, expected.microphoneCount, `${expected.id} channel count`);
|
||||||
|
assertEqual(arrayDefinition.aperture.width, expected.width, `${expected.id} aperture width`);
|
||||||
|
assertEqual(arrayDefinition.aperture.height, expected.height, `${expected.id} aperture height`);
|
||||||
|
assertEqual(
|
||||||
|
(await validateArrayDefinition(arrayDefinition)).length,
|
||||||
|
0,
|
||||||
|
`${expected.id} contract issues`,
|
||||||
|
);
|
||||||
|
assertEqual(
|
||||||
|
new Set(arrayDefinition.microphones.map(({ id }) => id)).size,
|
||||||
|
expected.microphoneCount,
|
||||||
|
`${expected.id} microphone ids are unique`,
|
||||||
|
);
|
||||||
|
assertEqual(
|
||||||
|
new Set(arrayDefinition.microphones.map(({ position }) => position.join(","))).size,
|
||||||
|
expected.microphoneCount,
|
||||||
|
`${expected.id} positions are unique`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const xCoordinates = arrayDefinition.microphones.map(({ position }) => position[0]);
|
||||||
|
const yCoordinates = arrayDefinition.microphones.map(({ position }) => position[1]);
|
||||||
|
assert(
|
||||||
|
arrayDefinition.microphones.every(({ position }) => position[2] === 0),
|
||||||
|
`${expected.id} microphones stay on the z=0 plane`,
|
||||||
|
);
|
||||||
|
assertEqual(new Set(xCoordinates).size, expected.columns, `${expected.id} column count`);
|
||||||
|
assertEqual(new Set(yCoordinates).size, expected.rows, `${expected.id} row count`);
|
||||||
|
const uniqueX = [...new Set(xCoordinates)].sort((left, right) => left - right);
|
||||||
|
const uniqueY = [...new Set(yCoordinates)].sort((left, right) => left - right);
|
||||||
|
const expectedXPitch = expected.width / (expected.columns - 1);
|
||||||
|
const expectedYPitch = expected.height / (expected.rows - 1);
|
||||||
|
assert(
|
||||||
|
uniqueX.slice(1).every((value, position) =>
|
||||||
|
Math.abs(value - uniqueX[position] - expectedXPitch) < 1.1e-6),
|
||||||
|
`${expected.id} uniform x pitch`,
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
uniqueY.slice(1).every((value, position) =>
|
||||||
|
Math.abs(value - uniqueY[position] - expectedYPitch) < 1.1e-6),
|
||||||
|
`${expected.id} uniform y pitch`,
|
||||||
|
);
|
||||||
|
assert(Math.abs(Math.min(...xCoordinates) + expected.width / 2) < 1e-6, `${expected.id} x minimum`);
|
||||||
|
assert(Math.abs(Math.max(...xCoordinates) - expected.width / 2) < 1e-6, `${expected.id} x maximum`);
|
||||||
|
assert(Math.abs(Math.min(...yCoordinates) + expected.height / 2) < 1e-6, `${expected.id} y minimum`);
|
||||||
|
assert(Math.abs(Math.max(...yCoordinates) - expected.height / 2) < 1e-6, `${expected.id} y maximum`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelSeries = arrays.slice(0, 5);
|
||||||
|
assert(
|
||||||
|
channelSeries.every(({ aperture }) => aperture.width === 0.45 && aperture.height === 0.45),
|
||||||
|
"channel-count series keeps the 0.45 m aperture fixed",
|
||||||
|
);
|
||||||
|
assertEqual(
|
||||||
|
channelSeries.map(({ microphones }) => microphones.length).join(","),
|
||||||
|
"4,12,48,192,384",
|
||||||
|
"channel-count series",
|
||||||
|
);
|
||||||
|
const apertureSeries = [
|
||||||
|
arrays.find(({ id }) => id === "rectangular-16x12-compact"),
|
||||||
|
arrays.find(({ id }) => id === "rectangular-16x12"),
|
||||||
|
arrays.find(({ id }) => id === "rectangular-16x12-large"),
|
||||||
|
];
|
||||||
|
assert(
|
||||||
|
apertureSeries.every(({ microphones }) => microphones.length === 192),
|
||||||
|
"aperture series keeps 192 channels fixed",
|
||||||
|
);
|
||||||
|
assertEqual(
|
||||||
|
apertureSeries.map(({ aperture }) => aperture.width).join(","),
|
||||||
|
"0.12,0.45,1.2",
|
||||||
|
"compact-to-large aperture series",
|
||||||
|
);
|
||||||
|
assertEqual(
|
||||||
|
Math.max(...arrays.map(({ microphones }) => microphones.length)),
|
||||||
|
384,
|
||||||
|
"high-count example",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("192채널 Fermat spiral 비교 배열이 계약과 원형 aperture를 만족한다", async () => {
|
test("192채널 Fermat spiral 비교 배열이 계약과 원형 aperture를 만족한다", async () => {
|
||||||
const arrayDefinition = await loadJson("/assets/array-geometries/fermat-spiral-192.array.json");
|
const arrayDefinition = await loadJson("/assets/array-geometries/fermat-spiral-192.array.json");
|
||||||
assertEqual(arrayDefinition.microphones.length, 192, "spiral microphone count");
|
assertEqual(arrayDefinition.microphones.length, 192, "spiral microphone count");
|
||||||
|
|||||||
Reference in New Issue
Block a user