feat(voice): dynamic silence threshold
The previous -40 dBFS static threshold sat right on top of typical phone mic ambient, so silence detection rarely fired and the user always had to tap stop manually. Silence threshold is now dynamic: track the session peak dBFS and treat "silent" as 15 dB below peak. Auto-calibrates per mic and environment rather than assuming a fixed ambient level. - Grace period (1500 ms) at start so the user has time to begin speaking before checks arm. - Static -35 dB fallback until the peak clears -20 dB so a dead- silent session doesn't spin forever. - Silence duration bumped 1500 → 2000 ms for breathing room. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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