fix(analysis): render full-track spectrograms

This commit is contained in:
zarzet
2026-07-23 18:24:14 +07:00
parent 5856729e9a
commit 0cbe51ad19
2 changed files with 632 additions and 431 deletions
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/widgets/audio_analysis_widget.dart';
void main() {
group('audio spectrogram filter', () {
test('keeps source rate and uses a full-range float pipeline', () {
final filter = buildAudioSpectrogramFilter();
expect(filter, contains('[0:a:0]aformat=sample_fmts=fltp'));
expect(filter, contains('s=1600x800'));
expect(filter, contains('scale=log'));
expect(filter, contains('fscale=lin'));
expect(filter, contains('win_func=hann'));
expect(filter, contains('drange=120'));
expect(filter, contains('limit=0'));
expect(filter, isNot(contains('pan=')));
expect(filter, isNot(contains('aresample')));
});
test('can render one source channel without app-specific branching', () {
final filter = buildAudioSpectrogramFilter(channel: 1);
expect(filter, contains('[0:a:0]pan=mono|c0=c1,'));
expect(filter, contains('aformat=sample_fmts=fltp'));
});
test('processes the complete source without resampling or downmixing', () {
final arguments = buildAudioSpectrogramArguments(
inputPath: 'source.flac',
outputPath: 'spectrum.rgba',
);
expect(arguments, containsAllInOrder(['-i', 'source.flac']));
expect(arguments, containsAllInOrder(['-frames:v', '1']));
expect(arguments, containsAllInOrder(['-pix_fmt', 'rgba']));
expect(arguments, isNot(contains('-t')));
expect(arguments, isNot(contains('-ss')));
expect(arguments, isNot(contains('-ar')));
expect(arguments, isNot(contains('-ac')));
});
});
group('broadband spectral cutoff', () {
const width = 200;
const height = 800;
const nyquist = 96000.0;
test('ignores a narrow ultrasonic pilot above a 22 kHz music band', () {
final rgba = _blankRgba(width, height);
_paintFrequencyBand(
rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
lowHz: 0,
highHz: 22000,
intensity: 220,
);
_paintFrequencyBand(
rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
lowHz: 53880,
highHz: 54120,
intensity: 255,
);
final cutoff = estimateBroadbandSpectralCutoffHz(
rgba: rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
);
expect(cutoff, isNotNull);
expect(cutoff!, inInclusiveRange(21000, 23500));
});
test('retains genuine broadband ultrasonic content', () {
final rgba = _blankRgba(width, height);
_paintFrequencyBand(
rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
lowHz: 0,
highHz: 48000,
intensity: 180,
);
final cutoff = estimateBroadbandSpectralCutoffHz(
rgba: rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
);
expect(cutoff, isNotNull);
expect(cutoff!, inInclusiveRange(47000, 49500));
});
test('does not report an isolated line as a broadband cutoff', () {
final rgba = _blankRgba(width, height);
_paintFrequencyBand(
rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
lowHz: 53880,
highHz: 54120,
intensity: 255,
);
final cutoff = estimateBroadbandSpectralCutoffHz(
rgba: rgba,
width: width,
height: height,
maxFrequencyHz: nyquist,
);
expect(cutoff, isNull);
});
test('rejects invalid or incomplete images', () {
expect(
estimateBroadbandSpectralCutoffHz(
rgba: Uint8List(3),
width: 1,
height: 1,
maxFrequencyHz: nyquist,
),
isNull,
);
});
});
}
Uint8List _blankRgba(int width, int height) {
final rgba = Uint8List(width * height * 4);
for (var offset = 3; offset < rgba.length; offset += 4) {
rgba[offset] = 255;
}
return rgba;
}
void _paintFrequencyBand(
Uint8List rgba, {
required int width,
required int height,
required double maxFrequencyHz,
required double lowHz,
required double highHz,
required int intensity,
}) {
for (var y = 0; y < height; y++) {
final frequency = (height - y - 0.5) / height * maxFrequencyHz;
if (frequency < lowHz || frequency > highHz) continue;
for (var x = 0; x < width; x++) {
final offset = (y * width + x) * 4;
rgba[offset] = intensity;
rgba[offset + 1] = intensity;
rgba[offset + 2] = intensity;
}
}
}