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
+92
View File
@@ -165,6 +165,98 @@ test("portable raw-map peak detection이 같은 band의 두 진원 후보를 강
);
});
test("portable peak detection treats MUSIC noise projection as a minimum", () => {
const scan = {
z: 1,
xMin: 0,
xMax: 4,
yMin: 0,
yMax: 4,
columns: 5,
rows: 5,
};
const q = new Float64Array(25);
q.fill(1);
q[6] = 0.1;
q[18] = 0.2;
q[3] = 0.6;
const peaks = detectBandPeaks(q, scan, {
rawScale: "noise-projection",
objectiveDirection: "minimum",
});
assertEqual(peaks.length, 2, "two q minima exceed the merit threshold");
assertEqual(peaks[0].index, 6, "smallest q ranks first");
assert(
Math.abs(peaks[0].noiseProjection - 0.1) <= 1e-12,
"raw q is retained",
);
assertEqual(peaks[0].objectiveDirection, "minimum", "minimum objective is explicit");
assertEqual(
detectBandPeaks(
new Float64Array(25).fill(0.5),
scan,
{
rawScale: "noise-projection",
objectiveDirection: "minimum",
},
).length,
0,
"flat q does not choose the first grid cell",
);
});
test("portable and streaming MUSIC consume unavailable auto-K windows without stalling", async () => {
const fixture = createFixture(256);
const musicConfig = {
...fixture.config,
algorithm: "music",
normalization: "none",
stft: { fftSize: 64, hopSize: 64 },
musicSourceCountMethod: "mdl",
musicSourceCount: 1,
};
const plan = createLocalizationPlan(musicConfig);
const block = await localizeMultichannel({
plan,
channels: sliceChannels(fixture.channels, 0, 128),
});
assertEqual(block.status, "unavailable", "short auto-K block is typed unavailable");
assertEqual(
block.unavailable.reason,
"insufficient-snapshots-for-source-count-selection",
"unavailable reason is explicit",
);
assertEqual(block.frameCount, 2, "unavailable result preserves analyzed frame count");
assertEqual(block.bands.length, 0, "no arbitrary MUSIC map");
assertEqual(block.locations.length, 0, "no arbitrary location");
const stream = createStreamingLocalizer({
...musicConfig,
analysisWindowSamples: 128,
analysisHopSamples: 128,
maximumBufferedSamples: 256,
maximumResultsPerCall: 2,
});
stream.append(fixture.channels);
const results = await stream.analyzeAvailable({
maximumResults: 2,
});
assertEqual(results.length, 2, "both unavailable windows are emitted");
assert(
results.every(
(result) =>
result.status === "unavailable" &&
result.locations.length === 0,
),
"each window remains explicitly location-free",
);
const streamState = stream.getState();
assertEqual(streamState.completedAnalyses, 2, "unavailable windows commit");
assertEqual(streamState.failedAnalyses, 0, "unavailable is not a runtime failure");
assertEqual(streamState.bufferedSamples, 0, "stream advances past both windows");
});
test("CPU 분석 계획이 반복 geometry·steering 연산과 bin map 할당을 실제로 제거한다", async () => {
const fixture = createFixture(128);
const plan = createLocalizationPlan(fixture.config);