feat: add realistic portable localization core
This commit is contained in:
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user