Files
Find-Word/tests/dsp.test.ts
T

57 lines
2.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { ENVIRONMENTS, LEVELS } from "../src/content";
import {
dbToGain,
normalizeRms,
mixingGains,
validatePcm,
} from "../src/audio/dsp";
// 청감이나 실제 음압은 장치마다 다르므로 디지털 정규화와 gain 비율을 검사한다.
// 배경은 저장된 LUFS를 유지하고 음성만 RMS를 맞춘다. 표시 dB는 기준 대비
// 음성 gain 변화이며 서로 다른 신호의 실측 RMS가 같다는 뜻이 아니다.
const rms = (a: Float32Array) =>
Math.sqrt(a.reduce((sum, n) => sum + n * n, 0) / a.length);
describe("상대 dB와 고정 현장음 혼합", () => {
it("20 log10(음성 gain / 배경 gain)이 표시 dB와 일치한다", () => {
for (const level of LEVELS)
expect(20 * Math.log10(dbToGain(level.db))).toBeCloseTo(level.db, 10);
expect(dbToGain(-6)).toBeCloseTo(0.501187, 5);
});
it("평균 전력을 맞추고 원본 배열을 변경하지 않으며 무음은 오류로 알린다", () => {
const input = new Float32Array([0.4, -0.2, 0.1, -0.3]);
const output = normalizeRms(input);
expect(rms(output)).toBeCloseTo(0.12, 6);
expect(output).not.toBe(input);
expect(() => normalizeRms(new Float32Array(4))).toThrow("무음");
});
it("여덟 환경은 고유 same-origin WAV이며 절차적 합성 매개변수를 갖지 않는다", () => {
expect(new Set(ENVIRONMENTS.map((e) => e.audioUrl)).size).toBe(8);
for (const environment of ENVIRONMENTS) {
const url = new URL(environment.audioUrl, "http://localhost");
expect(url.origin).toBe("http://localhost");
expect(url.pathname).toBe(`/ambience/${environment.id}.wav`);
expect(environment).not.toHaveProperty("seed");
}
});
it("상한 밖 녹음은 거부하고 허용된 신호는 고정 gain으로 SNR 비율을 보존한다", () => {
const voice = normalizeRms(
new Float32Array([1, ...Array(999).fill(0)]),
0.03,
);
const background = normalizeRms(new Float32Array([0.2, -0.2]), 0.03);
for (const db of [-12, -6, 0, 6, 12]) {
expect(() => validatePcm(voice, "voice")).toThrow("피크");
validatePcm(background, "background");
const gains = mixingGains(db);
expect(20 * Math.log10(gains.voice / gains.background)).toBeCloseTo(
db,
10,
);
expect(
0.2 * (gains.voice + gains.intro) + 0.03 * gains.background,
).toBeLessThanOrEqual(0.800001);
}
});
});