perf: harden realtime localization pipeline
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { fftInPlace } from "./fft.js";
|
||||
import { cooperativeYield } from "./cooperative-yield.js";
|
||||
|
||||
function throwIfAborted(signal) {
|
||||
if (signal?.aborted) {
|
||||
@@ -8,10 +9,6 @@ function throwIfAborted(signal) {
|
||||
}
|
||||
}
|
||||
|
||||
function yieldToEventLoop() {
|
||||
return new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
export function hannWindow(size) {
|
||||
if (!Number.isInteger(size) || size < 2) throw new Error("Hann window 크기는 2 이상이어야 합니다.");
|
||||
const window = new Float64Array(size);
|
||||
@@ -21,6 +18,32 @@ export function hannWindow(size) {
|
||||
return window;
|
||||
}
|
||||
|
||||
const STFT_WORKSPACE_KIND = "micarrsim.stft-workspace/1";
|
||||
|
||||
export function createStftWorkspace(fftSize) {
|
||||
return {
|
||||
kind: STFT_WORKSPACE_KIND,
|
||||
fftSize,
|
||||
window: hannWindow(fftSize),
|
||||
real: new Float64Array(fftSize),
|
||||
imaginary: new Float64Array(fftSize),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveStftWorkspace(fftSize, workspace) {
|
||||
if (workspace === undefined) return createStftWorkspace(fftSize);
|
||||
if (
|
||||
workspace?.kind !== STFT_WORKSPACE_KIND ||
|
||||
workspace.fftSize !== fftSize ||
|
||||
workspace.window?.length !== fftSize ||
|
||||
workspace.real?.length !== fftSize ||
|
||||
workspace.imaginary?.length !== fftSize
|
||||
) {
|
||||
throw new RangeError("STFT workspace must match fftSize.");
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
|
||||
export function getFrameCount(sampleCount, fftSize, hopSize) {
|
||||
if (!Number.isInteger(hopSize) || hopSize < 1 || hopSize > fftSize) {
|
||||
throw new Error("hopSize는 1 이상 fftSize 이하여야 합니다.");
|
||||
@@ -29,7 +52,15 @@ export function getFrameCount(sampleCount, fftSize, hopSize) {
|
||||
}
|
||||
|
||||
export async function extractStftBins(channels, options) {
|
||||
const { fftSize, hopSize, bins, signal, onProgress, transformBatchSize = 32 } = options;
|
||||
const {
|
||||
fftSize,
|
||||
hopSize,
|
||||
bins,
|
||||
signal,
|
||||
onProgress,
|
||||
transformBatchSize = 32,
|
||||
workspace: providedWorkspace,
|
||||
} = options;
|
||||
if (!Array.isArray(channels) || channels.length === 0) throw new Error("마이크 채널이 필요합니다.");
|
||||
const sampleCount = channels[0].length;
|
||||
if (channels.some((channel) => channel.length !== sampleCount)) throw new Error("마이크 채널 길이가 다릅니다.");
|
||||
@@ -51,9 +82,8 @@ export async function extractStftBins(channels, options) {
|
||||
});
|
||||
}
|
||||
|
||||
const window = hannWindow(fftSize);
|
||||
const real = new Float64Array(fftSize);
|
||||
const imaginary = new Float64Array(fftSize);
|
||||
const workspace = resolveStftWorkspace(fftSize, providedWorkspace);
|
||||
const { window, real, imaginary } = workspace;
|
||||
const totalTransforms = frameCount * microphoneCount;
|
||||
let completedTransforms = 0;
|
||||
for (let frameIndex = 0; frameIndex < frameCount; frameIndex += 1) {
|
||||
@@ -73,9 +103,12 @@ export async function extractStftBins(channels, options) {
|
||||
spectrum.imaginary[spectrumIndex] = imaginary[bin];
|
||||
}
|
||||
completedTransforms += 1;
|
||||
if (completedTransforms % transformBatchSize === 0) {
|
||||
if (
|
||||
completedTransforms % transformBatchSize === 0 &&
|
||||
completedTransforms < totalTransforms
|
||||
) {
|
||||
onProgress?.({ completedTransforms, totalTransforms });
|
||||
await yieldToEventLoop();
|
||||
await cooperativeYield();
|
||||
throwIfAborted(signal);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user