feat: add realistic portable localization core
This commit is contained in:
@@ -61,6 +61,43 @@ test("단일 tone 장면과 실험이 관계 검증을 통과한다", async () =
|
||||
assertEqual((await validateExperiment(experiment, { arrayDefinition, scene })).length, 0, "experiment issues");
|
||||
});
|
||||
|
||||
test("shoebox 음향 환경의 room과 occluder 경계를 검증한다", async () => {
|
||||
const scene = await loadJson("/assets/sample-scenes/single-tone.scene.json");
|
||||
scene.acoustics = {
|
||||
model: "shoebox",
|
||||
room: { min: [-2, -2, -1], max: [2, 2, 3] },
|
||||
reflections: { order: 1, wallGainDb: -8, scattering: 0.2 },
|
||||
diffuseReverb: { enabled: true, rt60Seconds: 0.35, gainDb: -20, tapCount: 12 },
|
||||
occluders: [{
|
||||
id: "screen",
|
||||
min: [-0.2, -0.2, 0.8],
|
||||
max: [0.2, 0.2, 1],
|
||||
transmissionDb: -18,
|
||||
diffractionGainDb: -12,
|
||||
}],
|
||||
};
|
||||
assertEqual((await validateScene(scene)).length, 0, "valid shoebox scene");
|
||||
|
||||
scene.acoustics.room.max[0] = scene.acoustics.room.min[0];
|
||||
let issues = await validateScene(scene);
|
||||
assert(issues.some(({ path }) => path === "$.acoustics.room"), "unordered room bounds");
|
||||
|
||||
scene.acoustics.room.max[0] = 2;
|
||||
scene.acoustics.occluders[0].max[2] = 4;
|
||||
issues = await validateScene(scene);
|
||||
assert(
|
||||
issues.some(({ path, message }) => path.includes("occluders[0]") && message.includes("room")),
|
||||
"occluder outside room",
|
||||
);
|
||||
|
||||
scene.acoustics = {
|
||||
model: "free-field",
|
||||
room: { min: [-2, -2, -1], max: [2, 2, 3] },
|
||||
};
|
||||
issues = await validateScene(scene);
|
||||
assert(issues.some(({ path }) => path === "$.acoustics"), "unused free-field settings");
|
||||
});
|
||||
|
||||
test("Experiment 피크 검출 임계값 비율은 재현 가능하고 유효한 범위여야 한다", async () => {
|
||||
const [arrayDefinition, scene, experiment] = await Promise.all([
|
||||
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
|
||||
@@ -125,6 +162,32 @@ test("중복 마이크 id를 거부한다", async () => {
|
||||
assert((await validateArrayDefinition(arrayDefinition)).some(({ message }) => message.includes("중복")));
|
||||
});
|
||||
|
||||
test("마이크 주파수 응답점은 안전한 범위에서 주파수 오름차순이어야 한다", async () => {
|
||||
const arrayDefinition = await loadJson("/assets/array-geometries/rectangular-16x12.array.json");
|
||||
arrayDefinition.microphones[0].frequencyResponse = [
|
||||
{ frequencyHz: 20, gainDb: -3 },
|
||||
{ frequencyHz: 1000, gainDb: 1.5, phaseDeg: 12 },
|
||||
{ frequencyHz: 20_000, gainDb: -2 },
|
||||
];
|
||||
assertEqual((await validateArrayDefinition(arrayDefinition)).length, 0, "valid response curve");
|
||||
|
||||
arrayDefinition.microphones[0].frequencyResponse[2].frequencyHz = 1000;
|
||||
let issues = await validateArrayDefinition(arrayDefinition);
|
||||
assert(
|
||||
issues.some(({ path, message }) =>
|
||||
path === "$.microphones[0].frequencyResponse[2].frequencyHz" &&
|
||||
message.includes("엄격히 증가")),
|
||||
"duplicate frequency is rejected with sorting guidance",
|
||||
);
|
||||
|
||||
arrayDefinition.microphones[0].frequencyResponse[2].frequencyHz = 1_000_001;
|
||||
issues = await validateArrayDefinition(arrayDefinition);
|
||||
assert(
|
||||
issues.some(({ path }) => path === "$.microphones[0].frequencyResponse[2].frequencyHz"),
|
||||
"globally bounded frequency is enforced without a Scene Nyquist dependency",
|
||||
);
|
||||
});
|
||||
|
||||
test("Nyquist를 넘는 tone을 거부한다", async () => {
|
||||
const scene = {
|
||||
schemaVersion: "1.0.0", kind: "micarrsim.scene", id: "nyquist-scene", name: "Nyquist test", units: "m",
|
||||
@@ -156,11 +219,13 @@ test("JSON Schema의 누락 필드와 범위·알 수 없는 필드를 거부한
|
||||
scene.unexpected = true;
|
||||
delete experiment.beamforming.normalization;
|
||||
experiment.beamforming.dynamicRangeDb = 4;
|
||||
experiment.beamforming.compensateFrequencyResponse = "false";
|
||||
assert((await validateArrayDefinition(arrayDefinition)).some(({ path }) => path.endsWith("phaseOffsetDeg")));
|
||||
assert((await validateScene(scene)).some(({ path }) => path === "$.unexpected"));
|
||||
const experimentIssues = await validateExperiment(experiment, { arrayDefinition, scene });
|
||||
assert(experimentIssues.some(({ path }) => path.endsWith("normalization")));
|
||||
assert(experimentIssues.some(({ path }) => path.endsWith("dynamicRangeDb")));
|
||||
assert(experimentIssues.some(({ path }) => path.endsWith("compensateFrequencyResponse")));
|
||||
});
|
||||
|
||||
test("image/audio asset 종류와 안전하지 않은 경로를 거부한다", async () => {
|
||||
|
||||
Reference in New Issue
Block a user