mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-05 18:48:38 +02:00
fix(analysis): detect full-band spectral cutoff
This commit is contained in:
@@ -5936,6 +5936,12 @@ abstract class AppLocalizations {
|
||||
/// **'Spectral Cutoff'**
|
||||
String get audioAnalysisSpectralCutoff;
|
||||
|
||||
/// Displayed when no reliable broadband spectral cutoff can be detected
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not detected'**
|
||||
String get audioAnalysisCutoffNotDetected;
|
||||
|
||||
/// Per-channel audio analysis section label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -3597,6 +3597,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spektralschnitt';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Pro Kanal Statistik';
|
||||
|
||||
|
||||
@@ -3555,6 +3555,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3555,6 +3555,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3648,6 +3648,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Limite spectrale';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Statistiques par chaîne';
|
||||
|
||||
|
||||
@@ -3552,6 +3552,9 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Tidak terdeteksi';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3544,6 +3544,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3453,6 +3453,9 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => '주파수 컷오프';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => '채널별 통계';
|
||||
|
||||
|
||||
@@ -3555,6 +3555,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3584,6 +3584,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3585,6 +3585,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -3600,6 +3600,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get audioAnalysisSpectralCutoff => 'Spectral Cutoff';
|
||||
|
||||
@override
|
||||
String get audioAnalysisCutoffNotDetected => 'Not detected';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannelStats => 'Per-channel Stats';
|
||||
|
||||
|
||||
@@ -4577,6 +4577,10 @@
|
||||
"@audioAnalysisSpectralCutoff": {
|
||||
"description": "Estimated spectral cutoff metric label"
|
||||
},
|
||||
"audioAnalysisCutoffNotDetected": "Not detected",
|
||||
"@audioAnalysisCutoffNotDetected": {
|
||||
"description": "Displayed when no reliable broadband spectral cutoff can be detected"
|
||||
},
|
||||
"audioAnalysisChannelStats": "Per-channel Stats",
|
||||
"@audioAnalysisChannelStats": {
|
||||
"description": "Per-channel audio analysis section label"
|
||||
|
||||
@@ -3432,6 +3432,7 @@
|
||||
"description": "Cache item title for local library cover art images"
|
||||
},
|
||||
"audioAnalysisSpectralCutoff": "Spectral Cutoff",
|
||||
"audioAnalysisCutoffNotDetected": "Tidak terdeteksi",
|
||||
"dialogDelete": "Hapus",
|
||||
"@dialogDelete": {
|
||||
"description": "Dialog button - delete item"
|
||||
|
||||
@@ -171,13 +171,16 @@ class _AudioInfoCard extends StatelessWidget {
|
||||
value: _formatClipping(context, data.clippingSamples),
|
||||
cs: cs,
|
||||
),
|
||||
if (data.spectralCutoffHz != null)
|
||||
_MetricChip(
|
||||
icon: Icons.filter_alt_outlined,
|
||||
label: context.l10n.audioAnalysisSpectralCutoff,
|
||||
value: _formatFrequency(data.spectralCutoffHz!),
|
||||
cs: cs,
|
||||
_MetricChip(
|
||||
icon: Icons.filter_alt_outlined,
|
||||
label: context.l10n.audioAnalysisSpectralCutoff,
|
||||
value: formatAudioAnalysisSpectralCutoff(
|
||||
data.spectralCutoffHz,
|
||||
notDetectedLabel:
|
||||
context.l10n.audioAnalysisCutoffNotDetected,
|
||||
),
|
||||
cs: cs,
|
||||
),
|
||||
_MetricChip(
|
||||
icon: Icons.numbers,
|
||||
label: context.l10n.audioAnalysisSamples,
|
||||
@@ -228,11 +231,6 @@ class _AudioInfoCard extends StatelessWidget {
|
||||
return data.channels > 0 ? '${data.channels}' : 'N/A';
|
||||
}
|
||||
|
||||
String _formatFrequency(double hz) {
|
||||
if (hz >= 1000) return '${(hz / 1000).toStringAsFixed(1)} kHz';
|
||||
return '${hz.round()} Hz';
|
||||
}
|
||||
|
||||
String _formatBitrate(int bitsPerSecond) {
|
||||
if (bitsPerSecond >= 1000000) {
|
||||
return '${(bitsPerSecond / 1000000).toStringAsFixed(2)} Mbps';
|
||||
|
||||
@@ -3,7 +3,7 @@ part of 'audio_analysis_widget.dart';
|
||||
// Analysis result models and per-run parameter records.
|
||||
|
||||
class AudioAnalysisData {
|
||||
static const cacheVersion = 7;
|
||||
static const cacheVersion = 8;
|
||||
|
||||
final String filePath;
|
||||
final int fileSize;
|
||||
|
||||
@@ -24,6 +24,19 @@ const int audioSpectrogramHeight = 800;
|
||||
const int audioSpectralAnalysisWidth = 400;
|
||||
const double audioSpectrogramDynamicRangeDb = 120;
|
||||
|
||||
String formatAudioAnalysisSpectralCutoff(
|
||||
double? cutoffHz, {
|
||||
required String notDetectedLabel,
|
||||
}) {
|
||||
if (cutoffHz == null || !cutoffHz.isFinite || cutoffHz <= 0) {
|
||||
return notDetectedLabel;
|
||||
}
|
||||
if (cutoffHz >= 1000) {
|
||||
return '${(cutoffHz / 1000).toStringAsFixed(1)} kHz';
|
||||
}
|
||||
return '${cutoffHz.round()} Hz';
|
||||
}
|
||||
|
||||
String _buildShowspectrumOptions({required int width, required String color}) {
|
||||
return 'showspectrumpic='
|
||||
's=${width}x$audioSpectrogramHeight:'
|
||||
@@ -385,8 +398,10 @@ double? estimateEffectiveSpectralCutoffHz({
|
||||
}
|
||||
|
||||
// A genuinely broadband signal with no internal falling edge reaches the
|
||||
// analysis ceiling. Report Nyquist only when both its baseband and top band
|
||||
// are populated; silence or an isolated high-frequency line returns null.
|
||||
// analysis ceiling. Natural music has a pronounced spectral tilt, so the
|
||||
// top band does not need to be almost as loud as the baseband. It must still
|
||||
// sit clearly above the measured low-level floor; silence or an isolated
|
||||
// high-frequency line therefore continues to return null.
|
||||
final basebandLevel = _spectralMedian(
|
||||
smoothed,
|
||||
(height * 0.05).floor(),
|
||||
@@ -397,7 +412,8 @@ double? estimateEffectiveSpectralCutoffHz({
|
||||
(height * 0.90).floor(),
|
||||
math.max(1, (height * 0.98).floor()),
|
||||
);
|
||||
if (basebandLevel >= 24 && topBandLevel >= basebandLevel - minimumDrop) {
|
||||
final populatedTopFloor = math.max(24.0, lowLevel * 0.60);
|
||||
if (basebandLevel >= 24 && topBandLevel >= populatedTopFloor) {
|
||||
return maxFrequencyHz;
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user