import { dbToLinear, TAU } from "./math.js"; import { hashString, pinkNoiseAt, whiteNoiseAt } from "./noise.js"; function sampleAsset(signal, assetBuffers, time) { const asset = assetBuffers.get(signal.assetId); if (!asset) throw new Error(`오디오 asset을 찾을 수 없습니다: ${signal.assetId}`); const channel = asset.channels[signal.channel ?? 0]; if (!channel) throw new Error(`${signal.assetId}의 channel ${signal.channel ?? 0}을 찾을 수 없습니다.`); let sourceIndex = time * asset.sampleRate; if (signal.loop && channel.length > 0) sourceIndex %= channel.length; if (sourceIndex < 0 || sourceIndex >= channel.length) return 0; const left = Math.floor(sourceIndex); const fraction = sourceIndex - left; const rightValue = channel[Math.min(left + 1, channel.length - 1)]; return channel[left] + (rightValue - channel[left]) * fraction; } export function renderSignal(entry, scene, assetBuffers = new Map(), seedSalt = 0) { const sampleCount = Math.ceil(scene.duration * scene.sampleRate); const output = new Float32Array(sampleCount); const startTime = entry.startTime ?? 0; const duration = entry.duration ?? scene.duration; 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 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; let value; if (signal.type === "tone") { value = Math.sin(TAU * signal.frequency * localTime + ((signal.phaseDeg ?? 0) * Math.PI) / 180); } else if (signal.type === "noise") { value = signal.color === "pink" ? pinkNoiseAt(seed, localIndex) : whiteNoiseAt(seed, localIndex); } else if (signal.type === "asset") { value = sampleAsset(signal, assetBuffers, localTime); } else { throw new Error(`지원하지 않는 signal type: ${signal.type}`); } output[sampleIndex] = value * gain; } return output; } export function sampleLinear(buffer, sampleIndex) { if (sampleIndex < 0 || sampleIndex >= buffer.length) return 0; const left = Math.floor(sampleIndex); const fraction = sampleIndex - left; const rightValue = buffer[Math.min(left + 1, buffer.length - 1)]; return buffer[left] + (rightValue - buffer[left]) * fraction; }