feat: define moving source trajectories

This commit is contained in:
Codex
2026-08-28 15:48:16 +09:00
parent b678d1f9c7
commit a7befa0270
6 changed files with 253 additions and 10 deletions
+72
View File
@@ -10,6 +10,11 @@ import { renderSceneMix } from "/packages/acoustics-core/mixdown.js";
import { whiteNoiseAt } from "/packages/acoustics-core/noise.js";
import { sphericalAttenuation, synthesizeMicrophoneSignals } from "/packages/acoustics-core/propagation.js";
import { getFrameCount } from "/packages/acoustics-core/stft.js";
import {
createQuasiStaticWindows,
sceneAtTime,
sourcePositionAtTime,
} from "/packages/acoustics-core/trajectory.js";
import { encodeMonoPcm16Wav } from "/packages/acoustics-core/wav.js";
function assertNear(actual, expected, tolerance, label) {
@@ -39,6 +44,73 @@ function createArray(columns, rows, width, height) {
return { id: "test-array", aperture: { width, height }, microphones };
}
test("trajectory 위치를 선형 보간하고 시간창 중앙에서 준정적으로 샘플링한다", () => {
const source = {
position: [9, 8, 7],
trajectory: [
{ time: 0.2, position: [-1, 0, 2] },
{ time: 0.6, position: [1, 2, 4] },
{ time: 1, position: [3, 0, 2] },
],
};
assertEqual(sourcePositionAtTime({ position: [4, 5, 6] }, 0.5).join(","), "4,5,6", "fixed fallback");
assertEqual(sourcePositionAtTime(source, 0).join(","), "-1,0,2", "before first keyframe");
assertEqual(sourcePositionAtTime(source, 1.5).join(","), "3,0,2", "after last keyframe");
const interpolated = sourcePositionAtTime(source, 0.4);
assertNear(interpolated[0], 0, 1e-12, "interpolated x");
assertNear(interpolated[1], 1, 1e-12, "interpolated y");
assertNear(interpolated[2], 3, 1e-12, "interpolated z");
const scene = { duration: 1, sources: [source] };
const sampled = sceneAtTime(scene, 0.4);
assert(sampled !== scene && sampled.sources[0] !== source, "sceneAtTime is immutable");
assertEqual(source.position.join(","), "9,8,7", "original fallback position is unchanged");
assertEqual(sampled.sources[0].trajectory, source.trajectory, "trajectory is preserved");
assertNear(sampled.sources[0].position[0], 0, 1e-12, "sampled scene x");
assertNear(sampled.sources[0].position[1], 1, 1e-12, "sampled scene y");
assertNear(sampled.sources[0].position[2], 3, 1e-12, "sampled scene z");
const windows = createQuasiStaticWindows(scene, { windowDuration: 0.4, hopDuration: 0.4 });
assertEqual(windows.length, 3, "partial final window");
assertNear(windows[0].sampleTime, 0.2, 1e-12, "first midpoint");
assertNear(windows[1].sampleTime, 0.6, 1e-12, "second midpoint");
assertNear(windows[2].sampleTime, 0.9, 1e-12, "partial midpoint");
assertNear(windows[2].duration, 0.2, 1e-12, "partial duration");
assertNear(windows[1].scene.sources[0].position[0], 1, 1e-12, "window x");
assertNear(windows[1].scene.sources[0].position[1], 2, 1e-12, "window y");
assertNear(windows[1].scene.sources[0].position[2], 4, 1e-12, "window z");
});
test("trajectory가 없는 sceneAtTime snapshot은 기존 합성 결과와 sample 단위로 동일하다", () => {
const scene = {
sampleRate: 8000,
duration: 0.01,
speedOfSound: 343,
seed: 17,
mix: { normalization: "none", targetPeak: 0.95 },
sources: [{
id: "fixed", name: "fixed", position: [0.1, -0.2, 1.2], gainDb: -12, startTime: 0, duration: 0.01,
signal: { type: "tone", frequency: 750, phaseDeg: 35 },
}],
ambient: [],
};
const arrayDefinition = {
microphones: [
{ id: "left", position: [-0.1, 0, 0] },
{ id: "right", position: [0.1, 0, 0], sensitivityDb: -1, phaseOffsetDeg: 3 },
],
};
const baseline = synthesizeMicrophoneSignals({ scene, arrayDefinition });
const sampled = synthesizeMicrophoneSignals({ scene: sceneAtTime(scene, 0.007), arrayDefinition });
assertEqual(sampled.appliedScale, baseline.appliedScale, "applied scale");
assertEqual(sampled.preNormalizationPeak, baseline.preNormalizationPeak, "pre-normalization peak");
baseline.channels.forEach((channel, channelIndex) => {
channel.forEach((value, sampleIndex) => {
assertEqual(sampled.channels[channelIndex][sampleIndex], value, `channel ${channelIndex} sample ${sampleIndex}`);
});
});
});
test("거리, dB와 구면 감쇠 기본식을 계산한다", () => {
assertNear(distance3([0, 0, 0], [1, 2, 2]), 3, 1e-12, "distance");
assertNear(dbToLinear(-6.020599913), 0.5, 1e-9, "dB amplitude");