feat: synthesize trajectory analysis windows

This commit is contained in:
Codex
2026-08-28 16:05:22 +09:00
parent a20b55e8e2
commit 6280fa4dfd
4 changed files with 198 additions and 11 deletions
+65 -5
View File
@@ -74,13 +74,73 @@ export function sampleQuasiStaticWindow(scene, startTime, endTime) {
if (Number.isFinite(scene?.duration) && endTime > scene.duration) {
throw new RangeError("window endTime must not exceed scene.duration.");
}
const sampleTime = startTime + (endTime - startTime) / 2;
const sampleRate = scene?.sampleRate;
if (!Number.isInteger(sampleRate) || sampleRate <= 0) {
const sampleTime = startTime + (endTime - startTime) / 2;
return {
startTime,
endTime,
sampleTime,
duration: endTime - startTime,
scene: sceneAtTime(scene, sampleTime),
};
}
// Ceil selects the first sample whose timestamp is inside each half-open
// boundary. Adjacent windows therefore share an exact boundary without a
// duplicated or omitted sample, even when their requested times are not
// integer multiples of the sample period.
const startSample = Math.ceil(startTime * sampleRate);
const endSample = Math.ceil(endTime * sampleRate);
if (endSample <= startSample) {
throw new RangeError("window must contain at least one sample.");
}
const alignedStartTime = startSample / sampleRate;
const alignedEndTime = endSample / sampleRate;
const sampleTime = (startSample + endSample) / (2 * sampleRate);
const sources = [];
const sourceOffsets = [];
for (const source of scene.sources) {
const signalStartTime = source.startTime ?? 0;
const signalDuration = source.duration ?? scene.duration;
const signalStartSample = Math.ceil(signalStartTime * sampleRate);
const signalEndSample = Math.ceil((signalStartTime + signalDuration) * sampleRate);
const activeStartSample = Math.max(startSample, signalStartSample);
const activeEndSample = Math.min(endSample, signalEndSample);
if (activeEndSample <= activeStartSample) continue;
sources.push({
...source,
position: sourcePositionAtTime(source, sampleTime),
startTime: (activeStartSample - startSample) / sampleRate,
duration: (activeEndSample - activeStartSample) / sampleRate,
});
sourceOffsets.push({ signalStartTime, signalStartSample });
}
const ambient = Array.isArray(scene.ambient) ? scene.ambient.slice() : scene.ambient;
const signalOffsets = {
sceneSampleOffset: startSample,
sources: sourceOffsets,
ambient: (scene.ambient ?? []).map(() => ({ signalStartTime: 0, signalStartSample: 0 })),
};
const duration = (endSample - startSample) / sampleRate;
return {
startTime,
endTime,
startTime: alignedStartTime,
endTime: alignedEndTime,
sampleTime,
duration: endTime - startTime,
scene: sceneAtTime(scene, sampleTime),
startSample,
endSample,
sampleCount: endSample - startSample,
duration,
signalOffsets,
scene: {
...scene,
duration,
sources,
...(Array.isArray(scene.ambient) ? { ambient } : {}),
},
};
}