perf: stream propagation working sets

This commit is contained in:
2026-08-29 10:56:51 +09:00
parent de0053eae3
commit 044be3cf92
13 changed files with 1532 additions and 267 deletions
+25 -5
View File
@@ -32,19 +32,36 @@ export function addFrequencyDomainPaths(options) {
frequencyResponse,
sampleRate,
workspace,
outputOffsetSamples = 0,
pathDelaySampleRate,
} = options;
if (!Array.isArray(paths) || paths.length === 0) {
throw new Error("frequency-domain path가 하나 이상 필요합니다.");
}
const delaySamplesForPath = (path) => {
if (path.delaySamples !== undefined) return path.delaySamples;
if (
path.delaySeconds !== undefined &&
pathDelaySampleRate > 0 &&
Number.isFinite(pathDelaySampleRate)
) {
return path.delaySeconds * pathDelaySampleRate;
}
return undefined;
};
for (const path of paths) {
if (!(path.delaySamples >= 0) || !Number.isFinite(path.delaySamples)) {
throw new Error("유효하지 않은 delay: " + path.delaySamples);
const delaySamples = delaySamplesForPath(path);
if (!(delaySamples >= 0) || !Number.isFinite(delaySamples)) {
throw new Error("유효하지 않은 delay: " + delaySamples);
}
if (!Number.isFinite(path.gain)) throw new Error("path gain은 유한한 숫자여야 합니다.");
}
if (workspace.real.length !== spectrum.fftSize || workspace.imaginary.length !== spectrum.fftSize) {
throw new Error("fractional-delay workspace 크기가 spectrum과 다릅니다.");
}
if (!Number.isSafeInteger(outputOffsetSamples) || outputOffsetSamples < 0) {
throw new RangeError("outputOffsetSamples must be a non-negative safe integer.");
}
const hasFrequencyResponse = Array.isArray(frequencyResponse) && frequencyResponse.length > 0;
if (hasFrequencyResponse && (!(sampleRate > 0) || !Number.isFinite(sampleRate))) {
throw new Error("frequencyResponse 적용에는 유효한 sampleRate가 필요합니다.");
@@ -58,7 +75,7 @@ export function addFrequencyDomainPaths(options) {
const halfSize = size / 2;
const phaseReanchorInterval = 1024;
for (const path of paths) {
const phaseStep = (-TAU * path.delaySamples) / size;
const phaseStep = (-TAU * delaySamplesForPath(path)) / size;
const stepReal = Math.cos(phaseStep);
const stepImaginary = Math.sin(phaseStep);
let phaseReal = 1;
@@ -118,9 +135,12 @@ export function addFrequencyDomainPaths(options) {
// bin back to the real axis preserves Hermitian symmetry for fractional delays.
workspace.imaginary[size / 2] = 0;
fftInPlace(workspace.real, workspace.imaginary, true);
const outputLength = Math.min(output.length, workspace.real.length);
const outputLength = Math.min(
output.length,
Math.max(0, workspace.real.length - outputOffsetSamples),
);
for (let sampleIndex = 0; sampleIndex < outputLength; sampleIndex += 1) {
output[sampleIndex] += workspace.real[sampleIndex];
output[sampleIndex] += workspace.real[outputOffsetSamples + sampleIndex];
}
}