feat: add realistic portable localization core

This commit is contained in:
Codex
2026-08-28 18:19:31 +09:00
parent bc63ada1cd
commit d002a2bd70
20 changed files with 2345 additions and 70 deletions
+57 -4
View File
@@ -3,6 +3,8 @@ import {
estimateDasWorkload,
finalizeDasBands,
} from "../acoustics-core/das.js";
import { interpolateFrequencyResponse } from "../acoustics-core/frequency-response.js";
import { dbToLinear } from "../acoustics-core/math.js";
import { extractStftBins } from "../acoustics-core/stft.js";
import {
DAS_PARAMS_BYTE_LENGTH,
@@ -66,13 +68,45 @@ function minimumBufferSize(byteLength) {
return Math.max(4, Math.ceil(byteLength / 4) * 4);
}
export function packStftBatch(stft, bins) {
export function packStftBatch(stft, bins, options = {}) {
if (!stft || !Number.isInteger(stft.frameCount) || !Number.isInteger(stft.microphoneCount)) {
throw new TypeError("유효한 STFT 결과가 필요합니다.");
}
if (!Array.isArray(bins) || bins.length === 0) throw new TypeError("하나 이상의 FFT bin이 필요합니다.");
const valuesPerBin = stft.frameCount * stft.microphoneCount;
const packed = new Float32Array(bins.length * valuesPerBin * 2);
const microphones = options.microphones;
const compensateFrequencyResponse = options.compensateFrequencyResponse !== false;
const hasFrequencyResponse =
compensateFrequencyResponse &&
Array.isArray(microphones) &&
microphones.some((microphone) => Array.isArray(microphone.frequencyResponse));
if (hasFrequencyResponse) {
if (microphones.length !== stft.microphoneCount) {
throw new Error("STFT microphone 수와 주파수 응답 calibration 수가 다릅니다.");
}
if (!(options.sampleRate > 0) || !(options.fftSize > 0)) {
throw new Error("주파수 응답 보정에는 유효한 sampleRate와 fftSize가 필요합니다.");
}
}
const inverseResponses = hasFrequencyResponse
? new Float64Array(bins.length * stft.microphoneCount * 2)
: null;
if (hasFrequencyResponse) {
for (let index = 0; index < inverseResponses.length; index += 2) inverseResponses[index] = 1;
const response = {};
bins.forEach((bin, binIndex) => {
const frequencyHz = (bin * options.sampleRate) / options.fftSize;
microphones.forEach((microphone, microphoneIndex) => {
if (!Array.isArray(microphone.frequencyResponse)) return;
interpolateFrequencyResponse(microphone.frequencyResponse, frequencyHz, response);
const inverseGain = dbToLinear(-response.gainDb);
const responseOffset = (binIndex * stft.microphoneCount + microphoneIndex) * 2;
inverseResponses[responseOffset] = inverseGain * Math.cos(response.phaseRadians);
inverseResponses[responseOffset + 1] = -inverseGain * Math.sin(response.phaseRadians);
});
});
}
bins.forEach((bin, binIndex) => {
const spectrum = stft.spectra.get(bin);
if (!spectrum || spectrum.real.length !== valuesPerBin || spectrum.imaginary.length !== valuesPerBin) {
@@ -80,8 +114,21 @@ export function packStftBatch(stft, bins) {
}
const binOffset = binIndex * valuesPerBin * 2;
for (let index = 0; index < valuesPerBin; index += 1) {
packed[binOffset + index * 2] = spectrum.real[index];
packed[binOffset + index * 2 + 1] = spectrum.imaginary[index];
if (!inverseResponses) {
packed[binOffset + index * 2] = spectrum.real[index];
packed[binOffset + index * 2 + 1] = spectrum.imaginary[index];
continue;
}
const microphoneIndex = index % stft.microphoneCount;
const responseOffset = (binIndex * stft.microphoneCount + microphoneIndex) * 2;
const inverseReal = inverseResponses[responseOffset];
const inverseImaginary = inverseResponses[responseOffset + 1];
const inputReal = spectrum.real[index];
const inputImaginary = spectrum.imaginary[index];
packed[binOffset + index * 2] =
inputReal * inverseReal - inputImaginary * inverseImaginary;
packed[binOffset + index * 2 + 1] =
inputReal * inverseImaginary + inputImaginary * inverseReal;
}
});
return packed;
@@ -488,7 +535,13 @@ export async function computeDasWebGpu(options) {
spectrumBatchCount: plan.binBatchCount,
}),
});
const packedSpectrum = packStftBatch(stft, batchBins);
const packedSpectrum = packStftBatch(stft, batchBins, {
microphones: arrayDefinition.microphones,
sampleRate,
fftSize: experiment.stft.fftSize,
compensateFrequencyResponse:
experiment.beamforming.compensateFrequencyResponse !== false,
});
stftMs += now() - stftStartedAt;
const binIndices = Uint32Array.from(batchBins);
const binResources = [];