feat: build reproducible dataset editor
This commit is contained in:
@@ -6,9 +6,11 @@ import {
|
||||
prepareDelaySpectrum,
|
||||
} from "/packages/acoustics-core/fractional-delay.js";
|
||||
import { dbToLinear, distance3 } from "/packages/acoustics-core/math.js";
|
||||
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 { encodeMonoPcm16Wav } from "/packages/acoustics-core/wav.js";
|
||||
|
||||
function assertNear(actual, expected, tolerance, label) {
|
||||
assert(Math.abs(actual - expected) <= tolerance, `${label}: expected ${expected} ± ${tolerance}, received ${actual}`);
|
||||
@@ -302,3 +304,68 @@ test("CPU DAS가 event-loop 취소와 working-set 한도를 지킨다", async ()
|
||||
assert(stftStarted, "STFT started before abort");
|
||||
assert(abortError?.name === "AbortError", "async abort");
|
||||
});
|
||||
|
||||
test("scene mix가 peak 정규화와 clipping 통계를 정확히 기록한다", () => {
|
||||
const assetBuffers = new Map([
|
||||
["levels", { sampleRate: 4, channels: [Float32Array.of(2, -1.5, 1, -0.5)] }],
|
||||
]);
|
||||
const scene = {
|
||||
sampleRate: 4,
|
||||
duration: 1,
|
||||
seed: 1,
|
||||
mix: { normalization: "peak", targetPeak: 0.5 },
|
||||
sources: [{
|
||||
id: "levels",
|
||||
gainDb: 0,
|
||||
startTime: 0,
|
||||
duration: 1,
|
||||
signal: { type: "asset", assetId: "levels" },
|
||||
}],
|
||||
ambient: [],
|
||||
};
|
||||
|
||||
const normalized = renderSceneMix(scene, assetBuffers);
|
||||
assertNear(normalized.preNormalizationPeak, 2, 1e-12, "pre-normalization peak");
|
||||
assertNear(normalized.appliedScale, 0.25, 1e-12, "normalization scale");
|
||||
assertNear(normalized.peak, 0.5, 1e-12, "normalized peak");
|
||||
assertEqual(normalized.clippedSampleCount, 0, "normalized clipping count");
|
||||
[0.5, -0.375, 0.25, -0.125].forEach((expected, index) => {
|
||||
assertNear(normalized.samples[index], expected, 1e-12, `normalized sample ${index}`);
|
||||
});
|
||||
|
||||
const unnormalized = renderSceneMix({ ...scene, mix: { normalization: "none", targetPeak: 0.5 } }, assetBuffers);
|
||||
assertNear(unnormalized.preNormalizationPeak, 2, 1e-12, "unnormalized input peak");
|
||||
assertNear(unnormalized.appliedScale, 1, 1e-12, "unnormalized scale");
|
||||
assertNear(unnormalized.peak, 2, 1e-12, "unnormalized output peak");
|
||||
assertEqual(unnormalized.clippedSampleCount, 2, "unnormalized clipping count");
|
||||
});
|
||||
|
||||
test("PCM16 WAV가 표준 mono header를 쓰고 범위 밖 sample을 clamp한다", async () => {
|
||||
const blob = encodeMonoPcm16Wav(Float32Array.of(-2, -1, -0.5, 0, 0.5, 1, 2), 8000);
|
||||
const buffer = await blob.arrayBuffer();
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const view = new DataView(buffer);
|
||||
const ascii = (offset, length) => String.fromCharCode(...bytes.subarray(offset, offset + length));
|
||||
|
||||
assertEqual(blob.type, "audio/wav", "WAV MIME type");
|
||||
assertEqual(buffer.byteLength, 58, "WAV byte length");
|
||||
assertEqual(ascii(0, 4), "RIFF", "RIFF id");
|
||||
assertEqual(view.getUint32(4, true), 50, "RIFF chunk size");
|
||||
assertEqual(ascii(8, 4), "WAVE", "WAVE id");
|
||||
assertEqual(ascii(12, 4), "fmt ", "fmt id");
|
||||
assertEqual(view.getUint32(16, true), 16, "PCM fmt chunk size");
|
||||
assertEqual(view.getUint16(20, true), 1, "PCM format");
|
||||
assertEqual(view.getUint16(22, true), 1, "mono channel count");
|
||||
assertEqual(view.getUint32(24, true), 8000, "sample rate");
|
||||
assertEqual(view.getUint32(28, true), 16000, "byte rate");
|
||||
assertEqual(view.getUint16(32, true), 2, "block align");
|
||||
assertEqual(view.getUint16(34, true), 16, "bits per sample");
|
||||
assertEqual(ascii(36, 4), "data", "data id");
|
||||
assertEqual(view.getUint32(40, true), 14, "PCM data byte length");
|
||||
|
||||
const actualSamples = Array.from({ length: 7 }, (_, index) => view.getInt16(44 + index * 2, true));
|
||||
const expectedSamples = [-32768, -32768, -16384, 0, 16383, 32767, 32767];
|
||||
expectedSamples.forEach((expected, index) => {
|
||||
assertEqual(actualSamples[index], expected, `PCM sample ${index}`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user