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
+83
View File
@@ -8,6 +8,10 @@ import {
describeFrequencyAliasing,
observeFrequencyBand,
} from "/packages/acoustics-core/sampling.js";
import {
DEFAULT_MAXIMUM_SYNTHESIS_BUFFER_BYTES,
estimateSynthesisWorkingSet,
} from "/packages/acoustics-core/propagation.js";
async function loadJson(path) {
const response = await fetch(path);
@@ -481,6 +485,85 @@ test("analytic tone-only Scene은 존재하지 않는 delay FFT 메모리로 거
);
});
test("Experiment preflight는 runtime과 같은 source-streaming shoebox delay working set을 쓴다", async () => {
const [arrayDefinition, scene, experiment] = await Promise.all([
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
loadJson("/assets/sample-scenes/single-tone.scene.json"),
loadJson("/assets/sample-scenes/single-tone.experiment.json"),
]);
arrayDefinition.microphones = arrayDefinition.microphones.slice(0, 16);
scene.sampleRate = 192_000;
scene.duration = 4_000_000 / scene.sampleRate;
scene.sources = [{
...structuredClone(scene.sources[0]),
id: "long-sampled-source",
name: "Long sampled source",
position: [0, 0, 1],
startTime: 0,
duration: scene.duration,
signal: { type: "noise", color: "white", seedOffset: 1 },
}];
scene.ambient = [];
scene.acoustics = {
model: "shoebox",
room: { min: [-10, -10, -10], max: [10, 10, 10] },
reflections: { order: 0, wallGainDb: -12, scattering: 0 },
diffuseReverb: {
enabled: true,
rt60Seconds: 4,
gainDb: -18,
tapCount: 1,
},
occluders: [],
};
experiment.beamforming.minimumDistance = 0.01;
const workingSet = estimateSynthesisWorkingSet({
scene,
arrayDefinition,
minimumDistance: experiment.beamforming.minimumDistance,
});
assertEqual(workingSet.sampleCount, 4_000_000, "fixture sample count");
assertEqual(
workingSet.maximumTransient.fftSize,
8_388_608,
"diffuse delay crosses the FFT power-of-two boundary",
);
assert(
workingSet.estimatedBufferBytes > DEFAULT_MAXIMUM_SYNTHESIS_BUFFER_BYTES,
"shared runtime estimate exceeds 512 MiB",
);
const issues = await validateExperiment(experiment, {
arrayDefinition,
scene,
});
assert(
issues.some(({ message }) =>
message.includes("source-streaming") && message.includes("512 MiB")),
`validator must reject the same shared estimate: ${JSON.stringify(issues)}`,
);
scene.sources[0].signal = { type: "tone", frequency: 1_000, phaseDeg: 0 };
const toneWorkingSet = estimateSynthesisWorkingSet({
scene,
arrayDefinition,
minimumDistance: experiment.beamforming.minimumDistance,
});
assertEqual(
toneWorkingSet.estimatedBufferBytes,
toneWorkingSet.channelBytes,
"analytic tone has no sampled FFT transient",
);
const toneIssues = await validateExperiment(experiment, {
arrayDefinition,
scene,
});
assert(
!toneIssues.some(({ message }) => message.includes("512 MiB")),
"validator does not charge analytic tone for a nonexistent delay FFT",
);
});
test("NaN 좌표를 거부한다", async () => {
const arrayDefinition = {
schemaVersion: "1.0.0", kind: "micarrsim.array", id: "nan-test", name: "NaN test", units: "m",