diff --git a/README.md b/README.md
index a085306..b904481 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# micarrsim
-192채널 MEMS 마이크 배열의 배치와 자유음장·저비용 실내 근사에서의 DAS 음향 영상을 비교하는 로컬 브라우저
+192채널 MEMS 마이크 배열의 배치와 자유음장·저비용 실내 근사에서 DAS·DAS-PHAT·GCC-PHAT
+위치 추정을 비교하는 로컬 브라우저
시뮬레이터입니다. Dataset Builder에서 녹음 파일·합성 신호와 이동 경로를 포함한 재현 가능한
음향 장면을 만들고 Acoustic Camera에서 CPU 또는 WebGPU로 배열 응답, acoustic map과 위치 검출
지표를 계산하거나 이동 장면을 시간축으로 재생합니다.
@@ -46,9 +47,12 @@ powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\serve.
1. 시작 화면에서 **Acoustic Camera**를 엽니다.
2. 기본 192채널 rectangular 배열, single-tone scene과 50×50 experiment가 준비될 때까지
기다립니다.
-3. Snapshot 카드의 **▶ Snapshot 실행**을 누릅니다.
+3. 처음에는 **DAS · 기준 power**를 두고 Snapshot 카드의 **▶ Snapshot 실행**을 누릅니다.
4. 계산이 끝나면 overlay와 평가 지표를 확인하고 **Result JSON**을 누릅니다.
+같은 입력에서 **위치 추정 알고리즘**만 DAS-PHAT으로 바꿔 다시 실행하면 아래 비교 기록에 두 결과가
+나란히 남습니다. GCC-PHAT은 광대역 입력에서 비교하세요.
+
실제 완료 시간은 브라우저와 장치에 따라 다릅니다. CPU fallback에서는 WebGPU보다 오래 걸릴 수
있습니다.
@@ -85,7 +89,7 @@ powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\serve.
5. 별도 실험 문서를 사용할 때만 **Experiment**에서 `.experiment.json`을 선택합니다.
6. Scene에 이미지나 audio asset이 있다면 **Assets**에서 Builder에 사용했던 원본 파일을 다시
선택합니다. 여러 파일을 한 번에 선택할 수 있습니다.
-7. 입력 검증이 통과하면 backend와 scan grid를 정하고 실행합니다.
+7. 입력 검증이 통과하면 위치 추정 알고리즘, backend와 scan grid를 정하고 실행합니다.
8. 완료 후 **Result JSON**을 저장합니다.
Array와 Scene을 먼저 불러와야 Experiment의 `arrayId`와 `sceneId` 관계를 안전하게 검증할 수
@@ -106,18 +110,38 @@ SHA-256 manifest를 저장합니다. 브라우저는 새 페이지나 세션에
- 실제 decode되어 메모리에 유지되는 오디오 합계는 최대 192 MiB입니다.
- Scene JSON에는 원본 파일명이 기록될 수 있으므로 파일을 외부에 공유하기 전에 확인하세요.
-## Backend와 scan grid
+## 알고리즘, Backend와 계산량
+
+| 알고리즘 | 원시 map | backend | 용도와 주의점 |
+| --- | --- | --- | --- |
+| DAS | 상대 acoustic power | CPU, WebGPU | 가장 단순한 기준선. 채널 gain과 알려진 마이크 응답을 보정합니다. |
+| DAS-PHAT | 무차원 score | CPU, WebGPU | 마이크별 진폭을 버리고 위상 정합을 비교해 gain·반사에 더 강건할 수 있습니다. |
+| GCC-PHAT | 무차원 score | CPU | 한 reference와 나머지 마이크의 TDOA 상관을 사용합니다. 광대역이 필요하며 협대역에서는 주기적 모호성이 큽니다. |
- **Auto (GPU→CPU)**: 권장값입니다. WebGPU adapter나 device를 사용할 수 없거나 지원되는 장치
한계를 넘으면 CPU로 계속합니다.
- **WebGPU 강제**: GPU 경로만 사용합니다. 장치를 열 수 없거나 한계를 넘으면 fallback하지 않고
오류를 표시합니다.
-- **CPU 강제**: 비교와 호환성 확인을 위해 CPU DAS만 사용합니다.
+- **CPU 강제**: 비교와 호환성 확인을 위해 선택한 알고리즘의 CPU 구현을 사용합니다.
+
+GCC-PHAT은 현재 CPU 전용이므로 선택하면 Auto 또는 CPU만 사용할 수 있습니다. Camera는 각 band가
+8개 미만의 FFT bin을 포함하면 협대역 경고를 표시합니다.
WebGPU 경로도 신호 합성과 STFT 일부는 CPU에서 수행합니다. grid의 point 수는 50×50을 1로 보면
100×100은 4배, 200×200은 16배입니다. 처음에는 50×50을 사용하고 해상도가 필요할 때만 높이세요.
실행 시간은 microphone 수, scene 길이, FFT, 대역 폭과 장치에 함께 좌우됩니다.
+DAS 계열의 scan 주항은 `P × M × T × F`입니다. 192채널·50×50·46 frames·2 bins의 기존
+fixture는 44,160,000 complex MAC입니다. GCC-PHAT은 같은 조건에서 forward STFT를 그대로
+수행한 뒤 191 reference pairs에 대해 35,144회의 2-pass cross-spectrum 연산, 191회의 inverse
+FFT와 477,500회의 point/pair lookup을 수행합니다. 따라서 큰 grid·광대역에서는 scan 비용을
+줄이지만 작은 grid에서는 공통 STFT 때문에 전체 시간이 같은 비율로 줄지는 않습니다.
+
+저장된 Intel Gen9 측정에서 DAS 50×50의 GPU wall은 79.2 ms였지만 CPU STFT는 1,669.8 ms,
+signal 합성은 별도 1,200.5 ms였습니다. 즉 현재 기본 fixture의 병목은 단순히 grid scan 하나가
+아닙니다. Camera의 workload 줄은 STFT, DAS MAC 또는 GCC cross-spectrum·inverse FFT·lookup,
+signal path와 distance cache를 따로 표시합니다.
+
## 음향 환경과 마이크 응답
Camera의 **고급 설정**에서 자유음장, 흡음된 실내, 일반 실내와 반사 강한 실내를 선택할 수
@@ -137,13 +161,18 @@ Camera의 **고급 설정**에서 자유음장, 흡음된 실내, 일반 실내
## 위치 추정, 빔 분포와 복수 음원
-DAS 출력은 각 scan point의 상대 acoustic power map이며 위치 확률분포가 아닙니다. Camera는 map의
-local peak를 위치 후보로 표시합니다. **빔 분포**를 켜면 각 peak의 연결된 half-power lobe에 대해
-power 가중 공분산을 계산해 1× RMS 점선과 2× RMS 영역을 그립니다. Gaussian의 등고선처럼 보일 수
-있지만 통계적으로 보정된 Gaussian, 신뢰구간 또는 발생 확률은 아닙니다.
+DAS 출력은 각 scan point의 상대 acoustic power, PHAT 계열 출력은 무차원 score이며 어느 쪽도
+위치 확률분포가 아닙니다. Camera는 map의 local peak를 위치 후보로 표시합니다. **빔 분포**를 켜면
+각 peak의 연결된 half-maximum lobe에 대해 map 값 가중 공분산을 계산해 1× RMS 점선과 2× RMS
+영역을 그립니다. Gaussian의 등고선처럼 보일 수 있지만 통계적으로 보정된 Gaussian, 신뢰구간 또는
+발생 확률은 아닙니다.
+
+GCC-PHAT raw score는 pair 수·band coefficient 수·IFFT 크기로 정규화한 0–1 양의 상관 응답입니다.
+scan 범위나 마이크 수 때문에 값이 단순히 커지는 현상은 막지만, 0.8을 “위치 확률 80%”로 읽을 수는
+없고 다른 알고리즘의 score와 절대값을 비교해서도 안 됩니다.
복수 음원은 각각 별도 local peak가 남을 만큼 공간적으로 분리되고 선택 대역에 에너지가 있어야
-검출됩니다. **Peak 검출 임계값 (%)**은 최강 peak 대비 후보의 최소 power입니다. 기본 25%에서 약한
+검출됩니다. **Peak 검출 임계값 (%)**은 최강 peak 대비 후보의 최소 map 값입니다. 기본 25%에서 약한
두 번째 음원이 빠지면 10–20%로 낮출 수 있지만 sidelobe 오검출도 늘어납니다. 서로 같은 대역에서
main lobe보다 가깝거나 gain 차이가 큰 음원은 conventional DAS의 물리·알고리즘 한계 때문에
threshold만으로 분리되지 않을 수 있습니다.
@@ -161,9 +190,9 @@ index와 audio asset 재생 위치가 이어지도록 신호를 합성합니다.
- 실제 장치: 물리 192채널 capture/stream gateway는 하드웨어 형식이 정해진 뒤 별도 adapter가 필요
Scene 합성 없이 동기식 `Float32Array` 마이크 채널을 바로 처리하는
-`createLocalizationPlan`, `localizeMultichannel`과 `StreamingDasLocalizer`가
+`createLocalizationPlan`, `localizeMultichannel`과 `StreamingLocalizer`가
`packages/acoustics-core`에 분리되어 있습니다. 고정 ring buffer, oldest-first drop,
-반복 geometry·steering cache와 `AbortSignal` 취소를 제공하며 실제 capture 장치에는
+반복 geometry·workspace cache와 `AbortSignal` 취소를 제공하며 실제 capture 장치에는
channel/clock/transport adapter만 별도로 연결합니다. 실시간 여부는 각 결과의
`processingTimeMs <= deadlineMs`와 누적 `deadlineMisses`·`droppedSamples`로
해당 장치에서 확인합니다. 복사할 파일과 운용 계약은
@@ -173,11 +202,11 @@ channel/clock/transport adapter만 별도로 연결합니다. 실시간 여부
Result JSON은 계약 버전 `1.0.0`의 `micarrsim.result` 문서입니다. 주요 내용은 다음과 같습니다.
-- 실행한 `experimentId`, `arrayId`, `sceneId`, 실제 `backend`와 생성 시각
+- 실행한 `experimentId`, `arrayId`, `sceneId`, 실제 `algorithm`·`backend`와 생성 시각
- Array·Scene·Experiment의 canonical SHA-256인 `inputHash`
- 정규화 전 peak, 적용 scale, clipping 수와 음향 모델·희소 경로 수를 담은 `signalStats`
-- 전체·신호 생성·beamforming 시간인 `timingMs`
-- 주파수 대역별 선형 power `rawMap`, 표시용 `displayMap`, 검출 `peaks`
+- 전체·신호 생성·위치 추정 시간인 `timingMs.localization`과 기존 reader용 `beamforming` alias
+- 주파수 대역별 power 또는 score `rawMap`, 표시용 `displayMap`, 검출 `peaks`
- 위치 오차, precision/recall, truth 반경 내 energy, peak-to-sidelobe, main-lobe 폭을 계산하기
위한 평가 파라미터와 `metrics`
- peak별 half-power 공분산과 RMS ellipse를 담은 `beamSpread` (`calibrated: false`)
@@ -195,7 +224,8 @@ noise source는 스펙트럼을 미리 알 수 없으므로 모든 band의 groun
음향 모델은 직접 경로의 자유음장과 1차 반사·scattering·occluder 회절·RT60 tail을 제한된
경로로 계산하는 shoebox 근사를 제공합니다. 파동 방정식, 고차 반사, 재질별 주파수 의존 흡음,
복잡한 mesh 회절, 바람, Doppler, 마이크 self-noise·비선형, ADC와 channel clock은 모델링하지
-않습니다. Beamformer는 conventional DAS이며 UI의 DAMAS와 CLEAN-SC는 아직 사용할 수 없습니다.
+않습니다. 위치 추정기는 DAS, DAS-PHAT와 CPU GCC-PHAT를 제공하지만 full-pair SRP-PHAT, MUSIC,
+DAMAS와 CLEAN-SC는 구현하지 않습니다.
따라서 결과는 배열·grid·대역의 상대 비교와 소프트웨어 검증용이며, 현장 성능 인증이나 안전
판단을 대신하지 않습니다.
diff --git a/apps/acoustic-camera/README.md b/apps/acoustic-camera/README.md
index ef17f18..fdbf07a 100644
--- a/apps/acoustic-camera/README.md
+++ b/apps/acoustic-camera/README.md
@@ -1,8 +1,9 @@
# Acoustic Camera
Scene과 마이크 배열로 자유음장 또는 저비용 shoebox microphone signal을 합성하고, STFT와
-conventional DAS를 통해 주파수 대역별 acoustic map과 ground-truth 평가 지표를 만드는 브라우저
-앱입니다. 정지 장면은 Snapshot, trajectory 장면은 Moving 흐름으로 분리되어 있습니다.
+DAS·DAS-PHAT·GCC-PHAT 위치추정으로 주파수 대역별 acoustic map과 ground-truth 평가 지표를
+만드는 브라우저 앱입니다. 정지 장면은 Snapshot, trajectory 장면은 Moving 흐름으로 분리되어
+있습니다.
## 열기와 Snapshot
@@ -42,12 +43,31 @@ Camera가 SHA-256을 계산해 manifest에 연결합니다. 같은 이름이라
파일은 외부로 업로드되지 않고 브라우저 메모리에서만 처리됩니다.
-## Backend 선택
+## 위치 추정 알고리즘과 Backend
+
+**분석 설정 → 위치 추정 알고리즘**에서 다음 세 구현을 선택합니다.
+
+- **DAS**: 채널 진폭과 위상을 유지해 coherent sum power를 계산하는 기준선입니다. CPU와 WebGPU를
+ 지원하고 `rawMap.scale`은 `power`입니다.
+- **DAS-PHAT**: 각 STFT 성분의 크기를 정규화하고 위상을 합산합니다. 채널 gain 차이에 덜
+ 민감한 비교용이며 CPU와 WebGPU를 지원합니다. 결과는 무차원 `score`입니다.
+- **GCC-PHAT**: 기준 마이크와 나머지 마이크의 PHAT 상관에서 기하학적 TDOA를 평가합니다. 현재
+ CPU 전용이므로 Auto는 CPU를 선택하고 **WebGPU 강제**는 선택할 수 없습니다. 각 band가 8개
+ 미만의 FFT bin을 포함하면 협대역 상관이 불안정할 수 있다는 경고가 표시됩니다.
+
+PHAT score는 위치 확률이나 음향 power가 아닙니다. 세 알고리즘 모두 같은 scan grid에 응답 map을
+만들지만, 진폭 보존 여부와 실패 양상이 다르므로 같은 Scene·Array·band로 비교하세요.
+GCC-PHAT raw score는 pair·band coefficient·IFFT 크기를 정규화한 0–1 양의 상관 응답이라 scan
+범위나 마이크 수에 따른 단순 scale 변화는 피하지만, 확률이나 다른 알고리즘과 공통인 confidence는
+아닙니다.
- **Auto (GPU→CPU)**: 권장. WebGPU를 먼저 시도하고 adapter/device 부재나 지원되는 장치 한계가
- 있으면 CPU로 계속합니다.
-- **WebGPU 강제**: GPU만 사용하며 운용 오류가 나도 CPU로 fallback하지 않습니다.
-- **CPU 강제**: CPU 수치 기준이나 WebGPU가 없는 환경에서 사용합니다.
+ 있으면 CPU로 계속합니다. 라이브 준비에서는 첫 fallback 뒤 남은 frame이 같은 CPU analysis
+ plan을 재사용하므로 GPU 실패를 매 frame 반복하지 않습니다. GCC-PHAT에서는 **Auto (CPU)**로
+ 표시됩니다.
+- **WebGPU 강제**: DAS와 DAS-PHAT를 GPU에서만 실행하며 운용 오류가 나도 CPU로 fallback하지
+ 않습니다.
+- **CPU 강제**: 선택한 알고리즘의 CPU 수치 기준이나 WebGPU가 없는 환경에서 사용합니다.
WebGPU가 감지돼도 실제 장치는 실행 시 확인합니다. 계산 중 **계산 취소**를 누르면 Worker를
종료하며, 이미 완료된 이전 결과가 있으면 그대로 남습니다.
@@ -82,9 +102,10 @@ Array의 각 microphone에는 선택적 `frequencyResponse`를 넣을 수 있습
```
curve는 합성 microphone channel에 항상 적용됩니다. **마이크 주파수 응답 처리**의 기본
-**측정 curve 적용 + 위치추정에서 역보정**은 CPU와 WebGPU DAS에서 알려진 gain/phase를
-역보정합니다. **측정 curve 적용 + 보정하지 않음**은 합성 응답은 유지하고 역보정만 꺼서 영향을
-비교합니다. curve가 없는 마이크는 기존 결과를 유지합니다. 응답 notch를 과도하게 증폭하지
+**측정 curve 적용 + 위치추정에서 역보정**은 선택한 CPU 위치추정과 DAS/DAS-PHAT WebGPU
+경로에서 알려진 gain/phase를 역보정합니다. **측정 curve 적용 + 보정하지 않음**은 합성 응답은
+유지하고 역보정만 꺼서 영향을 비교합니다. curve가 없는 마이크는 기존 결과를 유지합니다. 응답 notch를
+과도하게 증폭하지
않도록 역보정의 양의 gain boost는 기본 24 dB로 제한하며, 사용자 Experiment JSON의
`beamforming.maxCompensationDb`에서 0–120 dB로 지정할 수 있습니다.
@@ -94,32 +115,40 @@ curve는 합성 microphone channel에 항상 적용됩니다. **마이크 주파
## Grid, STFT와 대역
50×50은 2,500 points입니다. 이를 1로 보면 100×100은 4배, 200×200은 16배 points를 계산합니다.
-처음에는 50×50을 권장합니다. 실행량은 microphone 수, scene 길이, FFT/hop과 선택 band의 bin
-수에도 비례하므로 실제 시간 배율은 장치마다 다릅니다.
+처음에는 50×50을 권장합니다. 연산량 줄에는 공통 STFT transform 수와 signal path, distance
+cache를 표시하고 알고리즘별 위치추정 비용을 다음처럼 분리합니다.
+
+- DAS/DAS-PHAT: scan point × microphone × STFT frame × 선택 bin의 complex MAC
+- GCC-PHAT: 기준 마이크 pair별 two-pass cross-spectrum, pair × band inverse FFT와
+ scan point × pair × band correlation lookup
+
+이는 구현 연산 횟수이지 실행 시간 예측값은 아닙니다. scene 길이, microphone 수, FFT/hop,
+band 수와 폭, grid 및 CPU/GPU에 따라 실제 시간이 달라집니다.
각 band는 id, 최소/최대 Hz와 overlay 색을 가집니다. Nyquist를 넘거나 겹치는 id 등 계약 오류는
-실행 전에 표시됩니다. 현재 후처리는 raw DAS만 지원하며 DAMAS와 CLEAN-SC 항목은 비활성화되어
-있습니다.
+실행 전에 표시됩니다. DAMAS와 CLEAN-SC 후처리는 아직 구현하지 않았으며, 실행할 수 없는 비활성
+항목은 Camera UI에서 제거했습니다. Result의 `processedMaps` 계약만 향후 확장을 위해 남아 있습니다.
-**Peak 검출 임계값 (%)**은 최강 local peak의 power에 대한 상대 임계값이며 Experiment JSON에
+**Peak 검출 임계값 (%)**은 최강 local peak의 map 값에 대한 상대 임계값이며 Experiment JSON에
저장됩니다. 기본 25%에서 약한 두 번째 음원이 빠지면 10–20%로 낮추세요. 낮은 값은 더 약한 후보를
-살리는 대신 sidelobe를 음원으로 잘못 고를 가능성도 높입니다. 같은 대역의 두 음원이 main lobe
-폭보다 가깝다면 grid만 키우거나 threshold를 낮춰도 conventional DAS map에서 하나로 합쳐질 수
-있습니다.
+살리는 대신 sidelobe를 음원으로 잘못 고를 가능성도 높입니다. 같은 대역의 두 음원이 알고리즘의
+main lobe 폭보다 가깝다면 grid만 키우거나 threshold를 낮춰도 하나로 합쳐질 수 있습니다.
-## 화면과 배열 비교
+## 화면과 배열·알고리즘 비교
- acoustic overlay의 투명도와 표시할 band를 선택할 수 있습니다.
- **Ground truth**를 켜면 source 위치와 검출 peak를 비교할 수 있습니다.
-- **빔 분포**는 peak 주변 half-power lobe의 power 가중 공분산을 1× RMS 점선과 2× RMS 영역으로
- 표시합니다. 위치 확률, Gaussian 신뢰구간 또는 측정 불확실성 보정값은 아닙니다.
+- **빔 분포**는 DAS power의 half-power 또는 PHAT score의 half-maximum 연결 성분을 해당 map
+ 값으로 가중한 공분산으로 계산해 1× RMS 점선과 2× RMS 영역으로 표시합니다. 위치 확률,
+ Gaussian 신뢰구간 또는 측정 불확실성 보정값은 아닙니다.
- **배열 선택**에는 채널 수(4·12·48·192·384), aperture 크기(0.12·0.45·1.20 m), rectangular와
- Fermat spiral 배치를 분리 비교하는 8개 preset이 있습니다. 같은 Scene/Experiment에서 연속 실행하면
- **배열 비교 기록**에 backend, timing과 평가 요약이 남습니다.
+ Fermat spiral 배치를 분리 비교하는 8개 preset이 있습니다. 같은 scene·scan·STFT에서 연속
+ 실행하면 **비교 기록**에 배열, 알고리즘, 실제 backend, 위치추정/전체 timing과 평가 요약이 함께
+ 남습니다.
- 입력을 바꾼 뒤 화면에 남은 결과는 이전 실행 snapshot이며 UI가 이전 설정 결과임을 표시합니다.
- Camera에서 거리·음속·seed를 바꾼 Scene과 scan·STFT·band를 바꾼 Experiment는 우측의 각
**JSON 저장** 버튼으로 보존할 수 있습니다.
-- **전체 기록 JSON**은 세션에 남은 모든 배열 비교 요약을
+- **전체 기록 JSON**은 세션에 남은 배열·알고리즘·backend 비교 요약을
`micarrsim.comparison-history` 문서로 저장합니다.
미저장 Scene·Experiment·Result·비교 기록은 우측 저장 상태에 표시됩니다. 직전 Result를 저장하지
@@ -131,7 +160,7 @@ curve는 합성 microphone channel에 항상 적용됩니다. **마이크 주파
Scene source에 두 개 이상의 `trajectory` keyframe이 있으면 **Map 준비**가 활성화됩니다.
Builder에서는 **시간에 따라 음원 이동**과 경로 미리보기 slider로 이 데이터를 만들 수 있습니다.
-1. Map rate와 backend를 선택합니다. 처음에는 2 Hz와 Auto를 권장합니다.
+1. 알고리즘, Map rate와 backend를 선택합니다. 처음에는 DAS, 2 Hz와 Auto를 권장합니다.
2. **Map 준비**를 누릅니다. 현재 입력 snapshot을 최대 48개, map당 최대 2,500 points의 짧은
분석창으로 계산합니다.
3. **재생** 또는 시간 slider로 이동합니다. Heatmap은 이웃한 acoustic map 사이를 보간하고
@@ -146,13 +175,15 @@ Scene 시간에 이어 붙입니다. 한 창 안에서는 중앙 시각의 위
**Result JSON**은 검증을 통과한 `micarrsim.result` 문서만 저장합니다.
-- `experimentId`, `arrayId`, `sceneId`, `createdAt`, 실제 `backend`
+- `experimentId`, `arrayId`, `sceneId`, `createdAt`, 실제 `algorithm`과 `backend`
- 실행 입력 전체의 canonical SHA-256인 `inputHash`
- `signalStats`: 정규화 전 peak, 적용 scale, clipped sample 수, 실제 음향 모델과 sparse
path 전체·종류별 수
-- `timingMs`: 전체, signal generation과 beamforming 시간
-- band별 `rawMap` 선형 power, `displayMap`, 검출 `peaks`
-- 각 peak의 비보정 half-power 공분산과 RMS ellipse인 `beamSpread`
+- `timingMs`: 전체, signal generation과 `localization`; 기존 소비자 호환용 `beamforming`은 같은
+ 위치추정 시간을 담는 alias
+- band별 `rawMap`: DAS는 선형 `power`, PHAT 계열은 무차원 `score`; 화면용 `displayMap`과 검출
+ `peaks`
+- 각 peak의 비보정 half-power/half-maximum 공분산과 RMS ellipse인 `beamSpread`
- 평가 설정과 `peakToSidelobeDb`, `energyInsideTruthRatio`, `precision`, `recall`,
`mainLobeWidthM`
@@ -172,7 +203,8 @@ sensitivity/phase/frequency-response calibration은 적용하지만 결과는
실제 동기식 microphone `Float32Array`를 Scene 합성 없이 위치추정하려면
[실시간 위치추정 코어 포팅](../../docs/realtime-porting.md)의
`createLocalizationPlan`, `localizeMultichannel` 또는
-`StreamingDasLocalizer`를 사용하세요. 실시간성은 장치에서
+`createStreamingLocalizer`를 사용하세요. portable config의 `algorithm`으로 같은 세 위치추정을
+선택할 수 있습니다. 실시간성은 장치에서
`processingTimeMs <= deadlineMs`와 `deadlineMisses`·`droppedSamples`를
측정해 판정해야 합니다.
diff --git a/docs/acoustic-model.md b/docs/acoustic-model.md
index d5fd38a..c3ec9c1 100644
--- a/docs/acoustic-model.md
+++ b/docs/acoustic-model.md
@@ -11,8 +11,9 @@ Snapshot 한 번의 처리 순서는 다음과 같다.
1. tone, noise 또는 audio asset으로 음원 신호를 만든다.
2. 음원과 각 마이크 사이의 직접음 또는 희소 multipath 전달함수를 만든다.
3. 마이크별 `sensitivityDb`, `phaseOffsetDeg`와 `frequencyResponse`를 수신 신호에 적용한다.
-4. 합성된 동기식 다채널 신호를 STFT하고 conventional DAS map을 계산한다.
-5. 설정에 따라 알려진 마이크 응답을 DAS에서 역보정하고 peak와 평가 지표를 구한다.
+4. 합성된 동기식 다채널 신호를 STFT하고 `beamforming.algorithm`으로 선택한 위치 추정
+ 알고리즘의 scan map을 계산한다.
+5. 알고리즘에 맞게 알려진 마이크 응답을 보정하고 map의 peak와 평가 지표를 구한다.
Scene의 `acoustics`를 생략하거나 `model: "free-field"`로 지정하면 기존 직접음 결과를
유지한다. `model: "shoebox"`는 직육면체 방 안에서 경로 수가 제한된 근사를 추가한다.
@@ -97,9 +98,91 @@ preset을 적용한다.
각 source–microphone 직접 경로를 실제로 막을 때 하나씩 추가된다. 대략적인 합성 경로 예산은
`음원 수 × 마이크 수 × 수신점당 최대 경로`다. Camera는 실행 전에 이 예산을 표시한다.
-반사·tail을 늘리면 beamforming의 `O(scan points × microphones × selected bins × STFT frames)`
-뿐 아니라 신호 합성의 전달함수 누산도 늘어난다. 처음에는 50×50 grid와 자유음장으로 기준선을
-만들고, 같은 입력에서 preset을 하나씩 바꾸어 비교하는 것이 좋다.
+반사·tail을 늘리면 신호 합성의 전달함수 누산도 늘어난다. DAS 계열의 scan 누산은 대략
+`O(scan points × microphones × selected bins × STFT frames)`이고, GCC-PHAT는 pair별 상호
+상관을 만든 뒤 `O(scan points × (microphones - 1) × bands)` lookup을 수행한다. 어느 방식이든
+STFT와 거리 cache 비용은 별도로 필요하다. 처음에는 50×50 grid와 자유음장으로 기준선을 만들고,
+같은 입력에서 환경과 알고리즘을 하나씩 바꾸어 비교하는 것이 좋다.
+
+## 위치 추정 알고리즘
+
+마이크 `m`, STFT frame `t`, 주파수 `f`의 복소 spectrum을 `X_m(t, f)`, scan point `p`와
+마이크 사이 거리를 `r_m(p)`, 파수를 `k = 2πf/c`라고 하자. `φ_m(f)`는 알려진 마이크의 정적
+phase offset과, 주파수 응답 보정을 켠 경우 response phase를 합한 값이다. 모든 map은 scan point
+마다 하나의 비음수 상대값을 만들지만, 그 값은 위치의 확률이나 posterior가 아니다.
+
+### DAS (`das`)
+
+Conventional frequency-domain delay-and-sum은 각 후보점에서 구면파 전파 위상을 되돌려 채널을
+coherent sum한다.
+
+```text
+B_DAS(p,t,f) = (1/M) Σ_m A_m⁻¹(f) X_m(t,f) exp(j[k r_m(p) - φ_m(f)])
+P_DAS(p) = mean_(t,f) |B_DAS(p,t,f)|²
+```
+
+`A_m⁻¹(f)`에는 sensitivity의 역 gain과, 설정한 경우 frequency-response gain의 제한된 역보정이
+들어간다. 구현은 frame별 제곱 크기를 먼저 평균하고 선택 band의 bin들을 다시 평균한다. 따라서
+Result의 `rawMap.scale`은 `power`지만, 절대 SPL로 calibration한 power가 아니라 같은 입력·설정
+안에서 비교하는 선형 상대 power다. CPU와 WebGPU를 지원한다.
+
+### DAS-PHAT (`das-phat`)
+
+DAS-PHAT는 steering 전에 각 frame·bin·마이크 spectrum을 위상만 남기도록 정규화한다.
+
+```text
+U_m(t,f) = X_m(t,f) / |X_m(t,f)|
+S_DAS-PHAT(p) = mean_(t,f) |(1/M) Σ_m U_m(t,f) exp(j[k r_m(p) - φ_m(f)])|²
+```
+
+한 frame·bin에서 최강 마이크 크기의 기본 `10⁻⁶`배인 상대 floor 이하의 값은 `0`으로 두어
+무음이나 극소값의 불안정한 위상을 제외한다. 입력과 calibration의 amplitude gain은 coherent sum에 사용하지 않고,
+알려진 phase만 보정한다. 그 결과 source spectrum이나 채널 gain 차이에 덜 끌릴 수 있지만 유용한
+amplitude 정보도 버리며, 약한 bin의 위상과 multipath에 민감할 수 있다. `rawMap.scale`은 무차원
+`score`이고 CPU와 WebGPU를 지원한다.
+
+### GCC-PHAT (`gcc-phat`)
+
+현재 GCC-PHAT는 배열 중심에서 가장 먼 마이크 하나를 결정적인 reference로 고르고, reference와
+나머지 `M - 1`개 마이크의 pair만 사용한다. 각 band와 pair에 대해 다음 PHAT cross spectrum을
+STFT frame에 걸쳐 평균한다.
+
+```text
+C_mr(t,f) = X_m(t,f) conj(X_r(t,f))
+G_mr(f) = mean_t [C_mr(t,f) / max(|C_mr(t,f)|, floor)]
+ × exp(j[φ_r(f) - φ_m(f)])
+```
+
+기본 `floor`는 해당 band·pair의 모든 선택 bin과 frame에서 얻은 최대 cross-spectrum 크기의
+`10⁻⁶`배다. 이 floor는 amplitude를 score 가중치로 쓰기 위한 것이 아니라 극소 cross spectrum의
+불안정한 위상 정규화를 제한한다.
+
+선택 band 밖의 bin은 `0`으로 두고 켤레 대칭 spectrum을 IFFT하여 원형 상호상관
+`R_mr,b(ℓ)`을 만든다. scan point마다 far-field 각도 근사가 아니라 정확한 거리 차이로 near-field
+예측 lag를 계산한다.
+
+```text
+ℓ_mr(p) = [r_m(p) - r_r(p)] sampleRate / c
+Q_b(p) = Σ_(m≠r) R_mr,b(ℓ_mr(p))
+K_b = I(0 ∈ b) + I(N/2 ∈ b) + 2 × #(나머지 positive bins)
+S_b(p) = clamp[N Q_b(p) / ((M-1) K_b), 0, 1]
+```
+
+분수 sample lag는 IFFT 원형 상호상관의 이웃 두 sample을 선형 보간한다. Hermitian coefficient
+수 `K_b`, IFFT의 `1/N`, reference pair 수로 나눈 뒤 음의 상관은 0으로 두어 score를 0–1로
+제한한다. 따라서 같은 위치 가설의 raw GCC score는 scan 범위나 마이크 수 자체에 의해 재척도화되지
+않는다. 이 값도 확률이나 calibration된 confidence는 아니다. `rawMap.scale`은 `score`다.
+PHAT가 amplitude를 제거하므로 sensitivity와 frequency-response gain은 역보정하지 않고 알려진
+phase만 보정한다. 현재 CPU만 지원하고 두 개 이상의 마이크가 필요하다.
+
+GCC-PHAT는 broadband 신호에서 상호상관 peak가 뚜렷할수록 유리하다. 단일 tone이나 지나치게 좁은
+band는 주기적으로 같은 phase를 만드는 lag가 많아 여러 위치가 비슷한 score를 얻을 수 있고,
+반사음이나 여러 동시 음원은 pair 상관 peak를 섞는다. IFFT 길이가 `N = fftSize`이면 표현 가능한
+비중첩 원형 lag는 절대값 `N/2` sample까지다. 현재 구현은 scan 전체의 최대 기하학적 lag가 이를
+넘으면 실행을 거절하므로 FFT를 키우거나 array aperture 또는 scan 범위를 줄여야 한다. 이 검사는
+원형 lag alias를 막지만 좁은 대역의 주기적 모호성, 제한된 bandwidth, scan grid 양자화까지
+해결하지는 않는다. 단일 reference 방식은 모든 마이크 쌍을 쓰는 GCC보다 계산량이 작지만 pair
+중복성과 고장 마이크에 대한 강건성도 작다.
## 마이크 주파수 응답
@@ -126,11 +209,17 @@ preset을 적용한다.
보간하고 측정 범위 밖에서는 가장 가까운 끝점 값을 유지한다. 합성기는 이 curve를 실제 마이크
채널에 적용한다.
-Experiment에서 `beamforming.compensateFrequencyResponse`를 생략하거나 `true`로 두면
-CPU와 WebGPU DAS가 선택 FFT bin에서 알려진 curve의 역 gain/phase를 적용한다. 응답 notch의
-무제한 역보정은 noise까지 크게 증폭할 수 있으므로 양의 보상 boost는
-`beamforming.maxCompensationDb`로 제한하며 기본값은 24 dB다. 허용 범위는 0–120 dB이고
-응답 peak를 줄이는 attenuation과 phase 보상은 이 boost 상한에 의해 잘리지 않는다.
+합성기는 알고리즘과 무관하게 sensitivity, 정적 phase offset과 frequency-response curve를 수신
+channel에 적용한다. 위치 추정에서의 역보정은 알고리즘별로 다르다.
+
+- DAS는 sensitivity 역 gain과 정적 phase를 항상 보정한다. `compensateFrequencyResponse`를
+ 생략하거나 `true`로 두면 response의 역 gain과 phase도 보정한다. 응답 notch의 무제한 gain
+ boost는 noise까지 키울 수 있으므로 `maxCompensationDb`로 제한하며 기본값은 24 dB, 허용 범위는
+ 0–120 dB다. 응답 peak를 줄이는 attenuation과 phase 보상에는 이 boost 상한을 적용하지 않는다.
+- DAS-PHAT와 GCC-PHAT는 amplitude를 정규화하므로 sensitivity와 response gain의 역보정을
+ 사용하지 않는다. 정적 phase는 항상 보정하고, `compensateFrequencyResponse`가 `true`이면 알려진
+ response phase도 보정한다. 따라서 `maxCompensationDb`는 두 PHAT 알고리즘의 score에는 영향을
+ 주지 않는다.
```json
{
@@ -147,8 +236,10 @@ CPU와 WebGPU DAS가 선택 FFT bin에서 알려진 curve의 역 gain/phase를
```
Camera의 **마이크 주파수 응답 처리**에서 **측정 curve 적용 + 보정하지 않음**을 고르면 합성에는
-curve가 남고 DAS 역보정만 꺼진다. 같은 배열에서 보정 유무의 robustness를 비교할 때 사용한다.
-curve가 없는 마이크는 0 dB, 0°로 취급한다.
+curve가 남고 위치 추정의 response gain/phase 역보정만 꺼진다. DAS-PHAT와 GCC-PHAT는 여전히
+amplitude를 버리지만, curve가 신호 대 잡음과 relative floor 통과 여부에 준 영향까지 되돌리지는
+못한다. 같은 배열에서 알려진 response 보정 유무의 robustness를 비교할 때 사용한다. curve가 없는
+마이크는 0 dB, 0°로 취급한다.
## 결과에서 확인할 값
@@ -162,6 +253,10 @@ Result `signalStats`에는 다음 선택 필드가 들어간다.
같은 입력과 seed에서 환경 preset만 바꿨다면 이 값과 위치 오차·precision/recall·PSR을 함께
비교할 수 있다.
+Result 최상위 `algorithm`은 실제 실행한 `das`, `das-phat` 또는 `gcc-phat`를 기록한다. band의
+`rawMap.scale`은 DAS에서 `power`, 두 PHAT 방식에서 `score`다. peak 정규화된 `displayMap`은
+시각 비교용일 뿐 어느 scale도 확률분포나 신뢰구간으로 바꾸지 않는다.
+
## 정확도 범위와 제외 사항
이 구현은 FDTD/FEM/BEM이나 room impulse response 측정치를 대신하지 않는다. 다음 항목은
@@ -175,8 +270,9 @@ Result `signalStats`에는 다음 선택 필드가 들어간다.
- preamp, anti-alias filter, ADC quantization, channel clock jitter·drift
- 실제 장착 구조와 마이크 위치 공차
-따라서 preset의 RT60이나 wall gain은 특정 방을 인증하는 값이 아니라 배열과 DAS가 multipath에
-얼마나 민감한지 비교하는 조건이다. 실제 장치 성능을 평가할 때는 같은 Array/scan/band 설정으로
+따라서 preset의 RT60이나 wall gain은 특정 방을 인증하는 값이 아니라 배열과 위치 추정 알고리즘이
+multipath에 얼마나 민감한지 비교하는 조건이다. 실제 장치 성능을 평가할 때는 같은
+Array/scan/band 설정으로
동기식 실측 채널을 [실시간 위치추정 코어](realtime-porting.md)에 입력하고 합성 결과와 나란히
비교해야 한다. 주파수 응답 curve만으로 noise floor, distortion, ADC와 clock 오차까지 모사되지는
않는다.
diff --git a/docs/benchmarks/README.md b/docs/benchmarks/README.md
index 83605e5..57122b0 100644
--- a/docs/benchmarks/README.md
+++ b/docs/benchmarks/README.md
@@ -1,4 +1,4 @@
-# WebGPU 기준 실측
+# WebGPU DAS 기준 실측
2026-08-28에 Chrome 151 headless, Intel Gen9 adapter에서 synthetic 1 kHz fixture를 측정했다. 입력은
48 kHz · 0.25 s, 192 microphones, FFT 512 / hop 256, 900–1100 Hz band이며 16×16 warm-up 뒤 각
@@ -10,6 +10,19 @@
| 100×100 | 176,640,000 | 251.4 ms | 2,102.0 ms | 36.91 MiB | 3.17× |
| 200×200 | 706,560,000 | 891.9 ms | 4,206.7 ms | 80.47 MiB | 11.26× |
+이 표는 conventional DAS의 저장된 기준 측정이며 DAS-PHAT이나 GCC-PHAT의 실측값이 아니다.
+50×50에서 CPU STFT median은 1,669.8 ms로 `end-to-end` 1,818.1 ms의 약 91.8%였고,
+실제 GPU queue 구간은 79.2 ms였다. 별도로 한 번 수행한 192채널 signal 합성은 1,200.5 ms이며
+위 `end-to-end` 열에 포함되지 않는다. 따라서 이 fixture에서는 scan MAC만 줄인 비율을 전체
+Camera 실행 시간 향상으로 해석하면 안 된다.
+
+같은 192채널·50×50·46 frames·2 bins·한 band를 식으로만 비교하면 DAS scan은
+44,160,000 complex MAC이다. 현재 single-reference GCC-PHAT은 공통 forward STFT 뒤
+35,144회의 2-pass cross-spectrum 연산, 191회의 inverse FFT와 477,500회의 point/pair
+lookup을 수행한다. 이는 구현 연산량 비교이지 GCC 실측 benchmark가 아니며, 2 bins는 GCC-PHAT에
+너무 좁어 주기적 지연 모호성이 크다. 정확도·시간 비교에는 band마다 8 bins 이상의 광대역 fixture와
+같은 입력·장치·warm-up 조건을 사용해야 한다.
+
`GPU wall`은 host packing/`writeBuffer()` 호출 직후부터 queue에 들어간 transfer 완료, steering pass,
DAS pass와 readback까지 기다린 시간이다. `end-to-end`에는 이 host 작업과 장치/pipeline 초기화,
distance 생성, CPU STFT가 포함된다. 200×200은 64 MiB steering buffer 목표를 지키면서 두 FFT bin을
diff --git a/docs/data-contracts.md b/docs/data-contracts.md
index 0ab0584..b6bbc40 100644
--- a/docs/data-contracts.md
+++ b/docs/data-contracts.md
@@ -8,7 +8,7 @@ micarrsim의 저장 형식은 JSON Schema Draft 2020-12와 추가 관계 검증
- `micarrsim.array`: aperture와 마이크별 `[x, y, z]` 좌표
- `micarrsim.scene`: 음원·ambient recipe, 위치 ground truth와 로컬 asset manifest
-- `micarrsim.experiment`: scan plane/grid, 주파수 대역, STFT와 DAS 설정
+- `micarrsim.experiment`: scan plane/grid, 주파수 대역, STFT와 위치 추정 설정
- `micarrsim.result`: raw/display acoustic map, 복수 peak와 source 매칭, 평가·성능 정보
- `micarrsim.comparison-history`: 세션의 배열별 Result 요약을 모은 편의 export. core schema
검증 대상은 아니며 각 원본 Result JSON을 대신하지 않는다.
@@ -18,17 +18,36 @@ scene JSON에는 192채널 원시 sample이나 원본 asset byte를 넣지 않
`sha256` manifest로 참조한다. 사용자 컴퓨터의 절대 경로, URL, `..` 경로는 저장하지 않는다.
array의 `sensitivityDb`와 `phaseOffsetDeg`는 각 마이크 수신 channel에 적용되는 편차다. 합성기는
-이를 신호에 주입하고, beamformer는 알려진 calibration 값의 역 gain/phase를 적용한다. 선택적
+이를 신호에 주입하고, 위치 추정기는 선택한 알고리즘의 규칙에 따라 알려진 calibration을 보정한다.
+선택적
`frequencyResponse`는 2–64개의 `{ frequencyHz, gainDb, phaseDeg? }` 측정점이다.
주파수는 0–1,000,000 Hz에서 중복 없이 증가해야 하며, dB와 wrapped phase를 구간 선형 보간하고
측정 범위 밖에서는 가까운 끝점을 사용한다.
-Experiment의 선택적 `beamforming.compensateFrequencyResponse`는 기본 `true`다. 합성
-channel에는 curve가 항상 적용되며, `true`이면 CPU/WebGPU DAS가 선택 bin에서 curve의
-역 gain/phase도 적용한다. `false`이면 sensitivity/phase calibration은 유지하고 curve
-역보정만 끈다. 선택적 `beamforming.maxCompensationDb`는 응답 notch의 역보정에서 양의
-gain boost만 제한하는 0–120 dB 값이며 생략 시 24 dB다. 응답 peak를 줄이는 attenuation과
-phase 보상은 이 boost 상한을 그대로 적용하지 않는다.
+Experiment의 필수 `beamforming.algorithm` enum은 `das`, `das-phat`, `gcc-phat`다. backend와
+raw map 계약은 다음과 같다.
+
+| algorithm | `rawMap.scale` | 허용 backend |
+| --- | --- | --- |
+| `das` | `power` | `cpu`, `webgpu`; `auto` 가능 |
+| `das-phat` | `score` | `cpu`, `webgpu`; `auto` 가능 |
+| `gcc-phat` | `score` | `cpu`; `auto`는 CPU 선택 |
+
+`gcc-phat`에 명시적 `webgpu`를 조합하면 검증 오류이며, 마이크가 두 개 이상이어야 한다. 필드
+그룹 이름 `beamforming`은 기존 계약과의 호환을 위해 유지하지만 세 알고리즘을 모두 선택하는 위치
+추정 설정이다.
+
+선택적 `beamforming.compensateFrequencyResponse`는 기본 `true`다. 합성 channel에는 curve가
+항상 적용된다. DAS는 sensitivity와 정적 phase를 항상 역보정하고, 값이 `true`이면 선택 bin에서
+curve의 gain과 phase도 역보정한다. DAS-PHAT와 GCC-PHAT는 입력과 calibration의 amplitude gain을
+사용하지 않지만 정적 phase를 보정하며, 값이 `true`이면 알려진 curve phase도 보정한다. `false`는
+모든 알고리즘에서 curve 보정만 끄고 정적 phase 보정은 유지한다.
+
+선택적 `beamforming.maxCompensationDb`는 DAS가 응답 notch를 역보정할 때 양의 gain boost만
+제한하는 0–120 dB 값이며 생략 시 24 dB다. 응답 peak를 줄이는 attenuation과 phase 보상은 이
+boost 상한의 대상이 아니다. amplitude를 사용하지 않는 DAS-PHAT와 GCC-PHAT에는 이 값이 영향을
+주지 않는다. 세 알고리즘의 계산식과 한계는
+[음향 환경과 마이크 응답 모델](acoustic-model.md)에 설명한다.
## 로컬 asset 재연결
@@ -80,24 +99,55 @@ room 안의 min/max, 고유 id, -120–0 dB의 `transmissionDb`와
gain을 보존하고, `peak`는 합성 전 peak를 `targetPeak`에 맞춘다. result의 `signalStats`에는
정규화 전 peak, 실제 scale, scale 적용 후에도 범위를 넘은 sample 수를 기록한다.
-이는 acoustic map 표시 정규화와 별개다. result의 `rawMap`은 반드시 선형 power이고,
-`displayMap`은 peak-normalized 값 또는 dB 값일 수 있다. 후처리 결과는 `processedMaps`에 넣어
-raw DAS map을 덮어쓰지 않는다.
+이는 acoustic map 표시 정규화와 별개다. result의 `rawMap.scale`은 알고리즘의 원래 비음수 map을
+뜻하는 `power` 또는 `score`다. DAS의 `power`는 절대 SPL로 calibration한 값이 아니며,
+DAS-PHAT와 GCC-PHAT의 `score`도 확률, likelihood 또는 confidence가 아니다. 합이 1이 되도록
+정규화되지 않으며 서로 다른 scale이나 알고리즘의 절대값을 직접 비교해서는 안 된다.
+GCC-PHAT raw score는 pair 수·band의 Hermitian coefficient 수·IFFT 크기로 나눈 0–1 양의
+상관 응답이므로 scan 범위를 바꿔도 같은 위치 가설의 척도가 유지된다. 그래도 확률 calibration은
+아니며 DAS-PHAT score와 절대값을 비교하는 용도가 아니다.
+
+`displayMap`은 원래 scale, peak-normalized `normalized` 또는 `db`일 수 있다. peak 정규화는
+최댓값을 1로 만드는 표시 변환일 뿐 확률 정규화가 아니다. `processedMaps`는 `damas`, `clean-sc`
+또는 `custom` 후처리 결과를 별도로 교환하기 위한 예약 슬롯이다. 현재 위치 추정 코어는 DAMAS나
+CLEAN-SC를 실행하거나 이 필드를 자동 생성하지 않으며, raw map을 후처리 map으로 덮어쓰지 않는다.
map은 row-major 배열이다. 첫 행은 `yMax`, 마지막 행은 `yMin`, 각 행은 `xMin`에서 `xMax`
방향이다. `values.length`는 반드시 `rows × columns`와 같아야 한다.
-Experiment의 선택적 `evaluation.detectionThresholdRatio`는 최강 local peak power에 대한 검출
-임계 비율이며 `0 < ratio <= 1`이다. 생략된 이전 문서를 Camera에서 열면 기본값 `0.25`를 적용하며,
-Experiment JSON을 다시 저장하면 이 값이 명시된다. 값을 낮추면 약한 peak 후보를 포함하지만
+Experiment의 선택적 `evaluation.detectionThresholdRatio`는 최강 local peak의 같은-scale raw
+값에 대한 검출 임계 비율이며 `0 < ratio <= 1`이다. 생략된 이전 문서를 Camera에서 열면 기본값
+`0.25`를 적용하며, Experiment JSON을 다시 저장하면 이 값이 명시된다. 값을 낮추면 약한 peak 후보를 포함하지만
sidelobe false positive도 늘 수 있다.
## Result 평가 정보
-각 band의 `peaks`는 위치와 power 값, 선택적 `sourceId`, `localizationErrorM`,
-`localizationErrorPixels`, `beamSpread`를 저장한다. `beamSpread`는 선택 peak를 포함하는 연결된
-half-power 성분의 power 가중 centroid·공분산과 major/minor RMS 반경·각도다. `calibrated`는 현재
-항상 `false`이며 확률분포, Gaussian 신뢰구간 또는 통계적 위치 불확실성으로 해석하지 않는다.
+새 Result는 최상위 `algorithm`에 실제 실행한 `das`, `das-phat` 또는 `gcc-phat`를 기록하며
+Experiment의 선택값과 같아야 한다. 이 필드는 이전 `1.0.0` Result를 읽기 위해 schema상 선택적으로
+남아 있지만 현재 export에는 항상 들어간다. `backend`는 요청값 `auto`가 아니라 실제 사용한
+`cpu` 또는 `webgpu`다.
+
+관계 검증은 `das → power`, `das-phat/gcc-phat → score`를 강제한다. Peak에 `beamSpread`가
+있으면 power map은 power-weighted method, score map은 score-weighted method여야 한다. 따라서
+구조상 유효하지만 의미가 모순된 외부 Result도 import 시 거절된다.
+
+`timingMs.localization`은 선택한 위치 추정 알고리즘의 wall time이다. 현재 export는 기존 DAS 전용
+소비자를 위해 같은 값을 `timingMs.beamforming`에도 호환 alias로 기록한다. `1.0.0` schema는 이전
+문서를 위해 `beamforming`을 여전히 필수로 두므로, 새 writer는 `localization`과 `beamforming`을
+함께 저장해야 한다. `timingMs.total`은 전체 시간이고 선택적 `signalGeneration`은 합성 시간이다.
+
+각 band의 `peaks`는 위치와 해당 `rawMap.scale`의 값, 선택적 `sourceId`, `localizationErrorM`,
+`localizationErrorPixels`, `beamSpread`를 저장한다. `beamSpread`는 선택 peak를 포함하는 8-connected
+성분의 가중 centroid·공분산과 major/minor RMS 반경·각도다.
+
+- `power` map은 `connected-half-power-power-weighted-covariance`를 사용해 peak의
+ `halfPowerRatio` 이상인 연결 성분을 power로 가중한다.
+- `score` map은 `connected-half-maximum-score-weighted-covariance`를 사용해 peak의 같은 비율
+ 이상인 연결 성분을 score로 가중한다. 호환 필드명 `halfPowerRatio`는 이 경우 실제로
+ half-maximum score 비율을 뜻한다.
+
+두 방식 모두 `calibrated`는 현재 항상 `false`다. covariance ellipse는 map의 공간적 퍼짐을
+결정적으로 요약할 뿐 확률분포, Gaussian 신뢰구간 또는 통계적 위치 불확실성이 아니다.
`metrics`에는 다음 유한값만 기록한다.
- `peakToSidelobeDb`
@@ -105,6 +155,9 @@ half-power 성분의 power 가중 centroid·공분산과 major/minor RMS 반경
- `precision`, `recall`
- `mainLobeWidthM`
+호환 필드명 `energyInsideTruthRatio`는 `power` map에서는 truth 주변 power 비율, `score` map에서는
+truth 주변 score 비율이다. 후자의 값은 물리적 energy 비율이나 위치 확률이 아니다.
+
`evaluation`은 지표를 다시 해석할 수 있도록 truth 반경, source 매칭 tolerance, peak 검출
threshold와 최소 거리, sidelobe 제외 반경, half-power 비율, dynamic range, 최대 검출 수를
버전과 함께 저장한다. `environment`에는 실제 backend, microphone/grid 수, scan bounds와 z,
@@ -146,7 +199,8 @@ CPU 분석 계획은 기본 512 MiB, 반복 steering cache는 64 MiB, streaming
256 MiB hard cap을 할당 전에 적용한다. 채널 순서와 streaming 상태 계약은
[실측 다채널 위치추정 코어 포팅](realtime-porting.md)을 참고한다.
-portable block 결과의 `processingTimeMs`는 DAS와 local-peak 검출 wall time이다. Streaming
+portable block 결과의 `processingTimeMs`는 선택한 위치 추정 알고리즘과 local-peak 검출 wall
+time이다. Streaming
결과는 `deadlineMs`와 `deadlineMet`을 추가하고 상태에는 최근·최대 처리 시간,
`deadlineMisses`와 `droppedSamples`를 누적한다. 이 runtime telemetry는
`micarrsim.result` 저장 스키마의 필수 필드가 아니다.
diff --git a/docs/execution-plan.md b/docs/execution-plan.md
index 80031ff..929dd36 100644
--- a/docs/execution-plan.md
+++ b/docs/execution-plan.md
@@ -2,8 +2,8 @@
> 구현 상태 · 2026-08-28
>
-> 단계 0–4와 단계 5의 저비용 음향 환경·마이크 응답·portable streaming core까지 완료했다.
-> 설치 없는 브라우저 앱, CPU/WebGPU DAS, Builder→Camera asset 재연결, 임의 오디오·이동
+> 단계 0–4와 단계 5의 저비용 음향 환경·마이크 응답·다중 알고리즘 portable streaming core까지 완료했다.
+> 설치 없는 브라우저 앱, CPU/WebGPU DAS·DAS-PHAT와 CPU GCC-PHAT, Builder→Camera asset 재연결, 임의 오디오·이동
> trajectory, 빔 분포·복수 peak 감도, 라이브 시간축 재생, 배열 비교와 Result 내보내기를
> 전체 자동 테스트로 검증했다. 실제 장치 adapter와 현장 검증은 장치 계약이 필요한 후속 범위다.
@@ -15,7 +15,7 @@
1. 기계음 데이터와 ambient audio를 조합해 장면 및 ground truth를 생성한다.
2. 배열 JSON과 장면 정보를 사용해 각 마이크에 도달하는 신호를 합성한다.
-3. WGSL로 conventional delay-and-sum(DAS) 빔포밍을 수행한다.
+3. DAS, DAS-PHAT 또는 GCC-PHAT로 위치 map을 계산하고 같은 입력에서 비교한다.
4. 주파수 구간별 acoustic image를 ground truth 위에 표시하고 정량 평가한다.
5. 배열, 거리, 음원, 주파수, grid 해상도를 바꾼 실험을 저장하고 비교한다.
@@ -79,17 +79,21 @@ tap 수가 제한된 RT60 tail만 추가한다. 경로를 source–microphone
DAS는 STFT 후 각 scan point와 마이크 사이 거리로 steering phase를 만들고, 선택된 주파수 bin의
정렬 합 power를 계산한다. 여러 주파수 구간은 각각 독립 map과 색을 갖는다. CPU reference를 먼저
-작성한 뒤 같은 buffer layout으로 WGSL 결과를 대조한다. DAMAS, CLEAN-SC는 인터페이스만 마련하고
-초기 완료 범위에서는 제외한다.
+작성한 뒤 같은 buffer layout으로 WGSL 결과를 대조한다. DAS-PHAT은 각 frame/bin에서 마이크
+spectrum의 진폭을 제거하되 알려진 응답 phase를 보정한다. GCC-PHAT은 한 reference와 나머지
+마이크의 band-limited PHAT 상관을 IFFT하고 scan point의 near-field 예상 지연에서 보간한다.
+DAMAS, CLEAN-SC는 초기 완료 범위에서 제외한다.
-연산량의 주항은 대략 `O(P × M × F × T)`다. `M=192`, `P=50²=2,500`일 때 STFT
-frame·주파수 bin 하나당
-약 48만 개의 복소 steering 누산이 필요하다. 100×100은 50×50보다 4배, 200×200은 16배이므로
-장치별 실제 GPU timing을 UI에 표시하고 목표 frame budget에 맞춰 해상도를 제한한다. 정확한 성능과
-메모리는 대표 GPU에서 benchmark하여 기록하며 이론치만으로 약속하지 않는다.
+`P` points, `M` microphones, `T__ frames, `F` selected bins에서 DAS 계열 scan은
+`O(P × M × T × F)`다. GCC-PHAT은 공통 forward STFT 외에 2-pass cross spectrum
+`2(M-1)TΣF_b`, `(M-1)B` inverse FFT와 `P(M-1)B` lookup을 수행한다.
+192채널·50×50·46 frames·2 bins의 DAS는 44,160,000 complex MAC이고 같은 한 band
+GCC scan은 477,500 lookup이다. 다만 작은 grid에서는 공통 STFT가 지배할 수 있으므로 이 비율을
+end-to-end 속도 향상으로 해석하지 않는다. 100×100은 50×50보다 4배, 200×200은 16배 points이며
+장치별 실제 timing과 메모리를 기록하고 이론치만으로 실시간성을 약속하지 않는다.
-실측·반복 분석용 CPU 계획은 거리 행렬, bin calibration, 임시 map과 허용되는 steering matrix를
-재사용한다. 192채널·50×50·2 bins를 10회 분석하는 기준에서 거리 계산은 4,800,000회에서
+실측·반복 분석용 CPU 계획은 거리 행렬과 workspace를 재사용하고, DAS 계열은 bin calibration과
+허용되는 steering matrix도 재사용한다. 192채널·50×50·2 bins를 10회 DAS 분석하는 기준에서 거리 계산은 4,800,000회에서
480,000회, steering pair는 9,600,000회에서 960,000회로 줄어든다. snapshot working set과 계획은
512 MiB, steering cache는 64 MiB, streaming input은 256 MiB 기본 hard cap을 할당 전에 적용한다.
Streaming ring은 backlog에서 가장 오래된 sample을 버리고 호출당 결과 수를 제한한다.
@@ -176,20 +180,21 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
- peak별 half-power 공분산/RMS 빔 분포와 재현 가능한 검출 임계값
- sample-aligned 준정적 시간창 분석, bounded map 계획과 이동 장면 재생
-완료 조건: 사용자가 코드 수정 없이 배열 JSON 두 개를 같은 장면에서 비교할 수 있다.
+완료 조건: 사용자가 코드 수정 없이 배열 JSON과 위치 추정 알고리즘을 같은 장면에서 비교할 수 있다.
### 단계 5 — 현실성·포팅과 실제 데이터 · 부분 완료
-- [x] 마이크별 gain·phase·frequency-response 합성 및 선택적 DAS 역보정
+- [x] 마이크별 gain·phase·frequency-response 합성과 알고리즘별 amplitude/phase 보정
- [x] 자유음장을 보존하는 선택형 shoebox 1차 반사·scattering·회절·RT60 tail
- [x] Scene 합성에서 분리한 block/streaming multichannel 위치추정 API
- [x] 반복 거리·calibration·steering cache, bounded ring과 메모리 hard cap
+- [x] DAS·DAS-PHAT·reference-pair GCC-PHAT와 알고리즘별 power/score 계약
- [ ] 라이선스 검토를 통과한 DCASE/MIMII adapter와 batch workflow
- [ ] 실제 장치의 channel/clock/sample 형식에 맞춘 capture·transport adapter와 현장 검증
- [ ] 필요 시 센서 self-noise·위치 공차와 DAMAS/CLEAN-SC 후처리
현재 완료 조건은 기존 자유음장 결과를 유지한 채 propagation model과 응답 보상을 선택해
-재실행하고, 같은 CPU DAS를 실측 `Float32Array` channel에 직접 적용하는 것이다. 실제
+재실행하고, 선택한 CPU 위치 추정기를 실측 `Float32Array` channel에 직접 적용하는 것이다. 실제
hardware deadline과 정확도는 장치 adapter가 정해진 뒤 별도 측정한다.
## 9. 주요 위험과 대응
@@ -203,7 +208,7 @@ hardware deadline과 정확도는 장치 adapter가 정해진 뒤 별도 측정
쓴다. 실제 데이터 adapter는 단계 5의 라이선스 검토 뒤 추가한다.
- 실제 AC와 simulation gap: gain·phase·주파수 응답과 희소 실내 환경을 비교 조건으로 제공하되,
위치 공차·self-noise·ADC·clock은 실측 adapter와 현장 검증으로 다룬다.
-- post-processing이 기본 결과를 가림: raw DAS map과 후처리 map을 항상 별도로 보존한다.
+- post-processing이 기본 결과를 가림: 알고리즘별 raw power/score map과 후처리 map을 별도로 보존한다.
## 10. 구현 결과와 다음 백로그
@@ -211,6 +216,7 @@ hardware deadline과 정확도는 장치 adapter가 정해진 뒤 별도 측정
- [x] array/scene/experiment/result schema와 synthetic fixture
- [x] 좌표·거리·구면 감쇠·분수 지연·STFT 단위 테스트
- [x] CPU DAS golden implementation
+- [x] CPU/WebGPU DAS-PHAT와 CPU reference-pair GCC-PHAT 구현·비교 UI
- [x] WGSL kernel, CPU/GPU 비교와 50²/100²/200² benchmark
- [x] scene canvas, array view, parameter controls와 Result export
- [x] Builder→Camera SHA-256 asset 재연결 왕복 테스트
@@ -220,7 +226,7 @@ hardware deadline과 정확도는 장치 adapter가 정해진 뒤 별도 측정
- [x] 복수 음원용 Experiment peak 검출 감도
- [x] 마이크 주파수 응답 합성·CPU/WebGPU 역보정 비교
- [x] 자유음장·shoebox 희소 반사/산란/회절/RT60 환경 선택
-- [x] portable block/streaming localizer와 반복 DAS cache·메모리 상한
+- [x] portable block/streaming localizer와 알고리즘별 반복 plan·메모리 상한
- [x] Camera 입력→설정→실행→확인의 Snapshot/Moving UI 재구성
- [x] `test.cmd` one-command headless browser 검증
- [ ] DCASE/MIMII 라이선스 검토와 adapter
@@ -229,5 +235,5 @@ hardware deadline과 정확도는 장치 adapter가 정해진 뒤 별도 측정
- [ ] DAMAS/CLEAN-SC 후처리
정확성 test를 먼저 통과시킨 뒤 UI 또는 최적화를 진행한다는 원칙은 유지한다. 추가 현실성 모델은
-자유음장 DAS 결과를 바꾸지 않는 선택형 확장으로, 장치 연결은 portable localizer 앞의 adapter로
+자유음장 DAS 기준 결과를 바꾸지 않는 선택형 확장으로, 장치 연결은 portable localizer 앞의 adapter로
추가한다.
diff --git a/docs/index.html b/docs/index.html
index 82c7277..6088568 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -112,26 +112,48 @@
Compute backend Localization & compute
+ Camera의 위치 추정 알고리즘에서 실행할 방법을 고릅니다. 처음에는
+ DAS로 전체 흐름을 확인한 뒤 같은 scene·scan·STFT에서 PHAT 계열을 실행해 비교하세요.
+
- 먼저 50×50을 권장합니다. 100×100은 4배, 200×200은 16배의 scan point를 계산합니다.
- 실행 중에는 취소할 수 있으며, 취소하거나 실패해도 이전 완료 결과는 유지됩니다.
+ GCC-PHAT는 CPU 전용이므로 Auto가 자동으로 CPU를 사용하고 WebGPU 강제는 비활성화됩니다.
+ 각 band가 8개 미만의 FFT bin을 포함하면 협대역 상관 경고가 나오므로 band 폭이나 FFT
+ 설정을 확인하세요.
- 복수 음원에서 약한 peak가 빠지면 Peak 검출 임계값을 기본 25%에서
- 10–20%로 낮춰 보세요. 약한 후보와 함께 sidelobe 오검출도 늘 수 있으며, main lobe보다
- 가까운 같은 대역 음원은 conventional DAS에서 하나로 합쳐질 수 있습니다.
+ 먼저 50×50을 권장합니다. 100×100은 4배, 200×200은 16배의 scan point를 계산합니다.
+ 연산량 줄은 공통 STFT transform과 함께 DAS/DAS-PHAT의 complex MAC, 또는 GCC-PHAT의
+ two-pass cross-spectrum·pair×band inverse FFT·scan correlation lookup을 분리해
+ 표시합니다. 이는 실행 시간 예측값이 아니며 장치와 입력에 따라 달라집니다. 실행 중에는
+ 취소할 수 있고, 취소하거나 실패해도 이전 완료 결과는 유지됩니다.
+
+ 복수 음원에서 약한 peak가 빠지면 Peak 검출 임계값을 최강 map 값의
+ 기본 25%에서 10–20%로 낮춰 보세요. 약한 후보와 함께 sidelobe 오검출도 늘 수 있으며,
+ main lobe보다 가까운 같은 대역 음원은 어느 알고리즘에서도 하나로 합쳐질 수 있습니다.
속도와 격자 선택
+ 알고리즘, 속도와 격자 선택
+
+
+
+
+ 알고리즘 raw map Backend 특징
+ DAS 선형 power CPU · WebGPU 진폭과 위상을 유지하는 기준선
+ DAS-PHAT 무차원 score CPU · WebGPU STFT 크기를 정규화한 위상 기반 합산
+
+ GCC-PHAT 무차원 score CPU 기준 마이크 pair의 TDOA 상관
선택 동작 권장 상황
- Auto (GPU→CPU) WebGPU를 먼저 시도하고 불가능하면 CPU 사용 기본 권장
- WebGPU 강제 GPU 장치가 없거나 실패하면 fallback 없이 오류 GPU 비교·측정
+ CPU 강제 동일한 conventional DAS를 CPU에서 계산 호환성 확인·기준 비교
+ WebGPU 강제 DAS/DAS-PHAT를 GPU에서만 실행; 실패하면 fallback 없이 오류 GPU 비교·측정 CPU 강제 선택한 알고리즘을 CPU에서 계산 호환성 확인·기준 비교
고급 설정에서 자유음장 또는 흡음·일반·반사 강한 실내 preset을 고릅니다. 실내 모델은 1차 반사, scattering, occluder 회절과 제한된 RT60 tail을 희소 경로로 근사합니다. - Array의 마이크 응답 curve는 수신 신호에 적용하며 DAS 역보정 유무를 비교할 수 있습니다. + Array의 마이크 응답 curve는 수신 신호에 적용하며 위치추정 역보정 유무를 비교할 수 있습니다. 값과 한계는 음향 모델 설명에서 확인하세요.
- 실제 동기식 마이크 채널은 Scene 합성 없이 portable CPU DAS에 넣을 수 있습니다.
+ 실제 동기식 마이크 채널은 Scene 합성 없이 portable CPU 위치추정 코어에 넣고
+ DAS·DAS-PHAT·GCC-PHAT를 선택할 수 있습니다.
장치별 channel·clock·transport adapter는 별도로 필요하며 실시간 deadline은 해당
장치에서 processingTimeMs ≤ deadlineMs와 miss·drop 누계로 확인해야
합니다. 포팅 설명서에 입력 형식, ring buffer,
@@ -163,19 +186,26 @@
Result JSON
- Camera에서 바꾼 Scene과 Experiment는 각각 저장할 수 있습니다. 완료 Result와 배열 비교 + Camera에서 바꾼 Scene과 Experiment는 각각 저장할 수 있습니다. 완료 Result와 비교 기록도 별도 JSON으로 저장하세요. 저장하지 않은 항목이나 실행 중인 계산이 있으면 페이지를 닫을 때 브라우저가 확인합니다.
@@ -213,7 +243,11 @@Backend를 Auto 또는 CPU로 두고 50×50 grid부터 실행하세요. 최신 Chrome/Edge와 그래픽 드라이버도 확인하세요.
+Backend를 Auto 또는 CPU로 두고 50×50 grid부터 실행하세요. GCC-PHAT는 원래 CPU 전용입니다. DAS/DAS-PHAT WebGPU를 쓴다면 최신 Chrome/Edge와 그래픽 드라이버도 확인하세요.
+각 band가 포함하는 FFT bin이 너무 적으면 PHAT 상관이 협대역 주기성에 민감해집니다. 필요한 신호 대역 안에서 band를 넓히거나 FFT size를 조정하세요.
micarrsim 1.0은 점 음원의 자유음장과 계산량을 제한한 shoebox 희소 경로, 마이크 - 주파수 응답과 conventional DAS를 제공합니다. 파동 방정식·고차 반사·재질별 흡음, - 복잡한 mesh, sensor self-noise·비선형, ADC·clock, DAMAS/CLEAN-SC와 자동 장치 - capture adapter는 포함하지 않습니다. 결과는 장비의 절대 성능 보증이 아니라 같은 - 조건에서 배열과 알고리즘을 비교하는 재현 가능한 기준입니다. + 주파수 응답과 DAS·DAS-PHAT·GCC-PHAT를 제공합니다. 파동 방정식·고차 반사·재질별 + 흡음, 복잡한 mesh, sensor self-noise·비선형, ADC·clock, DAMAS/CLEAN-SC 후처리와 + 자동 장치 capture adapter는 포함하지 않습니다. 실행할 수 없는 DAMAS/CLEAN-SC + placeholder는 Camera UI에서 제거했습니다. 결과는 장비의 절대 성능 보증이 아니라 + 같은 조건에서 배열과 알고리즘을 비교하는 재현 가능한 기준입니다.
원본 asset은 브라우저 메모리에서만 읽고 서버나 외부 네트워크로 보내지 않습니다. diff --git a/docs/realtime-porting.md b/docs/realtime-porting.md index e992b1a..9e1c37d 100644 --- a/docs/realtime-porting.md +++ b/docs/realtime-porting.md @@ -1,7 +1,7 @@ # 실측 다채널 위치추정 코어 포팅 `packages/acoustics-core/realtime-localizer.js`는 Scene 생성이나 음향 합성 없이 이미 sampling된 -동기식 마이크 채널을 conventional CPU DAS에 직접 넣는다. 브라우저 DOM과 외부 package에 +동기식 마이크 채널을 CPU DAS·DAS-PHAT·GCC-PHAT 위치 추정에 직접 넣는다. 브라우저 DOM과 외부 package에 의존하지 않는 ES module이므로, 실제 장치 adapter 뒤에 붙이거나 다른 프로젝트로 분리할 수 있다. 이 API는 ADC와 transport를 제어하지 않는다. 입력 channel을 획득·정렬·동기화한 다음 위치 map과 @@ -9,13 +9,16 @@ ## 최소 복사 단위 -직접 경로로 `realtime-localizer.js`를 import한다면 다음 여덟 파일과 상대 경로를 함께 +직접 경로로 `realtime-localizer.js`를 import한다면 다음 열한 파일과 상대 경로를 함께 복사하면 된다. ```text acoustics-core/ realtime-localizer.js + localization.js + localization-algorithms.js das.js + gcc-phat.js stft.js fft.js math.js @@ -24,8 +27,10 @@ acoustics-core/ evaluation.js ``` -`index.js`는 전체 core의 편의 export이므로 최소 포팅에는 필요하지 않다. 현재 portable API는 -CPU 기준 구현이며 Camera의 WebGPU worker나 Scene·propagation 모듈을 요구하지 않는다. +`index.js`는 전체 core의 편의 export이므로 최소 포팅에는 필요하지 않다. Portable API와 +`localization.js`의 analysis plan은 CPU 기준 구현이며 Camera의 WebGPU worker나 Scene·propagation +모듈을 요구하지 않는다. Camera backend 통합은 DAS와 DAS-PHAT에 WebGPU를 제공하지만 GCC-PHAT는 +CPU only다. GCC-PHAT에 명시적 WebGPU를 요청하면 오류가 나며 `auto`는 CPU를 선택한다. ## 입력 계약 @@ -68,6 +73,58 @@ const array = { `beamforming.maxCompensationDb`로 제한하며 기본 24 dB, 허용 범위 0–120 dB다. 응답 peak를 줄이는 attenuation과 phase 보상은 이 boost 상한에 의해 잘리지 않는다. +## 알고리즘 선택과 결과 scale + +Portable plan은 top-level `algorithm`으로 위치 추정기를 선택한다. 이 값을 생략하면 기존 동작과 +결과를 유지하는 `das`다. 기존 구성 형태인 `beamforming.algorithm`도 허용한다. + +| `algorithm` | 핵심 계산 | `rawScale` | portable backend | +| --- | --- | --- | --- | +| `das` | frequency-domain delay-and-sum | `power` | CPU | +| `das-phat` | phase-only spectrum을 steering하는 DAS | `score` | CPU | +| `gcc-phat` | 한 reference mic에 대한 `M-1`개 GCC-PHAT 지연 scan | `score` | CPU only | + +같은 array·scan·band·STFT로 알고리즘을 비교할 때도 알고리즘마다 별도 plan을 만든다. + +```js +const dasPlan = createLocalizationPlan({ + ...commonConfig, + algorithm: "das", +}); + +const dasPhatPlan = createLocalizationPlan({ + ...commonConfig, + algorithm: "das-phat", + phatRelativeFloor: 1e-6, +}); + +const gccPhatPlan = createLocalizationPlan({ + ...commonConfig, + algorithm: "gcc-phat", + phatFloorRatio: 1e-6, +}); +``` + +`phatRelativeFloor`는 DAS-PHAT의 channel spectrum 정규화 floor이고, `phatFloorRatio`는 +GCC-PHAT pair cross spectrum 정규화 floor다. 둘 다 해당 묶음의 최대 magnitude에 대한 0~1 +비율이며 기본값은 `1e-6`이다. GCC-PHAT는 `phatRelativeFloor`도 호환 alias로 받지만 새 코드에서는 +`phatFloorRatio`를 쓰는 편이 의미가 명확하다. 값을 키우면 낮은 magnitude 성분의 기여가 줄어들지만 +너무 크게 두면 유효한 broadband 정보도 약해질 수 있으므로 현장 데이터로 조정한다. + +각 band는 `rawScale`을 반환한다. `rawValue`는 scale에 관계없이 읽을 수 있는 canonical peak +값이다. `rawPower`는 `rawScale === "power"`, `rawScore`는 `rawScale === "score"`인 peak에만 +사용하는 편의 alias이며 두 필드가 항상 함께 있다고 가정하면 안 된다. `score`는 음향 power, +확률 또는 신뢰도가 아니라 동일 알고리즘·설정 안에서 상대 peak를 비교하는 비보정 값이다. +`power`도 capture amplitude와 calibration이 절대 단위로 교정되지 않았다면 W 또는 Pa²가 아니다. +GCC-PHAT의 raw score는 pair 수·band coefficient 수·IFFT 크기로 정규화한 0–1 양의 상관 응답이라 +scan 범위와 microphone 수 자체에 따른 척도 변화는 피하지만, 확률로 보정된 confidence는 아니다. + +GCC-PHAT에는 여러 FFT bin에 에너지가 있는 broadband 입력을 권장한다. Band당 8 bin 미만도 hard +error는 아니지만 단일 tone이나 좁은 band에서는 반복 correlation peak 때문에 위치가 모호할 수 있다. +Plan 생성 시 reference pair와 scan point에서 가능한 최대 절대 기하 지연을 계산하며, 이 값이 +`fftSize / 2` sample을 넘으면 circular alias를 막기 위해 거절한다. 더 큰 `fftSize`를 쓰거나 +배열·scan 기하를 줄여야 하며, 결과의 `maximumLagSamples`로 실제 상한을 기록할 수 있다. + ## 공통 계획 만들기 Array, scan grid, band와 STFT 설정이 바뀌지 않는 동안 계획을 한 번 만들고 재사용한다. @@ -94,6 +151,7 @@ const plan = createLocalizationPlan({ { id: "machine", minHz: 900, maxHz: 1100, color: "#65f2bf" }, ], stft: { fftSize: 512, hopSize: 256 }, + algorithm: "das", // "das" | "das-phat" | "gcc-phat" speedOfSound: 343, normalization: "peak", compensateFrequencyResponse: true, @@ -113,19 +171,63 @@ const plan = createLocalizationPlan({ - scan의 `columns`와 `rows`는 양의 정수 - `speedOfSound`는 양수 - `normalization`은 `peak` 또는 `none` +- `algorithm`은 `das`, `das-phat`, `gcc-phat` 중 하나 +- `phatRelativeFloor`와 `phatFloorRatio`는 지정한 경우 0–1 - `maxCompensationDb`는 0–120 dB - `peakDetection.thresholdRatio`는 0–1, `minimumPeakDistanceM`는 0 이상, `maximumDetections`는 1–128의 정수 -계획은 거리 행렬, bin calibration, 임시 map과 허용되는 경우 steering matrix를 유지하는 -stateful 객체다. 같은 계획으로 두 분석을 동시에 실행하지 말고, worker마다 별도 계획을 만든다. -Array·scan·band·STFT를 바꾸면 새 계획을 만들어야 한다. +계획은 알고리즘별 거리 행렬과 workspace를 유지하는 stateful 객체다. DAS 계열은 bin calibration, +임시 map과 허용되는 경우 steering matrix를 유지하고, GCC-PHAT는 reference mic 선택, +STFT·correlation workspace를 유지한다. 같은 계획으로 두 분석을 동시에 실행하지 말고 worker마다 +별도 계획을 만든다. Array·scan·band·STFT·알고리즘을 바꾸면 새 계획을 만들어야 한다. +Portable plan의 generic cache는 `plan.analysisPlan`이며 기존 호출자를 위해 DAS 계열에는 +`plan.dasPlan`, GCC-PHAT에는 `plan.gccPhatPlan` alias도 제공한다. -portable 계획의 기본 compute batch는 scan point 256개와 STFT transform 128개 단위로 event -loop에 양보한다. `computeOptions.pointBatchSize`와 -`computeOptions.stftTransformBatchSize`로 조정할 수 있지만, 큰 batch는 throughput과 -취소·UI 응답성의 trade-off가 있으므로 장치에서 함께 측정한다. +portable 계획의 기본 scan point batch는 DAS 계열 256개, GCC-PHAT 4,096개이고 STFT transform +batch는 128개다. GCC-PHAT cross-spectrum은 16,384 operation 단위로도 양보한다. 각 batch 경계에서 +event loop에 기회를 주며 `computeOptions.pointBatchSize`, +`computeOptions.stftTransformBatchSize`, `computeOptions.crossSpectrumBatchSize`로 조정할 +수 있다. 큰 batch는 throughput과 취소·UI 응답성의 trade-off가 있으므로 장치에서 함께 측정한다. + +낮은 수준에서 직접 사용할 때는 `localization.js`의 generic plan과 compute 함수를 짝지어 쓴다. +Plan compatibility는 값이 같은 새 객체가 아니라 생성할 때 넘긴 `arrayDefinition`, `scene`, +`experiment`의 정확히 같은 객체를 요구한다. + +```js +import { + computeLocalizationMap, + createLocalizationAnalysisPlan, +} from "./acoustics-core/localization.js"; + +const experiment = { + stft: { fftSize: 512, hopSize: 256, window: "hann" }, + scan, + frequencyBands: bands, + beamforming: { algorithm: "das-phat", normalization: "peak" }, +}; +const scene = { speedOfSound: 343 }; +const analysisPlan = createLocalizationAnalysisPlan({ + sampleRate: 48_000, + arrayDefinition: array, + scene, + experiment, +}); + +for (const channels of capturedBlocks) { + const result = await computeLocalizationMap({ + channels, + sampleRate: 48_000, + arrayDefinition: array, + scene, + experiment, + analysisPlan, + phatRelativeFloor: 1e-6, + }); + consume(result); +} +``` ## 한 블록 분석 @@ -144,22 +246,28 @@ const result = await localizeMultichannel({ }); for (const location of result.locations) { - console.log(location.bandId, location.position, location.rawPower); + console.log( + location.bandId, + location.position, + location.rawScale, + location.rawValue, + ); } ``` `result.locations`는 입력 band별 raw map의 local peak를 flat array로 담는다. 각 band -안에서는 power가 강한 순서이며 항목에는 `bandId`, band 내 `rank`, scan -`index`, `position`과 `rawPower`가 있다. 기본 threshold는 해당 band 최강 -peak의 50%, 최소 peak 간격은 scan cell 대각선, 최대 검출 수는 band당 8개다. 여러 대역을 -사용한다면 `bandId`로 묶어 해석한다. +안에서는 `rawValue`가 큰 순서이며 항목에는 `bandId`, band 내 `rank`, scan `index`, +`position`, `rawValue`, `rawScale`이 있다. DAS의 기존 `rawPower` 필드는 호환 alias로 유지된다. +Score 결과에서는 `rawValue`를 공통 필드로 읽고, `rawScore`가 포함된 peak 객체에서는 같은 값의 +score alias로 사용할 수 있다. 기본 threshold는 해당 band 최강 peak의 50%, 최소 peak 간격은 +scan cell 대각선, 최대 검출 수는 band당 8개다. 여러 대역을 사용한다면 `bandId`로 묶어 해석한다. `peakDetection`은 후처리 위치 목록만 바꾸며 `result.bands[n].rawMap`과 `displayMap`은 그대로 보존한다. 같은 대역의 두 음원이 main lobe보다 가까우면 local peak가 하나로 합쳐질 수 있고, threshold를 낮추면 sidelobe 후보도 늘 수 있다. `sampleCount`와 `duration`도 결과에 함께 반환된다. -Block 결과의 `processingTimeMs`는 DAS 계산과 local-peak 검출에 걸린 monotonic wall +Block 결과의 `processingTimeMs`는 선택한 위치 추정 계산과 local-peak 검출에 걸린 monotonic wall time이다. capture, adapter 변환과 결과 rendering 시간은 포함하지 않으므로 end-to-end latency는 호출자가 별도로 측정한다. @@ -168,13 +276,14 @@ time이다. capture, adapter 변환과 결과 rendering 시간은 포함하지 ## 연속 chunk 처리 -`StreamingDasLocalizer` 또는 같은 factory인 `createStreamingLocalizer`는 고정 용량 -ring buffer와 분석 workspace를 갖는다. +`StreamingLocalizer`는 알고리즘 중립 이름이며 기존 `StreamingDasLocalizer` class의 export alias다. +오래된 이름은 호환을 위해 유지된다. 두 이름과 `createStreamingLocalizer` factory는 모두 선택한 +`algorithm`에 맞는 고정 용량 ring buffer와 분석 workspace를 만든다. ```js -import { createStreamingLocalizer } from "./acoustics-core/realtime-localizer.js"; +import { StreamingLocalizer } from "./acoustics-core/realtime-localizer.js"; -const localizer = createStreamingLocalizer({ +const localizer = new StreamingLocalizer({ sampleRate: 48_000, array, scan: { @@ -190,6 +299,8 @@ const localizer = createStreamingLocalizer({ { id: "machine", minHz: 900, maxHz: 1100, color: "#65f2bf" }, ], stft: { fftSize: 512, hopSize: 256 }, + algorithm: "das-phat", + phatRelativeFloor: 1e-6, analysisWindowSamples: 4_800, analysisHopSamples: 2_400, maximumBufferedSamples: 19_200, @@ -224,6 +335,11 @@ async function analysisLoop(signal) { `deadlineMs = analysisHopSamples / sampleRate × 1000`과 `deadlineMet = processingTimeMs <= deadlineMs`를 반환한다. +`deadlineMet`는 완료된 한 창의 위치 추정 시간이 hop 예산 안이었는지를 나타내는 측정값이지, +해당 구성이 실시간이라는 사전 보장이 아니다. JIT warm-up, GC, 다른 thread의 부하를 포함해 목표 +장치에서 연속 실행을 측정해야 한다. 이 값은 capture·transport·rendering을 포함하지 않으므로 전체 +시스템 deadline은 adapter 경계에서 따로 계측한다. + ## 지연과 backpressure 조정 서로 다른 hop이 두 층에 있다. @@ -268,18 +384,42 @@ async function analysisLoop(signal) { 입력 retained byte는 `채널 수 × (maximumBufferedSamples + analysisWindowSamples) × 4`이며 할당 전에 검사한다. -계획은 거리 행렬, bin calibration, Hann window, Float64 FFT workspace와 임시 map을 재사용한다. -기본 retained data가 `maximumPlanBytes`를 넘으면 계획 생성을 거절한다. 전체 steering -matrix가 64 MiB 또는 계획 전체 한도를 넘으면 선택 bin 앞쪽부터 예산에 들어가는 만큼만 cache하고 -나머지는 고정 microphone workspace에서 필요할 때 계산한다. +모든 계획은 거리 행렬과 STFT workspace를 재사용한다. DAS 계열은 bin calibration, Hann window, +임시 map과 steering cache를, GCC-PHAT는 선택 bin metadata, reference mic와 correlation workspace를 +재사용한다. 기본 retained data가 `maximumPlanBytes`를 넘으면 할당 전에 계획 생성을 거절한다. +Plan 없는 GCC 단발 분석도 거리 행렬 할당 전에 전체 working set을 `maximumWorkingBytes`와 비교한다. +DAS의 전체 steering matrix가 64 MiB 또는 계획 전체 한도를 넘으면 선택 bin 앞쪽부터 예산에 들어가는 +만큼만 cache하고 나머지는 고정 microphone workspace에서 필요할 때 계산한다. -192채널, 50×50 scan, 선택 bin 2개를 같은 계획으로 10회 분석하는 기준에서 구현상 거리 계산은 +기존 DAS 호환 경로에서 192채널, 50×50 scan, 선택 bin 2개를 같은 계획으로 10회 분석하면 거리 계산은 4,800,000회에서 480,000회, steering point–microphone pair는 9,600,000회에서 960,000회로 줄어든다. 17,280,000회의 `sin/cos` 호출을 피하고 약 16.5 MiB를 유지한다. 자동 테스트는 부분 cache와 cache 없음의 raw/display `Float32` map 모든 원소가 bit-exact임을 확인한다. -각 결과의 `analysisCache`에는 cache된 bin capacity와 실제 cached/uncached 사용, -거리·steering pair, STFT transform·FFT butterfly·DAS complex MAC, workspace allocation·reuse -수가 들어가므로 설정별 최적화 효과를 계측할 수 있다. +각 결과의 `analysisCache`에는 공통 거리·STFT 통계와 알고리즘별 작업량이 들어간다. DAS 계열은 +cached/uncached steering pair, FFT butterfly와 complex MAC을, GCC-PHAT는 cross-spectrum +operation, pair별 IFFT, scan lookup과 correlation workspace reuse를 기록한다. + +연산량을 비교할 때 다음 기호를 사용한다. + +- `P`: scan point 수 +- `M`: microphone 수 +- `T`: 한 분석창의 STFT frame 수 +- `F`: 선택한 고유 FFT bin 수 +- `F_b`: band `b`의 bin 수, `B`: band 수 +- `N`: `fftSize` + +DAS와 DAS-PHAT의 지배적인 steering 누산은 대략 `P × M × T × F` complex MAC이다. PHAT +정규화가 추가되지만 scan 차수는 같다. GCC-PHAT는 모든 mic의 forward STFT를 한 번 수행한 뒤 +reference pair `M-1`개에 대해 다음 작업을 수행한다. + +- magnitude 최대값 pass와 PHAT 누산 pass: + `2 × (M-1) × T × ΣF_b` cross-spectrum operation +- 각 pair·band의 길이 `N` correlation IFFT: `(M-1) × B`회 +- 각 scan point의 fractional lag lookup: `P × (M-1) × B`회 + +따라서 GCC-PHAT의 scan lookup은 DAS의 `P × M × T × F`보다 작을 수 있지만 공짜는 아니다. +Forward STFT는 그대로 필요하고, 작은 grid·큰 `N`·많은 band에서는 pair별 IFFT나 2-pass cross +spectrum이 지배할 수 있다. `workload`와 실제 `processingTimeMs`를 함께 비교한다. 실시간 목표를 맞추는 조정 순서는 보통 다음과 같다. @@ -293,15 +433,15 @@ matrix가 64 MiB 또는 계획 전체 한도를 넘으면 선택 bin 앞쪽부 7. capture와 분석을 별도 Worker/thread에 두고 결과만 UI 또는 제어 loop로 전달한다. 캐시 최적화와 위 수치는 반복 계산 감소의 근거이지 192채널·50×50 또는 다른 구성이 실시간이라는 -보장이 아니다. 마이크 수, scan point, 선택 bin, STFT frame에 따른 주연산은 여전히 대략 -`O(P × M × F × T)`다. `deadlineMisses` 또는 `droppedSamples`가 누적되면 -grid·band bin·FFT/분석창 또는 update rate를 줄여야 한다. deadline은 위치추정 처리량 판정이며 -capture와 출력까지 포함하는 시스템 deadline은 adapter에서 별도로 검증한다. +보장이 아니다. `deadlineMisses` 또는 `droppedSamples`가 누적되면 grid·band bin·FFT/분석창 또는 +update rate를 줄여야 한다. 평균 한 번만 재지 말고 목표 장치의 실제 입력으로 warm-up 뒤 충분히 긴 +구간의 최대값과 상위 percentile을 측정한다. Capture와 출력까지 포함하는 시스템 deadline은 +adapter에서 별도로 검증한다. ## 취소와 오류 복구 `localizeMultichannel`과 `analyzeAvailable`에 `AbortSignal`을 전달하면 STFT와 -DAS batch 경계에서 `AbortError`를 발생시키고 계획 lock을 해제한다. +알고리즘별 scan/correlation batch 경계에서 `AbortError`를 발생시키고 계획 lock을 해제한다. Streaming 분석이 취소되거나 실패하면 `failedAnalyses`가 증가하지만 해당 hop을 소비하지 않는다. 실패한 창은 별도 analysis workspace에 보존되며, 다음 @@ -329,4 +469,4 @@ hop을 commit하고 sequence를 증가시킨다. 코어 뒤에서는 `locations`를 제어에 바로 쓰기 전에 현장 데이터로 검출 임계값, false peak, latency와 위치 오차를 검증해야 한다. 현재 `locations`는 통계적 신뢰구간이 아니라 각 band -DAS map에서 threshold와 거리 억제를 통과한 grid local peak 후보들이다. +위치 map에서 threshold와 거리 억제를 통과한 grid local peak 후보들이다. diff --git a/packages/acoustics-core/README.md b/packages/acoustics-core/README.md index c8c53d2..feb37d8 100644 --- a/packages/acoustics-core/README.md +++ b/packages/acoustics-core/README.md @@ -6,7 +6,55 @@ - 자유음장 직접음과 선택적 저비용 shoebox multipath - zero-padded FFT 전달함수 기반 fractional sample delay와 마이크 주파수 응답 - radix-2 FFT, Hann STFT와 선택 bin 추출 -- scan grid 거리 cache 및 conventional frequency-domain DAS +- scan grid 거리 cache 및 DAS·DAS-PHAT·GCC-PHAT 위치 추정 + +## 위치 추정 알고리즘 + +`localization-algorithms.js`의 registry와 `localization.js`의 generic API가 다음 세 경로를 +공통 interface로 제공합니다. + +| `algorithm` | 계산 | raw scale | backend | +| --- | --- | --- | --- | +| `das` | conventional frequency-domain delay-and-sum | `power` | CPU, WebGPU | +| `das-phat` | 채널 spectrum을 phase-only로 만든 DAS | `score` | CPU, WebGPU | +| `gcc-phat` | 하나의 reference mic와 나머지 mic의 GCC-PHAT 지연 scan | `score` | CPU only | + +Portable API에서는 top-level `algorithm`으로 선택합니다. 생략하면 기존과 같은 `das`이며, 낮은 +수준의 generic API는 `experiment.beamforming.algorithm`을 사용합니다. + +```js +import { + createLocalizationPlan, + localizeMultichannel, +} from "./packages/acoustics-core/realtime-localizer.js"; + +const plan = createLocalizationPlan({ + ...commonConfig, // sampleRate, array, scan, bands, stft + algorithm: "gcc-phat", // "das" | "das-phat" | "gcc-phat" + phatFloorRatio: 1e-6, +}); + +// 반복 분석에서도 같은 plan을 재사용합니다. +const result = await localizeMultichannel({ plan, channels }); +``` + +`das-phat`의 `phatRelativeFloor`와 `gcc-phat`의 `phatFloorRatio`는 모두 0~1의 상대 +크기 floor이며 기본값은 `1e-6`입니다. GCC-PHAT는 `phatRelativeFloor`도 호환 alias로 +받지만 새 코드에서는 알고리즘별 이름을 권장합니다. PHAT 경로는 mic gain을 위치 score에 +사용하지 않고 알려진 mic·frequency-response phase만 보정합니다. + +GCC-PHAT는 단일 tone보다 여러 FFT bin에 에너지가 있는 broadband 입력을 권장합니다. 하나의 +band에 선택된 bin이 8개 미만이면 코어는 계산을 허용하지만 주기적 모호성과 ghost peak를 직접 +검증해야 합니다. 또한 reference pair의 scan 전체 최대 절대 기하 지연이 `fftSize / 2` sample을 +넘으면 circular correlation alias를 피하기 위해 plan 생성을 거절합니다. 이때는 `fftSize`를 +늘리거나 배열·scan 기하를 조정합니다. + +각 band의 `rawScale`은 `power` 또는 `score`입니다. `rawValue`가 scale에 무관한 표준 peak +값이며, `rawPower`는 `power`, `rawScore`는 `score`일 때만 쓰는 편의 alias입니다. `score`는 +확률·신뢰도·SPL이 아니고 동일 알고리즘·설정 안에서 상대 peak를 비교하는 비교정 값입니다. +`power`도 입력과 calibration이 절대 단위로 교정되지 않았다면 물리적 W 또는 Pa²로 해석하지 않습니다. +GCC-PHAT score는 pair 수, band coefficient 수와 IFFT 크기로 정규화한 0–1 양의 상관 응답이라 +scan 범위와 마이크 수에 의한 단순 재척도화는 피하지만, 여전히 확률로 보정된 confidence는 아닙니다. ## 실측 다채널 스트림 위치 추정 @@ -15,6 +63,10 @@ 디렉터리를 그대로 복사해 사용할 수 있습니다. 채널 순서는 `array.microphones` 순서와 같아야 하고 각 chunk는 동일 길이의 `Float32Array` 목록이어야 합니다. +`StreamingLocalizer`는 알고리즘 중립 class 이름입니다. 기존 `StreamingDasLocalizer`와 같은 class의 +export alias이므로 오래된 import도 그대로 동작하며, `createStreamingLocalizer()` factory도 세 +알고리즘을 모두 지원합니다. + ```js import { createStreamingLocalizer } from "./packages/acoustics-core/realtime-localizer.js"; @@ -24,6 +76,7 @@ const localizer = createStreamingLocalizer({ scan: { xMin: -1, xMax: 1, yMin: -1, yMax: 1, z: 2, columns: 50, rows: 50 }, bands: [{ id: "machine", minHz: 900, maxHz: 1100, color: "#65f2bf" }], stft: { fftSize: 512, hopSize: 256 }, + algorithm: "das", // 호환 기본값. "das-phat" 또는 "gcc-phat"도 선택할 수 있습니다. maxCompensationDb: 24, peakDetection: { thresholdRatio: 0.5, maximumDetections: 8 }, analysisWindowSamples: 4_800, @@ -38,15 +91,17 @@ localizer.append(channels); // Analysis loop: 호출마다 설정된 상한 이하의 map만 반환합니다. const results = await localizer.analyzeAvailable({ signal: abortController.signal }); for (const result of results) { - const { position, rawPower } = result.locations[0]; - drawMarker(position, rawPower, result.centerTime); + const { position, rawValue, rawScale } = result.locations[0]; + drawMarker(position, rawValue, rawScale, result.centerTime); if (!result.deadlineMet) reportOverload(result.processingTimeMs, result.deadlineMs); } ``` -`locations`는 band 순서, 같은 band 안에서는 raw power 내림차순의 평평한 목록입니다. 각 항목은 -기존 `{ bandId, index, position, rawPower }`에 `rank`를 더하며 같은 band의 여러 local peak를 -포함할 수 있습니다. `peakDetection.thresholdRatio` 기본값은 최강 peak의 0.5배, +`locations`는 band 순서, 같은 band 안에서는 `rawValue` 내림차순의 평평한 목록입니다. 각 항목은 +`{ bandId, rank, index, position, rawValue, rawScale }`이며 같은 band의 여러 local peak를 포함할 +수 있습니다. 기존 DAS 결과의 `rawPower` 별칭은 그대로 유지됩니다. PHAT score는 `rawValue`와 +`rawScale`을 기준으로 읽고, score peak 객체에 포함된 경우 `rawScore`를 같은 값의 별칭으로 씁니다. +`peakDetection.thresholdRatio` 기본값은 최강 peak의 0.5배, `minimumPeakDistanceM` 기본값은 scan cell 대각선, `maximumDetections` 기본값은 8입니다. 기존처럼 `locations[0]`만 읽으면 최강 후보를 그대로 얻습니다. @@ -55,10 +110,13 @@ Streaming 계산이 중단되거나 실패하면 해당 분석창과 sequence를 `onResult` callback의 예외는 `failedAnalyses`에 포함하지 않습니다. 계산 중 capture overflow가 나면 ring의 오래된 sample은 평소와 같이 drop telemetry에 기록됩니다. -한 블록 결과의 `processingTimeMs`는 DAS와 local-peak 검출에 걸린 실제 시간을 기록합니다. Streaming +한 블록 결과의 `processingTimeMs`는 선택한 위치 추정 알고리즘과 local-peak 검출에 걸린 실제 시간을 +기록합니다. Streaming 결과는 `deadlineMs = analysisHopSamples / sampleRate × 1000`과 `deadlineMet`을 더하며, `getState()`는 최근/최대 처리 시간과 `deadlineMisses` 누계를 제공합니다. `deadlineMisses` 또는 `droppedSamples`가 증가하면 grid·대역 bin·분석창·update rate를 장치 성능에 맞게 줄여야 합니다. +이 deadline 비교는 실시간 성능 보장이 아니라 완료된 창의 측정값입니다. 목표 장치에서 warm-up을 거친 +지속 부하를 직접 측정하고, capture·adapter·출력을 포함한 end-to-end deadline은 별도로 검증해야 합니다. 깊은 응답 notch의 역필터가 sensor noise까지 폭증시키지 않도록 inverse response의 양의 boost는 기본 24 dB로 제한합니다. Experiment의 `beamforming.maxCompensationDb` 또는 portable plan의 top-level @@ -71,16 +129,27 @@ cache가 나중에 `maximumPlanBytes`를 넘지 않습니다. buffer byte 수를 돌려주므로 capture adapter가 지연을 감시할 수 있습니다. 실제 장치의 clock, channel framing과 transport는 하드웨어별 adapter 책임이며 이 모듈은 그 다음 단계부터 시작합니다. -반복 CPU 분석 계획은 거리 행렬, bin calibration, Hann window, Float64 FFT workspace, 임시 bin map과 -예산 내의 Float64 steering matrix를 재사용합니다. 예를 들어 192채널·50×50·2 bins를 10회 분석하면 +반복 CPU 분석 계획은 공통 거리 행렬과 STFT workspace를 재사용합니다. DAS 계열은 bin calibration, +Hann window, 임시 bin map과 예산 내의 Float64 steering matrix를, GCC-PHAT는 reference 선택과 +correlation workspace를 추가로 유지합니다. 같은 plan을 동시에 두 분석에 쓰지 말고 설정이나 알고리즘이 +바뀌면 새 plan을 만듭니다. 예를 들어 DAS로 192채널·50×50·2 bins를 10회 분석하면 거리 계산은 4,800,000회에서 480,000회로, steering point/microphone pair는 9,600,000회에서 960,000회로 줄어듭니다(sin/cos 호출 17,280,000회 회피). 이 경우 약 16.5 MiB를 유지합니다. 기본 64 MiB보다 전체 wide-band steering matrix가 크더라도 cache 전체를 끄지 않습니다. 선택 bin 앞쪽부터 예산에 들어가는 만큼만 Float64로 유지하고 나머지는 고정 microphone workspace에서 계산합니다. -`analysisCache`는 cached bin capacity, uncached 사용 횟수·pair 수, STFT transform·FFT butterfly·DAS -complex MAC 수와 실제 workspace allocation/reuse 수를 제공합니다. 자동 테스트는 부분 cache와 -cache 없음 사이 raw/display Float32 map의 전 요소 bit-exact 일치를 고정합니다. +`analysisCache`는 알고리즘별 실제 작업을 제공합니다. DAS 계열은 cached bin capacity, +uncached steering pair, STFT transform·FFT butterfly·complex MAC을, GCC-PHAT는 STFT/IFFT, +2-pass cross-spectrum operation, scan lookup과 correlation workspace reuse를 기록합니다. + +`P`를 scan point 수, `M`을 mic 수, `T`를 STFT frame 수, `F`를 선택한 고유 bin 수, +`B`를 band 수, `N`을 `fftSize`라 하면 DAS의 주 scan 연산은 대략 +`P × M × T × F` complex MAC입니다. GCC-PHAT는 forward STFT에 더해 reference pair `M-1`개마다 +band bin을 두 번 순회하는 cross spectrum, `(M-1) × B`회의 길이 `N` IFFT, +`P × (M-1) × B`회의 fractional correlation lookup을 수행합니다. band별 bin 수를 `F_b`라 하면 +cross-spectrum 항목 수는 구현의 두 pass를 포함해 `2 × (M-1) × T × ΣF_b`입니다. 따라서 GCC가 +scan MAC을 줄여도 작은 grid에서는 forward STFT와 pair별 IFFT가 전체 시간을 지배할 수 있으므로 +두 알고리즘 모두 실제 목표 장치에서 측정해야 합니다. ## 이동 음원과 분석 시간창 @@ -94,25 +163,30 @@ seed 기반 noise index, audio asset 재생 위치와 ambient가 전체 timeline ## Beam spread 의미 -Peak의 `beamSpread`는 half-power 연결 성분을 power 가중한 공간 covariance(`covarianceM2`, m²)와 그 -RMS 주축 ellipse로 요약한 빔 폭입니다. 가우시안 확률 모델, 위치 확률 또는 신뢰구간으로 보정된 값이 -아니며 `calibrated: false`를 유지합니다. +Peak의 `beamSpread`는 raw map의 peak 주변 연결 성분을 가중한 공간 covariance(`covarianceM2`, m²)와 +그 RMS 주축 ellipse로 요약한 폭입니다. `power` map은 half-power/power-weighted, +`score` map은 half-maximum/score-weighted 규칙을 사용합니다. 어느 쪽도 가우시안 확률 모델, +위치 확률 또는 신뢰구간으로 보정된 값이 아니며 `calibrated: false`를 유지합니다. FFT는 `X[k] = Σ x[n] exp(-j2πkn/N)` 규약을 사용합니다. 마이크 신호의 전파 phase `exp(-j2πfr/c)`를 정렬하기 위해 DAS steering은 `exp(+j2πfr/c)`를 곱합니다. 단일 tone의 ground-truth 위치에서 peak가 생기는 회귀 테스트로 이 부호를 고정합니다. -마이크 `sensitivityDb`와 `phaseOffsetDeg`는 수신 channel에 편차로 적용하고, DAS에서는 알려진 -calibration 값의 역 gain/phase로 보정합니다. raw power map과 표시용 normalized map은 서로 다른 -buffer로 보존합니다. bin spectrum은 제한된 batch로 처리합니다. cooperative yield는 지원 환경에서 +마이크 `sensitivityDb`와 `phaseOffsetDeg`는 수신 channel에 편차로 적용합니다. Conventional DAS는 +알려진 calibration의 역 gain/phase를 모두 보정하고, DAS-PHAT와 GCC-PHAT는 amplitude gain을 무시한 +채 알려진 phase만 보정합니다. raw `power`/`score` map과 표시용 normalized map은 서로 다른 buffer로 +보존합니다. bin spectrum은 제한된 batch로 처리합니다. cooperative yield는 지원 환경에서 `scheduler.yield()`, 그 외에는 `MessageChannel`을 사용하고 timer task에도 주기적으로 기회를 주므로 진행 callback과 `AbortSignal` 취소를 유지하면서 연속 `setTimeout(0)` clamp를 피합니다. portable -plan의 기본 batch는 point 256개·STFT transform 128개이며 `computeOptions`로 조정할 수 있습니다. +plan의 기본 point batch는 DAS 계열 256개, GCC-PHAT 4,096개이고 STFT transform batch는 +128개입니다. GCC-PHAT의 두 cross-spectrum pass도 16,384 operation마다 양보합니다. +`pointBatchSize`, `stftTransformBatchSize`, `crossSpectrumBatchSize`를 +`computeOptions`로 조정할 수 있습니다. 마이크별 `frequencyResponse`는 2~64개의 `{ frequencyHz, gainDb, phaseDeg? }` 점으로 정의합니다. `interpolateFrequencyResponse`는 dB와 wrapped phase를 구간 선형 보간하고 측정 범위 밖에서는 가장 -가까운 끝점을 사용합니다. 합성 시 기존 sensitivity/phase 편차에 이 응답을 추가하며, CPU DAS는 -선택 FFT bin의 역응답을 calibration/steering cache에 포함합니다. WebGPU 경로는 GPU 전송 직전의 +가까운 끝점을 사용합니다. 합성 시 기존 sensitivity/phase 편차에 이 응답을 추가하며, CPU 위치 추정은 +선택 FFT bin의 알고리즘별 역응답을 calibration에 포함합니다. WebGPU DAS 계열 경로는 GPU 전송 직전의 packed STFT에 같은 역응답을 적용합니다. `beamforming.compensateFrequencyResponse: false`는 sensitivity/phase 보정은 유지한 채 곡선 보정만 꺼 robustness 비교를 재현합니다. @@ -143,6 +217,9 @@ Scene의 `acoustics`를 생략하거나 `{ "model": "free-field" }`로 두면 snapshot 합성은 전체 signal과 가장 늦은 sparse path를 포함하는 delay FFT working set을 512 MiB로 제한합니다. 실측·장시간 입력은 위 streaming localizer의 bounded ring buffer로 전체 녹음을 메모리에 보관하지 않고 처리할 수 있습니다. CPU analysis plan도 기본 512 MiB -hard cap을 할당 전에 검사하며 steering cache는 별도의 64 MiB 안에서 가능한 bin만 유지합니다. +hard cap을 할당 전에 검사합니다. DAS steering cache는 별도의 64 MiB 안에서 가능한 bin만 유지하고, +GCC-PHAT는 거리·STFT·correlation workspace 전체를 plan 한도 안에서 검사합니다. Plan 없는 단발 +GCC 분석도 거리 행렬을 만들기 전에 spectrum·calibration·output을 포함한 working set을 preflight합니다. -192채널·50×50 fixture는 브라우저 통합 테스트에서 실제 합성부터 DAS peak 검출까지 실행합니다. +192채널·50×50 fixture는 기존 DAS 호환 경로를 실제 합성부터 peak 검출까지 고정하며, 별도의 결정적 +회귀 fixture가 DAS-PHAT와 GCC-PHAT의 score scale·lag 부호·plan 재사용을 검사합니다. diff --git a/tests/integration/README.md b/tests/integration/README.md index dd0ebf9..03efcf5 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -17,13 +17,14 @@ Unit과 integration test가 같은 페이지에서 실행됩니다. WebGPU 장 ## 범위 - `cpu-baseline.test.js`: 192채널·50×50 synthetic fixture의 CPU DAS와 위치 기준 -- `webgpu.test.js`: 실제 adapter가 있으면 CPU/GPU map 오차, peak와 강제 bin/point batching; +- `webgpu.test.js`: 실제 adapter가 있으면 DAS·DAS-PHAT CPU/GPU map 오차, peak와 강제 bin/point batching; adapter가 없으면 Auto CPU fallback - `server.test.js`: 숨김 저장소 경로 차단, GET/HEAD 제한과 보안 response headers - `builder-ui.test.js`: 파일 선택 한 번으로 오디오 음원 생성, 이미지·오디오 연결, canvas 편집, trajectory keyframe 편집, Preview WAV/Scene 다운로드, SHA 재연결과 잘못된 import 보존 -- `camera-ui.test.js`: Dedicated Worker CPU/WebGPU 실행, 취소, Result schema, rectangular/Fermat 비교; - 이동 예제 로드, 8×8 CPU·2 Hz live map 준비, 복수 frame, scrub, 재생 시간 증가와 일시정지 +- `camera-ui.test.js`: Dedicated Worker CPU/WebGPU 실행, 취소, Result schema, rectangular/Fermat와 + DAS/DAS-PHAT 비교, GCC-PHAT CPU·협대역 제약; 이동 예제 로드, 8×8 CPU·2 Hz live map의 + Auto→CPU fallback 뒤 공통 analysis plan 재사용, 복수 frame, scrub, 재생 시간 증가와 일시정지 - `workflow-ui.test.js`: Dataset Builder의 Scene JSON과 원본 assets를 Acoustic Camera에 넘겨 Result JSON을 저장하는 end-to-end 흐름 diff --git a/tests/unit/README.md b/tests/unit/README.md index 5648586..7ab6c6c 100644 --- a/tests/unit/README.md +++ b/tests/unit/README.md @@ -17,16 +17,22 @@ Unit과 integration module은 같은 browser runner에서 순서대로 실행됩 ## 범위 -- `contracts.test.js`: Array·Scene·Experiment·Result schema, 관계 검증, 안전한 asset 경로와 이동 fixture +- `contracts.test.js`: Array·Scene·Experiment·Result schema, algorithm·scale·beam-spread 관계 검증, + 안전한 asset 경로와 이동 fixture - `acoustics.test.js`: seed 재현성, 자유음장 전파, trajectory 보간, sample 정렬 시간창과 - tone/noise/asset 절대 시간 연속성, calibration, STFT와 CPU DAS + tone/noise/asset 절대 시간 연속성, calibration, STFT, CPU DAS와 DAS-PHAT +- `gcc-phat.test.js`: reference 선택, near-field lag 부호, 광대역 source peak, alias·메모리 + 선할당 제한, 0–1 score bound·scan 범위 불변성과 geometry/STFT/IFFT workspace 재사용 +- `localization.test.js`: 알고리즘 registry와 generic CPU/backend dispatcher, power/score + 의미, portable GCC-PHAT block·streaming 경로 - `realtime-localizer.test.js`: 기존 CPU DAS와 bit-exact map 동등성, chunk 경계 독립성, bounded ring/result batch, 취소와 geometry·steering cache 연산 절감 - `builder.test.js`: Builder scene model, source/asset·trajectory 조작, 오디오 크기와 WAV 사전 검증 - `evaluation.test.js`: local peak와 검출 감도, truth matching, energy/sidelobe/main-lobe 및 비확률적 beam-spread covariance ellipse - `camera.test.js`: canonical input hash, workload 추정, 이동 timeline 계획·재생 보간, Result schema 검증 -- `webgpu.test.js`: WGSL buffer packing, bin/point batching, 장치 한계와 typed CPU fallback 정책 +- `webgpu.test.js`: WGSL buffer packing, DAS-PHAT phase normalization, bin/point batching, + 장치 한계와 typed CPU fallback 정책 WebGPU unit test 대부분은 실제 GPU 없이 planner와 binary layout을 검증합니다. 실제 GPU 수치 일치는 integration test에서 adapter가 있을 때 수행합니다.