feat: add physically explicit narrowband MUSIC

This commit is contained in:
2026-08-29 12:40:48 +09:00
parent 044be3cf92
commit 11fee76280
40 changed files with 6473 additions and 275 deletions
+40 -4
View File
@@ -40,6 +40,8 @@ function resultFrequencyObservation(scene, experiment, band, equivalentBandIds =
localizationModel:
experiment.beamforming?.algorithm === "gcc-phat"
? "band-limited-gcc-phat-geometric-lag"
: experiment.beamforming?.algorithm === "music"
? "near-field-music-at-positive-adc-bin-frequency"
: "spherical-steering-at-positive-adc-bin-frequency",
originalCarrierUsed: observation.originalCarrierUsed,
aliasOrderResolution: observation.aliased ? "not-attempted" : "not-applicable",
@@ -234,17 +236,26 @@ test("단일 tone 장면과 실험이 관계 검증을 통과한다", async () =
assertEqual((await validateExperiment(experiment, { arrayDefinition, scene })).length, 0, "experiment issues");
});
test("세 위치 추정 알고리즘과 backend 제약을 계약이 검증한다", async () => {
test("네 위치 추정 알고리즘과 backend 제약을 계약이 검증한다", async () => {
const [arrayDefinition, scene, baseline] = await Promise.all([
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
loadJson("/assets/sample-scenes/single-tone.scene.json"),
loadJson("/assets/sample-scenes/single-tone.experiment.json"),
]);
for (const algorithm of ["das", "das-phat", "gcc-phat"]) {
for (const algorithm of ["das", "das-phat", "gcc-phat", "music"]) {
const experiment = structuredClone(baseline);
experiment.beamforming.algorithm = algorithm;
experiment.beamforming.backend = algorithm === "gcc-phat" ? "cpu" : "auto";
experiment.beamforming.backend =
algorithm === "gcc-phat" || algorithm === "music" ? "cpu" : "auto";
if (algorithm === "music") {
experiment.frequencyBands = [
{ id: "single-bin", minHz: 1000, maxHz: 1050, color: "#65f2bf" },
];
experiment.beamforming.normalization = "none";
experiment.beamforming.musicSourceCountMethod = "fixed";
experiment.beamforming.musicSourceCount = 1;
}
assertEqual(
(await validateExperiment(experiment, { arrayDefinition, scene })).length,
0,
@@ -252,6 +263,31 @@ test("세 위치 추정 알고리즘과 backend 제약을 계약이 검증한다
);
}
const autoWithStaleK = structuredClone(baseline);
autoWithStaleK.frequencyBands = [
{ id: "single-bin", minHz: 1000, maxHz: 1050, color: "#65f2bf" },
];
autoWithStaleK.stft.hopSize = autoWithStaleK.stft.fftSize;
Object.assign(autoWithStaleK.beamforming, {
algorithm: "music",
backend: "cpu",
normalization: "none",
musicSourceCountMethod: "mdl",
musicSourceCount: 8,
});
const smallArray = {
...arrayDefinition,
microphones: arrayDefinition.microphones.slice(0, 4),
};
assertEqual(
(await validateExperiment(autoWithStaleK, {
arrayDefinition: smallArray,
scene,
})).length,
0,
"MDL ignores disabled stale fixed K after an array-size change",
);
const incompatible = structuredClone(baseline);
incompatible.beamforming.algorithm = "gcc-phat";
incompatible.beamforming.backend = "webgpu";
@@ -271,7 +307,7 @@ test("세 위치 추정 알고리즘과 backend 제약을 계약이 검증한다
"GCC-PHAT requires two microphones",
);
incompatible.beamforming.algorithm = "music";
incompatible.beamforming.algorithm = "invented-localizer";
issues = await validateExperiment(incompatible, { arrayDefinition, scene });
assert(
issues.some(({ path }) => path === "$.beamforming.algorithm"),