feat: build reproducible dataset editor
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { renderSignal } from "./signal.js";
|
||||
|
||||
export const MAX_MIX_WORKING_BYTES = 384 * 1024 * 1024;
|
||||
export const MAX_MIX_SAMPLE_OPERATIONS = 250_000_000;
|
||||
|
||||
export function estimateSceneMix(scene, assetBuffers = new Map()) {
|
||||
const sampleCount = Math.ceil(scene.duration * scene.sampleRate);
|
||||
const entryCount = scene.sources.length + (scene.ambient?.length ?? 0);
|
||||
let assetBytes = 0;
|
||||
for (const asset of assetBuffers.values()) {
|
||||
for (const channel of asset.channels ?? []) assetBytes += channel.byteLength ?? 0;
|
||||
}
|
||||
return {
|
||||
sampleCount,
|
||||
entryCount,
|
||||
assetBytes,
|
||||
workingBytes: sampleCount * Float32Array.BYTES_PER_ELEMENT * 2 + assetBytes,
|
||||
sampleOperations: sampleCount * entryCount,
|
||||
};
|
||||
}
|
||||
|
||||
export function renderSceneMix(scene, assetBuffers = new Map()) {
|
||||
const estimate = estimateSceneMix(scene, assetBuffers);
|
||||
if (!Number.isSafeInteger(estimate.sampleCount) || estimate.sampleCount <= 0) {
|
||||
throw new Error("장면 sample 수가 올바르지 않습니다.");
|
||||
}
|
||||
if (estimate.workingBytes > MAX_MIX_WORKING_BYTES) {
|
||||
throw new Error(
|
||||
`확인용 mix 작업 메모리 ${(estimate.workingBytes / 1024 ** 2).toFixed(1)} MiB가 ` +
|
||||
`${(MAX_MIX_WORKING_BYTES / 1024 ** 2).toFixed(0)} MiB 한도를 넘습니다. duration 또는 sample rate를 줄이세요.`,
|
||||
);
|
||||
}
|
||||
if (estimate.sampleOperations > MAX_MIX_SAMPLE_OPERATIONS) {
|
||||
throw new Error(
|
||||
`확인용 mix 연산량 ${estimate.sampleOperations.toLocaleString()} samples가 ` +
|
||||
`${MAX_MIX_SAMPLE_OPERATIONS.toLocaleString()} 한도를 넘습니다. duration 또는 음원 수를 줄이세요.`,
|
||||
);
|
||||
}
|
||||
const { sampleCount } = estimate;
|
||||
const samples = new Float32Array(sampleCount);
|
||||
for (const source of scene.sources) {
|
||||
const sourceSamples = renderSignal(source, scene, assetBuffers);
|
||||
for (let index = 0; index < sampleCount; index += 1) samples[index] += sourceSamples[index];
|
||||
}
|
||||
for (const ambient of scene.ambient ?? []) {
|
||||
const ambientSamples = renderSignal(ambient, scene, assetBuffers);
|
||||
for (let index = 0; index < sampleCount; index += 1) samples[index] += ambientSamples[index];
|
||||
}
|
||||
|
||||
let preNormalizationPeak = 0;
|
||||
for (const value of samples) preNormalizationPeak = Math.max(preNormalizationPeak, Math.abs(value));
|
||||
const appliedScale =
|
||||
scene.mix?.normalization === "peak" && preNormalizationPeak > 0
|
||||
? scene.mix.targetPeak / preNormalizationPeak
|
||||
: 1;
|
||||
let peak = 0;
|
||||
let clippedSampleCount = 0;
|
||||
for (let index = 0; index < samples.length; index += 1) {
|
||||
samples[index] *= appliedScale;
|
||||
peak = Math.max(peak, Math.abs(samples[index]));
|
||||
if (Math.abs(samples[index]) > 1) clippedSampleCount += 1;
|
||||
}
|
||||
return { samples, sampleRate: scene.sampleRate, preNormalizationPeak, appliedScale, peak, clippedSampleCount };
|
||||
}
|
||||
Reference in New Issue
Block a user