Fix voice quality regression

This commit is contained in:
Janez T
2026-03-12 19:38:41 +01:00
parent baa1aa6edd
commit d2e6b692f3
14 changed files with 1023 additions and 301 deletions

View File

@@ -91,6 +91,12 @@ class AppProvider with ChangeNotifier {
bool get isVoiceCompressorEnabled => _isVoiceCompressorEnabled;
bool _isVoiceLimiterEnabled = true;
bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled;
bool _isVoiceAutoGainEnabled = false;
bool get isVoiceAutoGainEnabled => _isVoiceAutoGainEnabled;
bool _isVoiceEchoCancellationEnabled = false;
bool get isVoiceEchoCancellationEnabled => _isVoiceEchoCancellationEnabled;
bool _isVoiceNoiseSuppressionEnabled = false;
bool get isVoiceNoiseSuppressionEnabled => _isVoiceNoiseSuppressionEnabled;
double _messageFontScale = 1.0;
double get messageFontScale => _messageFontScale;
bool _autoAddDiscoveredContacts = false;
@@ -142,6 +148,8 @@ class AppProvider with ChangeNotifier {
required this.imageProvider,
}) {
_setupCallbacks();
connectionProvider.canStartAutomaticMessageSyncCallback =
_canStartAutomaticMessageSync;
_wasDeviceConnected = connectionProvider.deviceInfo.isConnected;
_initializeLocationTracking();
_loadMapEnabled();
@@ -151,6 +159,9 @@ class AppProvider with ChangeNotifier {
_loadVoiceBandPassFilterEnabled();
_loadVoiceCompressorEnabled();
_loadVoiceLimiterEnabled();
_loadVoiceAutoGainEnabled();
_loadVoiceEchoCancellationEnabled();
_loadVoiceNoiseSuppressionEnabled();
_loadMessageFontScale();
_loadAutoAddDiscoveredContacts();
_loadMessagingRouteSettings();
@@ -439,6 +450,72 @@ class AppProvider with ChangeNotifier {
}
}
Future<void> _loadVoiceAutoGainEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceAutoGainEnabled =
prefs.getBool('voice_auto_gain_enabled') ?? false;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice auto gain setting: $e');
}
}
Future<void> toggleVoiceAutoGainEnabled(bool enabled) async {
try {
_isVoiceAutoGainEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_auto_gain_enabled', enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice auto gain setting: $e');
}
}
Future<void> _loadVoiceEchoCancellationEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceEchoCancellationEnabled =
prefs.getBool('voice_echo_cancellation_enabled') ?? false;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice echo cancellation setting: $e');
}
}
Future<void> toggleVoiceEchoCancellationEnabled(bool enabled) async {
try {
_isVoiceEchoCancellationEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_echo_cancellation_enabled', enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice echo cancellation setting: $e');
}
}
Future<void> _loadVoiceNoiseSuppressionEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceNoiseSuppressionEnabled =
prefs.getBool('voice_noise_suppression_enabled') ?? false;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice noise suppression setting: $e');
}
}
Future<void> toggleVoiceNoiseSuppressionEnabled(bool enabled) async {
try {
_isVoiceNoiseSuppressionEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_noise_suppression_enabled', enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice noise suppression setting: $e');
}
}
Future<void> _loadMessageFontScale() async {
try {
final prefs = await SharedPreferences.getInstance();
@@ -1642,6 +1719,8 @@ class AppProvider with ChangeNotifier {
if (!connectionProvider.deviceInfo.isConnected) return;
try {
_isReconnectSyncInProgress = true;
_hasCompletedConnectionBootstrap = false;
// Initialize contacts provider with device public key to exclude self
// If already initialized (from early load), this will just filter out self-contact
// This must happen before getContacts to ensure proper filtering
@@ -1693,7 +1772,9 @@ class AppProvider with ChangeNotifier {
debugPrint(
'🔄 [AppProvider] Performing initial message sync (fallback for missed pushes)',
);
final initialMessageCount = await connectionProvider.syncAllMessages();
final initialMessageCount = await connectionProvider.syncAllMessages(
force: true,
);
debugPrint(
'📥 [AppProvider] Initial sync retrieved $initialMessageCount message(s)',
);
@@ -1715,19 +1796,26 @@ class AppProvider with ChangeNotifier {
_hasCompletedConnectionBootstrap = true;
_wasDeviceConnected = connectionProvider.deviceInfo.isConnected;
await _flushDeferredAutomaticMessageSync();
notifyListeners();
} catch (e) {
debugPrint('Initialization error: $e');
} finally {
_isReconnectSyncInProgress = false;
}
}
Future<void> _syncAfterReconnect() async {
if (_isReconnectSyncInProgress ||
!connectionProvider.deviceInfo.isConnected) {
Future<void> _syncAfterReconnect({bool started = false}) async {
if (!connectionProvider.deviceInfo.isConnected) {
return;
}
_isReconnectSyncInProgress = true;
if (!started) {
if (_isReconnectSyncInProgress) {
return;
}
_isReconnectSyncInProgress = true;
}
try {
debugPrint(
'🔄 [AppProvider] Device reconnected - syncing contacts and missed messages',
@@ -1738,14 +1826,19 @@ class AppProvider with ChangeNotifier {
);
await connectionProvider.getContacts();
final messageCount = await connectionProvider.syncAllMessages();
final messageCount = await connectionProvider.syncAllMessages(
force: true,
);
debugPrint(
'📥 [AppProvider] Reconnect sync retrieved $messageCount message(s)',
);
} catch (e) {
debugPrint('❌ [AppProvider] Reconnect sync error: $e');
} finally {
_hasCompletedConnectionBootstrap =
connectionProvider.deviceInfo.isConnected;
_isReconnectSyncInProgress = false;
await _flushDeferredAutomaticMessageSync();
}
}
@@ -2737,7 +2830,9 @@ class AppProvider with ChangeNotifier {
debugPrint(
'🔄 [AppProvider] Manual message sync requested (user initiated)',
);
final messageCount = await connectionProvider.syncAllMessages();
final messageCount = await connectionProvider.syncAllMessages(
force: true,
);
debugPrint(
'✅ [AppProvider] Manual sync completed: $messageCount messages',
);
@@ -2766,9 +2861,32 @@ class AppProvider with ChangeNotifier {
_stopLocationTracking();
}
if (isConnected && !wasConnected && _hasCompletedConnectionBootstrap) {
unawaited(_syncAfterReconnect());
if (!isConnected) {
connectionProvider.clearPendingAutomaticMessageSync();
}
if (isConnected && !wasConnected && _hasCompletedConnectionBootstrap) {
_isReconnectSyncInProgress = true;
unawaited(_syncAfterReconnect(started: true));
}
}
bool _canStartAutomaticMessageSync() {
return connectionProvider.deviceInfo.isConnected &&
_hasCompletedConnectionBootstrap &&
!_isReconnectSyncInProgress;
}
Future<void> _flushDeferredAutomaticMessageSync() async {
if (!connectionProvider.hasPendingAutomaticMessageSync ||
!_canStartAutomaticMessageSync()) {
return;
}
debugPrint(
'🔄 [AppProvider] Running deferred automatic message sync after bootstrap',
);
await connectionProvider.syncAllMessages(force: true);
}
/// Start location tracking

View File

@@ -186,9 +186,11 @@ class ConnectionProvider with ChangeNotifier {
Function(Uint8List publicKeyPrefix, Uint8List statusData)? onStatusResponse;
Function(Uint8List payload, int snrRaw, int rssiDbm)? onRawDataReceived;
Contact? Function(Uint8List contactPublicKey)? resolveContactForDmCallback;
bool Function()? canStartAutomaticMessageSyncCallback;
// Track pending send operations for auto-recovery
final Map<String, _PendingSendOperation> _pendingSendOperations = {};
bool _pendingAutomaticMessageSync = false;
ConnectionProvider() {
_wireServiceCallbacks(_bleService);
@@ -333,6 +335,13 @@ class ConnectionProvider with ChangeNotifier {
debugPrint('📥 [Provider] MSG_WAITING ignored during spectrum scan');
return;
}
if (!(canStartAutomaticMessageSyncCallback?.call() ?? true)) {
_pendingAutomaticMessageSync = true;
debugPrint(
'📥 [Provider] MSG_WAITING deferred until connection bootstrap completes',
);
return;
}
debugPrint('📥 [Provider] MSG_WAITING - auto-syncing');
if (_isSyncingMessages) {
_syncRequestedWhileBusy = true;
@@ -1894,11 +1903,18 @@ class ConnectionProvider with ChangeNotifier {
}
/// Sync all waiting messages from device
Future<int> syncAllMessages() async {
Future<int> syncAllMessages({bool force = false}) async {
if (_isSpectrumScanActive) {
debugPrint('⏸️ [Provider] Message sync skipped during spectrum scan');
return 0;
}
if (!force && !(canStartAutomaticMessageSyncCallback?.call() ?? true)) {
_pendingAutomaticMessageSync = true;
debugPrint(
'⏸️ [Provider] Message sync deferred until connection bootstrap completes',
);
return 0;
}
if (_isSyncingMessages) {
// Already syncing; avoid overlapping loops
_syncRequestedWhileBusy = true;
@@ -1914,6 +1930,7 @@ class ConnectionProvider with ChangeNotifier {
int totalCount = 0;
try {
_pendingAutomaticMessageSync = false;
_isSyncingMessages = true;
do {
_syncRequestedWhileBusy = false;
@@ -2005,6 +2022,12 @@ class ConnectionProvider with ChangeNotifier {
}
}
bool get hasPendingAutomaticMessageSync => _pendingAutomaticMessageSync;
void clearPendingAutomaticMessageSync() {
_pendingAutomaticMessageSync = false;
}
/// Login to a room or repeater
///
/// Sends login request with password. Results will be delivered via

View File

@@ -416,7 +416,7 @@ class ContactsProvider with ChangeNotifier {
final existingAdvertLocation = existingContact.advertLocation;
var updatedContact = incomingContact.copyWith(
isNew: existingContact.isNew,
isNew: false,
advertHistory: existingContact.advertHistory,
telemetry: mergedTelemetry,
outPathLen:

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/contact.dart';
@@ -299,7 +300,11 @@ class VoiceProvider with ChangeNotifier {
);
try {
final pcm = await _codec.decodePackets(session.packets, session.mode);
final decodedPcm = await _codec.decodePackets(
session.packets,
session.mode,
);
final pcm = _preparePlaybackPcm(decodedPcm, session.mode);
debugPrint('🎙️ [VoiceProvider] decoded ${pcm.length} PCM samples');
_playingSessionId = sessionId;
notifyListeners();
@@ -319,6 +324,28 @@ class VoiceProvider with ChangeNotifier {
notifyListeners();
}
Int16List _preparePlaybackPcm(Int16List pcm, VoicePacketMode mode) {
if (pcm.isEmpty) {
return pcm;
}
if (mode.codec != VoiceCodecKind.lpcnet) {
return pcm;
}
final output = Int16List(pcm.length);
var dc = 0.0;
const dcAlpha = 0.995;
for (var i = 0; i < pcm.length; i++) {
final sample = pcm[i].toDouble();
dc = (dcAlpha * dc) + ((1.0 - dcAlpha) * sample);
final filtered = sample - dc;
output[i] = filtered.clamp(-32768.0, 32767.0).round();
}
return output;
}
Future<void> clearStoredVoiceData() async {
_sessions.clear();
_outgoingSessions.clear();