# Acoustics Core 두 앱이 공유하는 무의존성 수치 기준 구현입니다. - 결정적 tone/white/pink noise 및 오디오 asset resampling - 자유음장 직접음과 선택적 저비용 shoebox multipath - zero-padded FFT 전달함수 기반 fractional sample delay와 마이크 주파수 응답 - radix-2 FFT, Hann STFT와 선택 bin 추출 - scan grid 거리 cache 및 DAS·DAS-PHAT·GCC-PHAT·MUSIC 위치 추정 ## 단일 채널 활동 검출 API 전 대역 후보 검출은 위치추정기와 분리된 무의존성 ES module입니다. analyzeCfarActivity()는 한 채널의 모든 complete Hann frame에 CA-CFAR 또는 OS-CFAR를 적용하고, computeDixonSpectralFlux()는 Dixon 2006의 Hamming, linear-magnitude, half-wave, full-DFT L1 onset novelty를 계산합니다. ~~~js import { analyzeCfarActivity, computeDixonSpectralFlux, pickCausalSpectralFluxPeaks, } from "./packages/acoustics-core/index.js"; const cfar = await analyzeCfarActivity({ samples: onePhysicalAdcChannel, sampleRate: 48_000, fftSize: 512, hopSize: 256, window: "hann", channelId: "mic-001", detector: "os-cfar", trainingCellsPerSide: 8, guardCellsPerSide: 2, rank: 12, nominalFalseAlarmProbability: 0.001, }); const flux = computeDixonSpectralFlux({ samples: onePhysicalAdcChannel, sampleRate: 48_000, fftSize: 512, hopSize: 256, channelId: "mic-001", }); ~~~ 두 함수는 여러 마이크, frame 또는 detector를 합치지 않으며 위치·음원 수·아날로그 alias order를 추론하지 않습니다. CFAR 결과는 DC와 Nyquist를 포함한 모든 ADC bin record를 보존하고, full symmetric reference가 없는 edge를 untested로 남깁니다. Pfa는 IID exponential 모델의 tested CUT 하나당 명목값입니다. Spectral flux는 모든 frame×full-DFT-bin 양의 차이와 raw ODF를 보존하며 peak picker는 별도 명시 호출입니다. estimateCfarActivityWorkload()와 estimateDixonSpectralFluxWorkload()로 할당 전에 frame/bin/FFT 연산과 raw output byte를 검사할 수 있습니다. 기본 상한을 넘으면 일부 frame, top-K bin 또는 축소 결과를 만들지 않고 실패합니다. 상세 수식과 문헌은 [전 대역 활동 검출 모델](../../docs/activity-detection.md)에 있습니다. ## Nyquist 초과 입력 `sampling.js`는 anti-alias filter 없는 이상적 real sampler의 signed alias와 band-to-FFT-bin mapping을 제공한다. tone 전파는 먼저 원 analog 주파수에서 path delay와 microphone response를 적용하고 그 뒤 ADC 시각에서 sampling한다. 따라서 시간축에는 alias가 나타나지만 센서 간 위상은 원 파장의 물리를 유지한다. Nyquist 위 band는 모든 등가 ADC FFT bin center로 접어 관찰한다. 연속 fold segment와 이산 bin-center membership은 별도 metadata다. DAS 계열은 양의 ADC bin 주파수로 steering하고 GCC-PHAT는 선택 bin의 lag correlation을 사용하며, 원 carrier를 몰래 추론하거나 peak를 고르지 않는다. 단일 channel 시간축에서 같은 sequence로 접혀도 다채널 공간 위상에는 원 파장의 증거가 남을 수 있다. 현재 localizer는 carrier hypothesis를 열거하지 않으므로 Result에 `aliasOrderResolution: not-attempted`로 남긴다. asset/noise 입력은 이미 sampling된 baseband로 취급한다. Tone과 band의 검증 범위는 0–1 MHz다. 이는 Nyquist cutoff가 아니라 현재 IEEE-754 위상 계산, 마이크 응답과 희소 경로 모델의 명시적 정밀도 범위다. public sampling helper는 비정상적으로 작은 sample rate에서도 최대 513개 fold segment를 넘기 전에 거부한다. ## 수신 빔 패턴 API `beam-pattern.js`의 `computeReceiveBeamPattern()`은 Scene과 ADC 없이 microphone geometry의 narrowband far-field conventional delay-and-sum receive array factor를 계산합니다. ~~~js import { computeReceiveBeamPattern } from "./packages/acoustics-core/beam-pattern.js"; const pattern = computeReceiveBeamPattern({ array, frequencyHz: 49_125, speedOfSound: 343, cut: "xz", steeringAngleDeg: 0, sampleCount: "auto", dbFloor: -60, }); ~~~ 파수는 `2π frequencyHz / speedOfSound`이며 `frequencyHz`를 temporal ADC alias로 접지 않습니다. 결과는 `1/M` weight의 raw normalized complex·power·dB와 별도의 display-floor buffer를 보존합니다. 이상적으로 보정된 무지향성 point microphone의 geometry-only 응답이므로 `sensitivityDb`, `phaseOffsetDeg`, `frequencyResponse`, element directivity, 방과 near-field는 포함하지 않습니다. XZ는 0°=+Z/90°=+X, YZ는 0°=+Z/90°=+Y, XY는 0°=+X/90°=+Y인 great-circle 단면입니다. 투영 maximum baseline `D`와 angle 수 `N`에서 `2 k D sin(π/N) ≤ π/8` phase-step criterion을 검사합니다. 이는 모든 lobe 검출 보장이 아니라 metric sampling 정책입니다. 미충족 시 raw samples만 반환하고 HPBW와 sampled peak 해석을 `resolution-unresolved`로 남깁니다. HPBW도 sampled half-power bracket이 있을 때만 raw analytic response를 48회 이분하며, 비상수 all-above grid를 360° 폭으로 만들지 않습니다. 마이크 수·baseline pair·angular sample·phasor evaluation은 큰 반복과 할당 전에 상한을 검사합니다. portable 결과 계약과 상태별 의미는 [수신 빔 패턴 문서](../../docs/beam-pattern.md)를 참고하세요. ## 위치 추정 알고리즘 `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 | | `music` | Schmidt narrowband MUSIC의 근거리 spherical noise-subspace projection | `noise-projection` | 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" | "music" 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는 아닙니다. ### Narrowband MUSIC `music`은 [Schmidt MUSIC](https://doi.org/10.1109/TAP.1986.1143830)을 sample covariance와 근거리 구면파 direct-path manifold에 적용합니다. 근거리 배열 처리의 문헌 배경은 [Huang–Barkat](https://doi.org/10.1109/8.86917)을 참고하세요. raw map은 reciprocal을 취하지 않은 Float64 noise projection `q(r)=Σ_(j=K)^(M-1)|v_jᴴa(r)|²/(a(r)ᴴa(r))` 이고 objective는 minimum입니다. 표준 pseudospectrum `P=1/q`와 표시용 `P/Pmax`는 위치 확률·likelihood·confidence·신뢰구간이 아닙니다. raw `q`는 peak normalization이나 epsilon floor로 바꾸지 않습니다. 각 Experiment band는 FFT spacing에 놓인 **아날로그 carrier 가설 하나**만 포함해야 합니다. 관측 ADC bin이 같아도 alias order가 다른 아날로그 가설은 별도 band입니다. Nyquist 위 값은 허용하며 signed alias가 음수이면 positive-bin STFT, mic calibration과 전체 complex manifold를 켤레 방향으로 사용합니다. 공간 위상은 `exp[-j sign(f_signed) 2π f_a Δr/c]`로 analog carrier 크기를 유지하고 signed ADC alias는 orientation만 정합니다. 이 carrier는 Experiment에서 온 가설이고 Scene source truth는 localizer에 전달되지 않습니다. 정확한 DC/Nyquist bin은 quadrature-degenerate라 거부합니다. source 수는 `musicSourceCountMethod: "fixed"`와 `musicSourceCount: K` 또는 [Wax–Kailath](https://doi.org/10.1109/TASSP.1985.1164557) `"aic"`/`"mdl"`로 정합니다. AIC/MDL은 `hopSize === fftSize`, 완전한 frame만 사용, `T>M`, roundoff보다 큰 양의 noise eigenvalue가 필요합니다. IID complex Gaussian snapshot, spatially white noise와 일정한 source 수를 가정하지만 실제 데이터에서 이를 검증하지 않으며 score는 posterior가 아닙니다. 조건이 깨지거나 signal/noise eigengap이 수치 오차보다 작으면 `MusicUnavailableError` (`code: "MUSIC_UNAVAILABLE"`)를 반환합니다. 동일 주파수의 coherent source는 covariance rank를 잃을 수 있습니다. grating lobe, 동률과 flat objective를 고르기 좋게 깨지 않으며, diagonal loading, eigenvalue floor, spatial smoothing, band 간 association/fusion을 적용하지 않습니다. [Wax–Shan–Kailath broadband 방법](https://doi.org/10.1109/TASSP.1984.1164400)은 문헌상 별도 알고리즘이며 현재 구현하지 않았습니다. 빠른 portable 예: ```js const plan = createLocalizationPlan({ sampleRate: 48_000, array, scan, bands: [{ id: "1k", minHz: 950, maxHz: 1_050, color: "#65f2bf" }], stft: { fftSize: 512, hopSize: 256 }, algorithm: "music", normalization: "none", musicSourceCountMethod: "fixed", musicSourceCount: 1, }); const result = await localizeMultichannel({ plan, channels }); ``` band는 정확히 한 FFT-grid center를 포함해야 하므로 `minHz/maxHz`는 사용 중인 `Fs/N`에서 하나의 center만 감싸야 합니다. CPU plan/working-set, covariance accumulation, projection MAC, Jacobi pair-update와 FFT butterfly 상한은 할당·계산 전에 검사합니다. 기본 상한을 넘으면 부분 eigensystem이나 축소 map을 반환하지 않습니다. ## 실측 다채널 스트림 위치 추정 `realtime-localizer.js`는 Scene·음원 합성 없이 이미 sampling된 동기식 마이크 채널을 직접 받습니다. 외부 package 의존성이 없는 ES module이며, 다른 프로젝트에는 `acoustics-core` 디렉터리를 그대로 복사해 사용할 수 있습니다. 채널 순서는 `array.microphones` 순서와 같아야 하고 각 chunk는 동일 길이의 `Float32Array` 목록이어야 합니다. `StreamingLocalizer`는 알고리즘 중립 class 이름입니다. 기존 `StreamingDasLocalizer`와 같은 class의 export alias이므로 오래된 import도 그대로 동작하며, `createStreamingLocalizer()` factory도 네 알고리즘을 모두 지원합니다. MUSIC plan은 `plan.musicPlan` alias로도 접근할 수 있습니다. ```js import { createStreamingLocalizer } from "./packages/acoustics-core/realtime-localizer.js"; const localizer = createStreamingLocalizer({ sampleRate: 48_000, array, 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, analysisHopSamples: 4_800, maximumBufferedSamples: 19_200, maximumResultsPerCall: 1, }); // Capture callback: 복사 후 즉시 반환하며, 오래 밀린 sample은 oldest-first로 버립니다. localizer.append(channels); // Analysis loop: 호출마다 설정된 상한 이하의 map만 반환합니다. const results = await localizer.analyzeAvailable({ signal: abortController.signal }); for (const result of results) { const { position, rawValue, rawScale } = result.locations[0]; drawMarker(position, rawValue, rawScale, result.centerTime); if (!result.deadlineMet) reportOverload(result.processingTimeMs, result.deadlineMs); } ``` `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]`만 읽으면 최강 후보를 그대로 얻습니다. Streaming 계산이 중단되거나 실패하면 해당 분석창과 sequence를 commit하지 않고 다음 호출에서 같은 창을 재시도합니다. 성공한 뒤에만 hop·sequence·`completedAnalyses`를 commit하며, 그 뒤 호출되는 `onResult` callback의 예외는 `failedAnalyses`에 포함하지 않습니다. 계산 중 capture overflow가 나면 ring의 오래된 sample은 평소와 같이 drop telemetry에 기록됩니다. 한 블록 결과의 `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 `maxCompensationDb`를 0~120 dB로 조절할 수 있습니다. 이 값은 boost만 제한하며 attenuation과 phase 보정은 유지합니다. 선택 bin 전체의 gain/phase calibration byte도 plan 생성 전에 산정하므로 lazy cache가 나중에 `maximumPlanBytes`를 넘지 않습니다. 한 블록만 처리할 때는 `createLocalizationPlan()`과 `localizeMultichannel()`을 사용합니다. `getState()`은 absolute sample origin, buffered/dropped sample, 대기 window 수와 고정 input buffer byte 수를 돌려주므로 capture adapter가 지연을 감시할 수 있습니다. 실제 장치의 clock, channel framing과 transport는 하드웨어별 adapter 책임이며 이 모듈은 그 다음 단계부터 시작합니다. 반복 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`는 알고리즘별 실제 작업을 제공합니다. 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가 전체 시간을 지배할 수 있으므로 두 알고리즘 모두 실제 목표 장치에서 측정해야 합니다. ## 이동 음원과 분석 시간창 `sourcePositionAtTime`은 scene 시작 기준의 절대 시각에서 trajectory keyframe 사이를 선형 보간하며, 경로 범위 밖에서는 가장 가까운 끝점 위치를 유지합니다. `sampleQuasiStaticWindow`는 요청 경계를 sample 경계로 올림 정렬하고, 시간창 중앙의 위치에서 이동 음원을 정지한 것으로 근사합니다. 시간창은 요청 출력 시작보다 앞선 sampled noise·asset을 해당 source들의 최대 경로 지연만큼 pre-roll합니다. analytic tone은 과거 sample buffer 없이 절대시간에서 직접 계산하므로 tone의 더 긴 경로는 render buffer를 늘리지 않습니다. 그래도 창이 시작되기 전에 발음이 끝난 tone을 direct·reflection·scattering·diffraction·diffuse 도착 구간으로 선별해 제거하지 않습니다. `signalOffsets`는 원래 scene/source의 절대 sample offset과 출력 crop을 함께 보존합니다. analytic tone은 pre-roll 길이의 마이크 channel을 만들지 않고 요청 crop에 절대 sample 위상으로 바로 더합니다. sampled noise·asset은 지연에 필요한 pre-roll 방사 buffer와 zero-padded FFT는 유지하되 IFFT 결과의 crop 구간만 crop 크기 channel에 더합니다. 따라서 tone phase, seed 기반 noise index, audio asset 재생 위치와 ambient가 전체 timeline과 같은 절대 시간축을 사용하면서 `마이크 수 × pre-roll` 출력 메모리와 tone sample-add를 만들지 않습니다. 이 보장은 현재 희소 경로 모델 안에서의 **전파 지연 tail**에 관한 것입니다. quasi-static 창은 pre-roll 구간까지 포함해 source를 출력 창 중앙의 한 위치에 고정하며, retarded-time trajectory, 창 안의 연속 이동과 Doppler는 계산하지 않습니다. 합성 결과의 `windowing` metadata는 적용한 pre-roll, 출력 절대 sample, `source-position-fixed-at-window-midpoint` geometry와 `dopplerMode: not-modeled`를 기록합니다. `sourceArrivals`는 source별 방사·도착 sample 경계와 출력에 겹친 path kind를, `arrivingSourceIds`는 적어도 한 실제 path의 half-open 도착 구간이 출력과 겹친 source를 기록합니다. 이는 사후 ground-truth 평가에만 쓰이며 위치 추정 입력에는 전달하지 않습니다. loop하지 않는 asset의 방사 종료는 선언 duration과 decoded channel duration 중 빠른 쪽이며, PCM 내용의 leading/trailing zero까지 분석해 truth를 줄이지는 않습니다. sampled asset/noise의 fractional delay와 microphone response는 기존 zero-padded block FFT 모델을 그대로 사용하므로 임의 길이 analog microphone impulse response의 streaming state를 재현하는 모델은 아닙니다. ## Beam spread 의미 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에 편차로 적용합니다. 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의 기본 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 위치 추정은 선택 FFT bin의 알고리즘별 역응답을 calibration에 포함합니다. WebGPU DAS 계열 경로는 GPU 전송 직전의 packed STFT에 같은 역응답을 적용합니다. `beamforming.compensateFrequencyResponse: false`는 sensitivity/phase 보정은 유지한 채 곡선 보정만 꺼 robustness 비교를 재현합니다. 응답 곡선이 없는 배열은 기존 계산 경로와 결과를 유지합니다. 곡선이 있으면 합성 FFT마다 bin당 보간·복소 곱이 추가되고, WebGPU 준비 단계에는 `선택 bin × 마이크` calibration과 STFT 복소 곱이 추가되지만 GPU buffer layout이나 dispatch 크기는 늘어나지 않습니다. ## 저비용 환경 근사 Scene의 `acoustics`를 생략하거나 `{ "model": "free-field" }`로 두면 기존 직접음 `delay = r/c`, `gain = 1/r`만 사용합니다. `model: "shoebox"`는 다음 경로를 선택적으로 추가합니다. - 직육면체 `room`의 여섯 면에 대한 1차 image-source 반사 - 반사 에너지 일부를 deterministic 지연 경로로 나누는 scattering 근사 - 축 정렬 box `occluders`를 통과하는 직접음 감쇠와 가장 짧은 corner detour 회절 경로 - RT60·gain·tap 수로 제한한 deterministic diffuse-reverb tail 모든 경로의 위상과 gain은 마이크마다 하나의 frequency-domain 전달함수로 합친 뒤 IFFT를 한 번만 수행합니다. 경로 위상은 recurrence로 갱신하고 1,024 bin마다 재고정하므로 각 bin·path마다 삼각함수를 다시 호출하지 않습니다. UI preset도 수신점당 sparse path 상한을 표시합니다. 이 모델은 배열 배치와 위치추정 robustness를 빠르게 비교하기 위한 근사입니다. 파동 방정식, 고차 반사, 재질별 주파수 의존 흡음, 복잡한 mesh 회절, Doppler, 음원·마이크 지향성, 압력/SPL, 센서 self-noise, ADC quantization·clock jitter는 계산하지 않습니다. 그런 효과가 중요한 검증에서는 실측 다채널 데이터를 아래 streaming API에 넣어 결과를 비교해야 합니다. 합성기는 source별로 경로를 한 receiver 묶음씩 만들고, sampled source도 하나씩 `render → spectrum → microphone 적용 → 해제`합니다. render 길이를 `N`, 출력 crop을 `C`, 마이크 수를 `M`, sampled source `i`의 자체 최대 경로 지연을 포함한 FFT 길이를 `F_i`라 하면 명시적으로 살아 있는 typed-array peak는 다음입니다. ```text 4 M C + max_i(4 N + 32 F_i) ``` `32 F_i`는 Float64 spectrum 두 개와 재사용 workspace 두 개입니다. sampled ambient는 zero-delay `F_0 = nextPow2(N + 2)` 후보 하나로 같은 max에 들어가며, analytic tone이나 다른 sampled source의 수를 transient byte에 더하지 않습니다. 이 논리적 typed-array working set을 기본 512 MiB로 할당 전에 검사하고, Experiment validator도 동일 estimator와 실제 reflection·scattering·diffraction·diffuse 지연을 사용합니다. 외부 decoded asset buffer와 JS engine의 객체 heap/RSS까지 512 MiB라고 주장하는 값은 아닙니다. 라이브 preflight가 만든 경로 count·source별 최대 지연 summary는 geometry, source/microphone id와 좌표, acoustics/seed, 거리 설정 전체의 서명이 맞을 때만 합성 preflight에 재사용합니다. summary는 불변이고 모듈 내부에서 인증되므로 복제·변조·다른 geometry의 cache는 자동 재계산합니다. 동일 Scene·Array 객체에서 band/scan/backend만 바뀐 반복 validation은 이 exact summary를 재사용합니다. 실제 전달함수 경로는 receiver별로 한 번 다시 만들며, sampled source의 IFFT는 아직 source와 microphone 조합마다 한 번입니다. 다수 non-tone source의 transform batching은 수학 동치와 메모리 예산을 함께 검증해야 하는 후속 최적화입니다. 실측·장시간 입력은 위 streaming localizer의 bounded ring buffer로 전체 녹음을 메모리에 보관하지 않고 처리할 수 있습니다. CPU analysis plan도 기본 512 MiB 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 검출까지 고정하며, 별도의 결정적 회귀 fixture가 DAS-PHAT와 GCC-PHAT의 score scale·lag 부호·plan 재사용을 검사합니다.