# 실측 다채널 위치추정 코어 포팅 `packages/acoustics-core/realtime-localizer.js`는 Scene 생성이나 음향 합성 없이 이미 sampling된 동기식 마이크 채널을 CPU DAS·DAS-PHAT·GCC-PHAT 위치 추정에 직접 넣는다. 브라우저 DOM과 외부 package에 의존하지 않는 ES module이므로, 실제 장치 adapter 뒤에 붙이거나 다른 프로젝트로 분리할 수 있다. 이 API는 ADC와 transport를 제어하지 않는다. 입력 channel을 획득·정렬·동기화한 다음 위치 map과 대역별 local peak 후보를 계산하는 경계부터 담당한다. ## 최소 복사 단위 직접 경로로 `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 frequency-response.js cooperative-yield.js evaluation.js ``` `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를 선택한다. ## 입력 계약 `channels`는 다음 조건을 만족해야 한다. - `array.microphones`와 같은 순서·개수의 JavaScript 배열 - 모든 원소가 `Float32Array` - 한 호출 또는 chunk 안에서 모든 채널의 sample 수가 동일 - `sampleRate`가 모든 채널에서 동일하고 sample index가 같은 시각을 의미함 - 값은 선형 진폭 sample이며, 장치 integer PCM은 adapter에서 일관된 scale의 float로 변환 코어는 channel timestamp, interleaved frame, endian, packet loss 또는 sample clock을 추론하지 않는다. 잘못된 channel 순서나 채널 사이의 sample slip은 위치 오차로 그대로 나타난다. Array에는 최소한 다음 정보가 필요하다. ```js const array = { microphones: [ { id: "mic-0", position: [-0.10, 0.10, 0], sensitivityDb: 0, phaseOffsetDeg: 0, frequencyResponse: [ { frequencyHz: 100, gainDb: -6, phaseDeg: -5 }, { frequencyHz: 1000, gainDb: 0, phaseDeg: 0 }, { frequencyHz: 10000, gainDb: -2, phaseDeg: 8 }, ], }, // 실제 capture channel 순서대로 계속 ], }; ``` `frequencyResponse`는 선택 사항이다. 있으면 2–64개 점의 주파수가 엄격히 증가해야 한다. `compensateFrequencyResponse`의 기본값은 `true`이며, 알려진 curve와 `sensitivityDb`/`phaseOffsetDeg`의 역보정을 steering에 포함한다. 응답 notch의 양의 보상 boost는 top-level `maxCompensationDb` 또는 `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 | | `music` | 표준 좁은대역 Schmidt noise-subspace scan | `noise-projection` | 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, }); const musicPlan = createLocalizationPlan({ ...commonConfig, algorithm: "music", normalization: "none", musicSourceCountMethod: "fixed", musicSourceCount: 1, }); ``` `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 설정이 바뀌지 않는 동안 계획을 한 번 만들고 재사용한다. ```js import { createLocalizationPlan, localizeMultichannel, } from "./acoustics-core/realtime-localizer.js"; const plan = createLocalizationPlan({ 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" | "das-phat" | "gcc-phat" | "music" speedOfSound: 343, normalization: "peak", compensateFrequencyResponse: true, maxCompensationDb: 24, peakDetection: { thresholdRatio: 0.5, // minimumPeakDistanceM을 생략하면 scan cell 대각선 길이 maximumDetections: 8, }, }); ``` 주요 검증 조건은 다음과 같다. - `fftSize`는 2 이상의 2의 거듭제곱이고 `hopSize <= fftSize` - band는 하나 이상이며 `0 <= minHz < maxHz`. Nyquist 위 구간은 anti-alias filter 없는 ideal real ADC의 등가 baseband bin으로 접힌다. 단일 channel 시간 sample은 원 alias order를 복원하지 못하지만 다채널 공간 위상에는 원 파장의 증거가 남을 수 있다. 현재 portable localizer는 carrier hypothesis를 열거하지 않는다. - scan의 `columns`와 `rows`는 양의 정수 - `speedOfSound`는 양수 - `normalization`은 `peak` 또는 `none` - `algorithm`은 `das`, `das-phat`, `gcc-phat`, `music` 중 하나 - `phatRelativeFloor`와 `phatFloorRatio`는 지정한 경우 0–1 - `maxCompensationDb`는 0–120 dB - `peakDetection.thresholdRatio`는 0–1, `minimumPeakDistanceM`는 0 이상, `maximumDetections`는 1–128의 정수 계획은 알고리즘별 거리 행렬과 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`, MUSIC에는 `plan.musicPlan` alias도 제공한다. ### MUSIC 포팅 경계 MUSIC raw `q = ||E_n^H a||²/(a^H a)`는 작은 값이 좋은 noise projection이다. 표시용 `P/Pmax = q_min/q`도 확률·신뢰도·Gaussian 신뢰구간이 아니며, raw q 보존을 위해 `normalization: "none"`만 허용한다. band마다 사용자가 지정한 analog FFT-grid 가설 하나와 접힌 positive ADC bin 하나만 사용한다. Scene carrier/위치 truth는 읽지 않는다. analog carrier `f_a`의 공간 위상을 alias orientation `η`로 `exp[-j η 2π f_a Δr/c]`에 적용하며, inversion이면 calibration과 steering을 포함한 전체 complex manifold를 켤레화한다. 정확한 DC/Nyquist는 quadrature-degenerate라 거절한다. Fixed K는 `1..M-1`이다. Wax–Kailath MDL/AIC는 complete non-overlapping frame, `T > M`, strict-positive noise eigenvalue, IID complex Gaussian snapshot·spatially white noise·고정 K를 가정한다. non-overlap은 독립성의 증명이 아니다. 조건 불충족과 식별 불가능한 partition은 `MusicUnavailableError` (`code: "MUSIC_UNAVAILABLE"`)이며 fixed fallback은 없다. diagonal loading, floor, spatial smoothing, multibin/wideband fusion도 없다. grating lobe, 동률과 coherent rank deficiency를 결과가 좋아 보이도록 제거하지 않는다. 계획은 allocation 전에 typed working bytes, FFT butterfly, covariance complex accumulation, Jacobi pair update와 projection MAC 상한을 검사한다. 이 CPU cap은 메모리·응답성 보호 장치이지 realtime 보장이 아니므로 `processingTimeMs`와 streaming deadline 통계를 대상 장치에서 측정한다. 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); } ``` ## 한 블록 분석 짧은 녹음이나 adapter가 이미 만든 고정 길이 frame은 `localizeMultichannel`로 처리한다. ```js const controller = new AbortController(); const result = await localizeMultichannel({ plan, channels, // Array signal: controller.signal, onProgress(progress) { updateProgress(progress); }, }); for (const location of result.locations) { console.log( location.bandId, location.position, location.rawScale, location.rawValue, ); } ``` `result.locations`는 입력 band별 raw map의 local peak를 flat array로 담는다. 각 band 안에서는 `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`는 선택한 위치 추정 계산과 local-peak 검출에 걸린 monotonic wall time이다. capture, adapter 변환과 결과 rendering 시간은 포함하지 않으므로 end-to-end latency는 호출자가 별도로 측정한다. 계획을 전달하지 않고 `localizeMultichannel({ channels, ...config })` 형태로도 호출할 수 있지만, 매번 geometry cache를 다시 만들므로 반복·실시간 처리에는 권장하지 않는다. ## 연속 chunk 처리 `StreamingLocalizer`는 알고리즘 중립 이름이며 기존 `StreamingDasLocalizer` class의 export alias다. 오래된 이름은 호환을 위해 유지된다. 두 이름과 `createStreamingLocalizer` factory는 모두 선택한 `algorithm`에 맞는 고정 용량 ring buffer와 분석 workspace를 만든다. ```js import { StreamingLocalizer } from "./acoustics-core/realtime-localizer.js"; const localizer = new StreamingLocalizer({ 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-phat", phatRelativeFloor: 1e-6, analysisWindowSamples: 4_800, analysisHopSamples: 2_400, maximumBufferedSamples: 19_200, maximumResultsPerCall: 1, }); function onCaptureChunk(channels) { // append()는 동기식으로 복사하고 즉시 상태를 반환한다. const state = localizer.append(channels); if (state.droppedSamples > 0) reportBackpressure(state); } async function analysisLoop(signal) { const results = await localizer.analyzeAvailable({ signal }); for (const result of results) { renderMap(result.bands[0].displayMap, result.centerTime); if (!result.deadlineMet) { reportOverload(result.processingTimeMs, result.deadlineMs); } } const state = localizer.getState(); if (state.deadlineMisses > 0 || state.droppedSamples > 0) reportBackpressure(state); } ``` `push(channels, options)`는 `append` 후 `analyzeAvailable`을 한 번 호출하는 편의 메서드다. 임의의 capture chunk 경계로 나누어 넣어도 보존된 분석창의 결과는 동일하다. 각 streaming 결과에는 `sequence`, `startSample`, `endSample`, `startTime`, `endTime`과 `centerTime`이 추가된다. sample 위치는 localizer를 만든 뒤 받은 첫 sample을 0으로 하는 절대 index다. 또한 `deadlineMs = analysisHopSamples / sampleRate × 1000`과 `deadlineMet = processingTimeMs <= deadlineMs`를 반환한다. `deadlineMet`는 완료된 한 창의 위치 추정 시간이 hop 예산 안이었는지를 나타내는 측정값이지, 해당 구성이 실시간이라는 사전 보장이 아니다. JIT warm-up, GC, 다른 thread의 부하를 포함해 목표 장치에서 연속 실행을 측정해야 한다. 이 값은 capture·transport·rendering을 포함하지 않으므로 전체 시스템 deadline은 adapter 경계에서 따로 계측한다. ## 지연과 backpressure 조정 서로 다른 hop이 두 층에 있다. - `stft.hopSize`: 한 분석창 안에서 spectrum을 평균하는 STFT frame 간격 - `analysisHopSamples`: 다음 위치 map을 시작하는 간격 `analysisWindowSamples`는 `fftSize` 이상이어야 한다. 긴 창은 안정적인 spectrum 평균에 유리하지만 결과 지연과 연산량이 늘어난다. 주파수 bin 간격은 주로 `fftSize`로 결정된다. `analysisHopSamples`는 창보다 클 수 없으며 작게 할수록 map update rate와 중첩 연산량이 늘어난다. `maximumBufferedSamples`는 최소 한 분석창 이상이어야 한다. 새 chunk가 용량을 넘으면 가장 오래된 sample부터 버리고 `droppedSamples`, `bufferStartSample`을 갱신한다. 호출마다 반환할 결과 수는 기본 1개이고 구성 상한은 32개다. 한 번에 긴 backlog를 모두 계산하지 않도록 `maximumResultsPerCall`을 작게 유지하고 event loop나 worker scheduler가 다시 호출하게 한다. `getState()`으로 다음 값을 감시할 수 있다. - `bufferedSamples`, `availableAnalysisCount`, `bufferStartSample` - `totalReceivedSamples`와 `droppedSamples` - `analysisAttempts`, `completedAnalyses`, `failedAnalyses` - `analysisDeadlineMs`, `lastProcessingTimeMs`, `maximumProcessingTimeMs`와 `deadlineMisses` - `busy`, `retainedInputBytes`, 다음 `sequence` `deadlineMisses`는 완료됐지만 deadline을 넘긴 결과의 누계다. 취소·계산 오류는 `failedAnalyses`에서 별도로 확인한다. `analyzeAvailable()`을 같은 instance에서 중복 호출하면 오류가 난다. 분석 중에도 `append()`는 안전하다. 분석기는 먼저 현재 창을 별도 workspace에 복사하며, 해당 hop은 위치추정이 성공한 뒤에만 ring에서 소비한다. ## 메모리와 연산 최적화 기본 한도는 다음과 같다. - CPU 계획 전체 `maximumPlanBytes`: 512 MiB - steering cache `maximumSteeringCacheBytes`: 64 MiB - streaming 입력 `maximumInputBufferBytes`: 256 MiB 입력 retained byte는 `채널 수 × (maximumBufferedSamples + analysisWindowSamples) × 4`이며 할당 전에 검사한다. 모든 계획은 거리 행렬과 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에서 필요할 때 계산한다. 기존 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`에는 공통 거리·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`를 함께 비교한다. 실시간 목표를 맞추는 조정 순서는 보통 다음과 같다. 1. scan grid를 50×50 이하로 시작한다. 2. 실제 필요한 band만 좁게 두어 선택 FFT bin 수를 줄인다. 3. 필요한 시간 분해능에 맞춰 `analysisHopSamples`을 정한다. 4. 낮은 주파수 분해능이 필요하지 않다면 FFT와 분석창을 줄인다. 5. 같은 장치에서 각 결과의 `processingTimeMs <= deadlineMs`가 지속되는지 확인한다. 6. `getState()`의 `deadlineMisses`, backlog와 `droppedSamples`가 증가하지 않는지 확인한다. 7. capture와 분석을 별도 Worker/thread에 두고 결과만 UI 또는 제어 loop로 전달한다. 캐시 최적화와 위 수치는 반복 계산 감소의 근거이지 192채널·50×50 또는 다른 구성이 실시간이라는 보장이 아니다. `deadlineMisses` 또는 `droppedSamples`가 누적되면 grid·band bin·FFT/분석창 또는 update rate를 줄여야 한다. 평균 한 번만 재지 말고 목표 장치의 실제 입력으로 warm-up 뒤 충분히 긴 구간의 최대값과 상위 percentile을 측정한다. Capture와 출력까지 포함하는 시스템 deadline은 adapter에서 별도로 검증한다. ## 취소와 오류 복구 `localizeMultichannel`과 `analyzeAvailable`에 `AbortSignal`을 전달하면 STFT와 알고리즘별 scan/correlation batch 경계에서 `AbortError`를 발생시키고 계획 lock을 해제한다. Streaming 분석이 취소되거나 실패하면 `failedAnalyses`가 증가하지만 해당 hop을 소비하지 않는다. 실패한 창은 별도 analysis workspace에 보존되며, 다음 `analyzeAvailable()` 호출이 같은 `startSample`과 sample을 먼저 재시도한다. 계산 중 capture overflow로 ring의 시작점이 전진해도 이 pending 창은 workspace에 남는다. 성공하면 그때 hop을 commit하고 sequence를 증가시킨다. `onResult` callback은 성공 결과와 hop을 commit한 뒤 호출된다. consumer callback의 예외는 호출자에게 전달되지만 성공한 창을 `failedAnalyses`로 세거나 재시도 상태로 되돌리지 않는다. `reset()`은 idle 상태에서 ring, 시간 원점과 통계를 초기화한다. 분석 중에는 호출할 수 없다. ## 장치 adapter가 책임질 부분 포팅 대상 시스템은 코어 앞에서 다음을 구현해야 한다. - 장치 발견, sample rate·channel 수 설정과 capture lifecycle - interleaved integer/float frame을 channel별 `Float32Array`로 변환 - `array.microphones`와 물리 channel 번호의 고정 mapping - 공통 sample clock, channel framing, timestamp와 drift/slip 감시 - packet loss·overflow 정책과 `droppedSamples` telemetry 결합 - 필요 시 DC 제거, anti-alias/장치 filter와 일관된 amplitude scale - 실측한 gain·phase·주파수 응답을 Array 정의에 연결 - worker scheduling, `AbortController`, UI/네트워크로의 결과 전달 코어 뒤에서는 `locations`를 제어에 바로 쓰기 전에 현장 데이터로 검출 임계값, false peak, latency와 위치 오차를 검증해야 한다. 현재 `locations`는 통계적 신뢰구간이 아니라 각 band 위치 map에서 threshold와 거리 억제를 통과한 grid local peak 후보들이다.