Merge pull request 'Release v26.04.15.1 — Dynamic voice silence threshold' (#24) from dev into main
This commit was merged in pull request #24.
This commit is contained in:
@@ -99,10 +99,23 @@ class VoiceNotifier extends Notifier<VoiceState> {
|
|||||||
StreamSubscription<Amplitude>? _amplitudeSubscription;
|
StreamSubscription<Amplitude>? _amplitudeSubscription;
|
||||||
|
|
||||||
// Recording / silence detection
|
// Recording / silence detection
|
||||||
|
//
|
||||||
|
// Silence threshold is dynamic: we track the session peak dBFS and treat
|
||||||
|
// "silent" as "current level is at least _dropFromPeakDb below peak."
|
||||||
|
// This auto-calibrates to whatever mic + room the user is on rather than
|
||||||
|
// assuming a fixed ambient level. Until the peak climbs above
|
||||||
|
// _dynamicArmDb we fall back to a conservative static threshold so a
|
||||||
|
// quiet room doesn't spin forever. A grace period at the start of the
|
||||||
|
// recording gives the user time to begin speaking before silence checks
|
||||||
|
// arm.
|
||||||
int _recordingStartMs = 0;
|
int _recordingStartMs = 0;
|
||||||
int _silenceMs = 0;
|
int _silenceMs = 0;
|
||||||
static const _silenceThresholdDb = -40.0;
|
double _peakDb = -100.0;
|
||||||
static const _silenceDurationMs = 1500;
|
static const _fallbackThresholdDb = -35.0;
|
||||||
|
static const _dropFromPeakDb = 15.0;
|
||||||
|
static const _dynamicArmDb = -20.0;
|
||||||
|
static const _graceMs = 1500;
|
||||||
|
static const _silenceDurationMs = 2000;
|
||||||
static const _minRecordingMs = 300;
|
static const _minRecordingMs = 300;
|
||||||
|
|
||||||
// Voice mode callbacks
|
// Voice mode callbacks
|
||||||
@@ -236,6 +249,7 @@ class VoiceNotifier extends Notifier<VoiceState> {
|
|||||||
if (!state.voiceModeActive) return;
|
if (!state.voiceModeActive) return;
|
||||||
|
|
||||||
_silenceMs = 0;
|
_silenceMs = 0;
|
||||||
|
_peakDb = -100.0;
|
||||||
_recordingStartMs = DateTime.now().millisecondsSinceEpoch;
|
_recordingStartMs = DateTime.now().millisecondsSinceEpoch;
|
||||||
state = state.copyWith(mode: VoiceMode.recording);
|
state = state.copyWith(mode: VoiceMode.recording);
|
||||||
|
|
||||||
@@ -285,13 +299,22 @@ class VoiceNotifier extends Notifier<VoiceState> {
|
|||||||
if ((norm - state.amplitude).abs() > 0.02) {
|
if ((norm - state.amplitude).abs() > 0.02) {
|
||||||
state = state.copyWith(amplitude: norm);
|
state = state.copyWith(amplitude: norm);
|
||||||
}
|
}
|
||||||
|
if (db > _peakDb) _peakDb = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
final elapsed =
|
final elapsed =
|
||||||
DateTime.now().millisecondsSinceEpoch - _recordingStartMs;
|
DateTime.now().millisecondsSinceEpoch - _recordingStartMs;
|
||||||
|
// Grace period at the start — let the user begin speaking before we
|
||||||
|
// start silence-counting. Also suppresses any false triggers while
|
||||||
|
// the native recorder is still warming up.
|
||||||
|
if (elapsed < _graceMs) return;
|
||||||
if (elapsed < _minRecordingMs) return;
|
if (elapsed < _minRecordingMs) return;
|
||||||
|
|
||||||
final isSilent = !validDb || db < _silenceThresholdDb;
|
// Dynamic threshold once we've seen real speech; static fallback
|
||||||
|
// before that so a dead-silent session doesn't spin forever.
|
||||||
|
final threshold =
|
||||||
|
_peakDb > _dynamicArmDb ? _peakDb - _dropFromPeakDb : _fallbackThresholdDb;
|
||||||
|
final isSilent = !validDb || db < threshold;
|
||||||
|
|
||||||
if (isSilent) {
|
if (isSilent) {
|
||||||
_silenceMs += 200;
|
_silenceMs += 200;
|
||||||
|
|||||||
Reference in New Issue
Block a user