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
+25 -3
View File
@@ -15,7 +15,16 @@ function sampleAsset(signal, assetBuffers, time) {
return channel[left] + (rightValue - channel[left]) * fraction;
}
export function renderSignal(entry, scene, assetBuffers = new Map(), seedSalt = 0) {
/**
* Render one signal entry into the scene-sized output buffer.
*
* `timeMapping` lets a short, rebased scene window retain the original signal
* timeline. `sceneSampleOffset` maps output sample zero to an absolute sample
* in the original scene, while `signalStartTime`/`signalStartSample` preserve
* the entry's original tone/asset time origin and deterministic noise index.
* Omitting it keeps the historical whole-scene behaviour exactly unchanged.
*/
export function renderSignal(entry, scene, assetBuffers = new Map(), seedSalt = 0, timeMapping = {}) {
const sampleCount = Math.ceil(scene.duration * scene.sampleRate);
const output = new Float32Array(sampleCount);
const startTime = entry.startTime ?? 0;
@@ -23,12 +32,25 @@ export function renderSignal(entry, scene, assetBuffers = new Map(), seedSalt =
const gain = dbToLinear(entry.gainDb ?? 0);
const startSample = Math.max(0, Math.ceil(startTime * scene.sampleRate));
const endSample = Math.min(sampleCount, Math.ceil((startTime + duration) * scene.sampleRate));
const sceneSampleOffset = timeMapping.sceneSampleOffset ?? 0;
const signalStartTime = timeMapping.signalStartTime ?? startTime;
const signalStartSample = timeMapping.signalStartSample ?? Math.ceil(startTime * scene.sampleRate);
if (!Number.isSafeInteger(sceneSampleOffset) || sceneSampleOffset < 0) {
throw new RangeError("sceneSampleOffset must be a non-negative safe integer.");
}
if (!Number.isFinite(signalStartTime)) {
throw new TypeError("signalStartTime must be finite.");
}
if (!Number.isSafeInteger(signalStartSample) || signalStartSample < 0) {
throw new RangeError("signalStartSample must be a non-negative safe integer.");
}
const signal = entry.signal;
const seed = (scene.seed + (signal.seedOffset ?? 0) + hashString(entry.id ?? "signal") + seedSalt) >>> 0;
for (let sampleIndex = startSample; sampleIndex < endSample; sampleIndex += 1) {
const localIndex = sampleIndex - startSample;
const localTime = sampleIndex / scene.sampleRate - startTime;
const absoluteSampleIndex = sceneSampleOffset + sampleIndex;
const localIndex = absoluteSampleIndex - signalStartSample;
const localTime = absoluteSampleIndex / scene.sampleRate - signalStartTime;
let value;
if (signal.type === "tone") {
value = Math.sin(TAU * signal.frequency * localTime + ((signal.phaseDeg ?? 0) * Math.PI) / 180);