feat: add varied microphone array presets

This commit is contained in:
2026-08-28 19:43:46 +09:00
parent 85ad056a73
commit 7fe846fbf9
14 changed files with 7086 additions and 77 deletions
+25
View File
@@ -196,6 +196,31 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
!documentObject.querySelector("#run-button").disabled,
"original comparison configuration restore",
);
const comparisonPresets = [
["rectangular-2x2-450mm", "4 ch", "0.450 × 0.450 m"],
["rectangular-4x3-450mm", "12 ch", "0.450 × 0.450 m"],
["rectangular-8x6-450mm", "48 ch", "0.450 × 0.450 m"],
["rectangular-24x16-450mm", "384 ch", "0.450 × 0.450 m"],
["rectangular-16x12-compact", "192 ch", "0.120 × 0.120 m"],
["rectangular-16x12-large", "192 ch", "1.200 × 1.200 m"],
];
assertEqual(
documentObject.querySelectorAll("#array-preset option").length,
9,
"all built-in and custom array choices are visible",
);
for (const [presetId, microphoneCount, aperture] of comparisonPresets) {
dispatchValue(windowObject, documentObject.querySelector("#array-preset"), presetId);
await waitFor(
() => documentObject.querySelector("#array-preset").value === presetId &&
documentObject.querySelector("#microphone-count").textContent === microphoneCount &&
documentObject.querySelector("#array-meta").textContent.includes(aperture) &&
documentObject.documentElement.dataset.cameraValid === "true",
`${presetId} preset`,
);
}
dispatchValue(
windowObject,
documentObject.querySelector("#array-preset"),
+96
View File
@@ -11,6 +11,16 @@ async function loadJson(path) {
return response.json();
}
const RECTANGULAR_ARRAY_EXAMPLES = [
{ id: "rectangular-2x2-450mm", microphoneCount: 4, columns: 2, rows: 2, width: 0.45, height: 0.45 },
{ id: "rectangular-4x3-450mm", microphoneCount: 12, columns: 4, rows: 3, width: 0.45, height: 0.45 },
{ id: "rectangular-8x6-450mm", microphoneCount: 48, columns: 8, rows: 6, width: 0.45, height: 0.45 },
{ id: "rectangular-16x12", microphoneCount: 192, columns: 16, rows: 12, width: 0.45, height: 0.45 },
{ id: "rectangular-24x16-450mm", microphoneCount: 384, columns: 24, rows: 16, width: 0.45, height: 0.45 },
{ id: "rectangular-16x12-compact", microphoneCount: 192, columns: 16, rows: 12, width: 0.12, height: 0.12 },
{ id: "rectangular-16x12-large", microphoneCount: 192, columns: 16, rows: 12, width: 1.2, height: 1.2 },
];
test("moving-tone sample Scene and Experiment satisfy their linked contracts", async () => {
const [arrayDefinition, scene, experiment] = await Promise.all([
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
@@ -36,6 +46,92 @@ test("192채널 기준 배열이 계약을 통과한다", async () => {
assertEqual((await validateArrayDefinition(arrayDefinition)).length, 0, "array issues");
});
test("rectangular array examples cover low-to-high channel counts and compact-to-wide apertures", async () => {
const arrays = await Promise.all(RECTANGULAR_ARRAY_EXAMPLES.map(({ id }) =>
loadJson(`/assets/array-geometries/${id}.array.json`)));
for (let index = 0; index < arrays.length; index += 1) {
const arrayDefinition = arrays[index];
const expected = RECTANGULAR_ARRAY_EXAMPLES[index];
assertEqual(arrayDefinition.id, expected.id, `${expected.id} fixture id`);
assertEqual(arrayDefinition.microphones.length, expected.microphoneCount, `${expected.id} channel count`);
assertEqual(arrayDefinition.aperture.width, expected.width, `${expected.id} aperture width`);
assertEqual(arrayDefinition.aperture.height, expected.height, `${expected.id} aperture height`);
assertEqual(
(await validateArrayDefinition(arrayDefinition)).length,
0,
`${expected.id} contract issues`,
);
assertEqual(
new Set(arrayDefinition.microphones.map(({ id }) => id)).size,
expected.microphoneCount,
`${expected.id} microphone ids are unique`,
);
assertEqual(
new Set(arrayDefinition.microphones.map(({ position }) => position.join(","))).size,
expected.microphoneCount,
`${expected.id} positions are unique`,
);
const xCoordinates = arrayDefinition.microphones.map(({ position }) => position[0]);
const yCoordinates = arrayDefinition.microphones.map(({ position }) => position[1]);
assert(
arrayDefinition.microphones.every(({ position }) => position[2] === 0),
`${expected.id} microphones stay on the z=0 plane`,
);
assertEqual(new Set(xCoordinates).size, expected.columns, `${expected.id} column count`);
assertEqual(new Set(yCoordinates).size, expected.rows, `${expected.id} row count`);
const uniqueX = [...new Set(xCoordinates)].sort((left, right) => left - right);
const uniqueY = [...new Set(yCoordinates)].sort((left, right) => left - right);
const expectedXPitch = expected.width / (expected.columns - 1);
const expectedYPitch = expected.height / (expected.rows - 1);
assert(
uniqueX.slice(1).every((value, position) =>
Math.abs(value - uniqueX[position] - expectedXPitch) < 1.1e-6),
`${expected.id} uniform x pitch`,
);
assert(
uniqueY.slice(1).every((value, position) =>
Math.abs(value - uniqueY[position] - expectedYPitch) < 1.1e-6),
`${expected.id} uniform y pitch`,
);
assert(Math.abs(Math.min(...xCoordinates) + expected.width / 2) < 1e-6, `${expected.id} x minimum`);
assert(Math.abs(Math.max(...xCoordinates) - expected.width / 2) < 1e-6, `${expected.id} x maximum`);
assert(Math.abs(Math.min(...yCoordinates) + expected.height / 2) < 1e-6, `${expected.id} y minimum`);
assert(Math.abs(Math.max(...yCoordinates) - expected.height / 2) < 1e-6, `${expected.id} y maximum`);
}
const channelSeries = arrays.slice(0, 5);
assert(
channelSeries.every(({ aperture }) => aperture.width === 0.45 && aperture.height === 0.45),
"channel-count series keeps the 0.45 m aperture fixed",
);
assertEqual(
channelSeries.map(({ microphones }) => microphones.length).join(","),
"4,12,48,192,384",
"channel-count series",
);
const apertureSeries = [
arrays.find(({ id }) => id === "rectangular-16x12-compact"),
arrays.find(({ id }) => id === "rectangular-16x12"),
arrays.find(({ id }) => id === "rectangular-16x12-large"),
];
assert(
apertureSeries.every(({ microphones }) => microphones.length === 192),
"aperture series keeps 192 channels fixed",
);
assertEqual(
apertureSeries.map(({ aperture }) => aperture.width).join(","),
"0.12,0.45,1.2",
"compact-to-large aperture series",
);
assertEqual(
Math.max(...arrays.map(({ microphones }) => microphones.length)),
384,
"high-count example",
);
});
test("192채널 Fermat spiral 비교 배열이 계약과 원형 aperture를 만족한다", async () => {
const arrayDefinition = await loadJson("/assets/array-geometries/fermat-spiral-192.array.json");
assertEqual(arrayDefinition.microphones.length, 192, "spiral microphone count");