perf(analysis): reuse sorted spectral percentile windows

Reuse sorted windows for related percentiles and reject unsuitable edges before computing expensive tail statistics. Preserve cutoff results with 31 regression fixtures and 400 exact old/new comparisons.
This commit is contained in:
zarzet
2026-09-20 17:21:11 +07:00
parent de238fd0cd
commit 7691acecef
2 changed files with 126 additions and 33 deletions
+29 -33
View File
@@ -430,18 +430,9 @@ double? estimateEffectiveSpectralCutoffHz({
final centralStart = math.max(0, (height * 0.05).floor());
final centralEnd = math.min(height, (height * 0.95).ceil());
final lowLevel = _spectralPercentile(
smoothed,
centralStart,
centralEnd,
0.10,
);
final highLevel = _spectralPercentile(
smoothed,
centralStart,
centralEnd,
0.95,
);
final central = _sortedSpectralWindow(smoothed, centralStart, centralEnd);
final lowLevel = _sortedSpectralPercentile(central, 0.10);
final highLevel = _sortedSpectralPercentile(central, 0.95);
final dynamicSpan = highLevel - lowLevel;
if (highLevel < 24) return null;
if (dynamicSpan < 1) return maxFrequencyHz;
@@ -485,16 +476,19 @@ double? estimateEffectiveSpectralCutoffHz({
if (belowEnd > belowStart && aboveEnd > aboveStart) {
final belowLevel = _spectralMedian(smoothed, belowStart, belowEnd);
final aboveLevel = _spectralMedian(smoothed, aboveStart, aboveEnd);
final tailLevel = _spectralMedian(smoothed, aboveStart, height);
if (belowLevel - aboveLevel < minimumDrop) continue;
// All tail statistics share one sorted window. Rejected local edges do
// not need to copy and sort the rest of the spectrum at all.
final tail = _sortedSpectralWindow(smoothed, aboveStart, height);
final tailLevel = _sortedSpectralPercentile(tail, 0.50);
if (belowLevel - tailLevel < minimumDrop) continue;
final tailSpread =
_spectralPercentile(smoothed, aboveStart, height, 0.80) -
_spectralPercentile(smoothed, aboveStart, height, 0.20);
_sortedSpectralPercentile(tail, 0.80) -
_sortedSpectralPercentile(tail, 0.20);
if (tailSpread > stableTailSpread) continue;
final baseStart = math.max(0, edgeIndex - (3000 / hzPerRow).ceil());
final baseLevel = _spectralMedian(smoothed, baseStart, belowEnd);
if (belowLevel - aboveLevel >= minimumDrop &&
belowLevel - tailLevel >= minimumDrop &&
baseLevel - tailLevel >= minimumDrop &&
tailSpread <= stableTailSpread) {
if (baseLevel - tailLevel >= minimumDrop) {
final cutoff = (edgeIndex + 0.5) * hzPerRow;
return cutoff.clamp(0.0, maxFrequencyHz).toDouble();
}
@@ -523,12 +517,13 @@ double? estimateEffectiveSpectralCutoffHz({
if (belowEnd <= belowStart || aboveStart >= height) continue;
final belowLevel = _spectralMedian(smoothed, belowStart, belowEnd);
if (belowLevel < activeThreshold) continue;
final tailLevel = _spectralMedian(smoothed, aboveStart, height);
final tail = _sortedSpectralWindow(smoothed, aboveStart, height);
final tailLevel = _sortedSpectralPercentile(tail, 0.50);
if (belowLevel - tailLevel < activeMargin) continue;
final tailSpread =
_spectralPercentile(smoothed, aboveStart, height, 0.80) -
_spectralPercentile(smoothed, aboveStart, height, 0.20);
if (belowLevel - tailLevel >= activeMargin &&
tailSpread <= stableTailSpread) {
_sortedSpectralPercentile(tail, 0.80) -
_sortedSpectralPercentile(tail, 0.20);
if (tailSpread <= stableTailSpread) {
final cutoffIndex = edgeIndex - supportRows / 2;
final cutoff = (cutoffIndex + 0.5) * hzPerRow;
return cutoff.clamp(0.0, maxFrequencyHz).toDouble();
@@ -571,19 +566,20 @@ double? estimateEffectiveSpectralCutoffHz({
}
double _spectralMedian(Float64List values, int start, int end) {
return _spectralPercentile(values, start, end, 0.50);
return _sortedSpectralPercentile(
_sortedSpectralWindow(values, start, end),
0.50,
);
}
double _spectralPercentile(
Float64List values,
int start,
int end,
double percentile,
) {
Float64List _sortedSpectralWindow(Float64List values, int start, int end) {
final safeStart = start.clamp(0, values.length).toInt();
final safeEnd = end.clamp(safeStart, values.length).toInt();
if (safeEnd <= safeStart) return 0;
final sorted = values.sublist(safeStart, safeEnd)..sort();
return values.sublist(safeStart, safeEnd)..sort();
}
double _sortedSpectralPercentile(Float64List sorted, double percentile) {
if (sorted.isEmpty) return 0;
final index = ((sorted.length - 1) * percentile)
.round()
.clamp(0, sorted.length - 1)
+97
View File
@@ -222,6 +222,58 @@ lavfi.r128.true_peak=0.907
const height = 800;
const nyquist = 96000.0;
test('preserves exact cutoffs across noisy and transient spectra', () {
// Captured from the estimator before sharing sorted percentile windows.
// Cover sharp/gradual limits, full-band slopes, noise and stepped bands
// at several resolutions and Nyquist frequencies.
const expected = <int, double>{
3: 5290.322580645161,
7: 5406.25,
11: 5403.508771929824,
15: 5395.0,
31: 14952.65625,
47: 16305.0,
63: 32610.0,
79: 65220.0,
83: 5032.258064516129,
87: 5156.25,
91: 5162.907268170426,
95: 5155.0,
111: 14291.15625,
127: 15585.0,
143: 31230.0,
159: 62340.0,
175: 8000.0,
191: 22050.0,
207: 24000.0,
223: 48000.0,
239: 96000.0,
255: 8000.0,
271: 22050.0,
287: 24000.0,
303: 48000.0,
319: 96000.0,
335: 5795.0,
351: 16055.15625,
367: 17505.0,
383: 35010.0,
399: 70020.0,
};
for (final entry in expected.entries) {
final spectrum = _noisySpectrum(entry.key);
expect(
estimateEffectiveSpectralCutoffHz(
intensity: spectrum.intensity,
width: spectrum.width,
height: spectrum.height,
maxFrequencyHz: spectrum.maxFrequencyHz,
),
entry.value,
reason: 'spectrum ${entry.key}',
);
}
});
test('ignores a narrow ultrasonic pilot above a 22 kHz music band', () {
final intensity = _blankIntensity(width, height, value: 12);
_paintFrequencyBand(
@@ -585,6 +637,51 @@ lavfi.r128.true_peak=0.907
});
}
({Uint8List intensity, int width, int height, double maxFrequencyHz})
_noisySpectrum(int seed) {
final width = [17, 83, 200, 400][seed % 4];
final height = [31, 128, 399, 800][(seed ~/ 4) % 4];
final maxFrequencyHz = [
8000.0,
22050.0,
24000.0,
48000.0,
96000.0,
][(seed ~/ 16) % 5];
final intensity = Uint8List(width * height);
var random = seed + 1;
final mode = (seed ~/ 80) % 5;
for (var y = 0; y < height; y++) {
final frequency = (height - y - 1) / (height - 1);
for (var x = 0; x < width; x++) {
random = (1664525 * random + 1013904223) & 0xffffffff;
final noise = (random >> 24) % 13;
final base = switch (mode) {
0 => frequency < 0.68 ? 120 : 24,
1 => frequency < 0.65 ? (160 - frequency * 170).round() : 24,
2 => (140 - frequency * 110).round(),
3 => 16 + (random >> 20) % 170,
_ =>
frequency < 0.35
? 90
: frequency < 0.73
? 64
: 35,
};
// Five percent broadband transients remain below the temporal P90.
intensity[y * width + x] = x < width ~/ 20
? 255
: (base + noise).clamp(0, 255);
}
}
return (
intensity: intensity,
width: width,
height: height,
maxFrequencyHz: maxFrequencyHz,
);
}
Uint8List _blankIntensity(int width, int height, {int value = 0}) {
final intensity = Uint8List(width * height);
if (value > 0) intensity.fillRange(0, intensity.length, value);