feat: Stille-Toleranz fuer Aufnahme einstellbar in App-Settings
Neuer +/- Block in SettingsScreen → Spracheingabe → "Stille-Toleranz", 1.0-8.0s, Default 2.8s. Wert in AsyncStorage (aria_vad_silence_sec). audio.ts liest den Wert beim Aufnahme-Start und nutzt ihn fuer den VAD-Auto-Stop-Schwellwert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a4379eb64
commit
0a04972455
|
|
@ -27,6 +27,10 @@ import {
|
||||||
TTS_PREROLL_MIN_SEC,
|
TTS_PREROLL_MIN_SEC,
|
||||||
TTS_PREROLL_MAX_SEC,
|
TTS_PREROLL_MAX_SEC,
|
||||||
TTS_PREROLL_STORAGE_KEY,
|
TTS_PREROLL_STORAGE_KEY,
|
||||||
|
VAD_SILENCE_DEFAULT_SEC,
|
||||||
|
VAD_SILENCE_MIN_SEC,
|
||||||
|
VAD_SILENCE_MAX_SEC,
|
||||||
|
VAD_SILENCE_STORAGE_KEY,
|
||||||
} from '../services/audio';
|
} from '../services/audio';
|
||||||
import ModeSelector from '../components/ModeSelector';
|
import ModeSelector from '../components/ModeSelector';
|
||||||
import QRScanner from '../components/QRScanner';
|
import QRScanner from '../components/QRScanner';
|
||||||
|
|
@ -82,6 +86,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
const [storageSize, setStorageSize] = useState('...');
|
const [storageSize, setStorageSize] = useState('...');
|
||||||
const [ttsEnabled, setTtsEnabled] = useState(true);
|
const [ttsEnabled, setTtsEnabled] = useState(true);
|
||||||
const [ttsPrerollSec, setTtsPrerollSec] = useState<number>(TTS_PREROLL_DEFAULT_SEC);
|
const [ttsPrerollSec, setTtsPrerollSec] = useState<number>(TTS_PREROLL_DEFAULT_SEC);
|
||||||
|
const [vadSilenceSec, setVadSilenceSec] = useState<number>(VAD_SILENCE_DEFAULT_SEC);
|
||||||
const [editingPath, setEditingPath] = useState(false);
|
const [editingPath, setEditingPath] = useState(false);
|
||||||
const [xttsVoice, setXttsVoice] = useState('');
|
const [xttsVoice, setXttsVoice] = useState('');
|
||||||
const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
|
const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
|
||||||
|
|
@ -117,6 +122,14 @@ const SettingsScreen: React.FC = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
AsyncStorage.getItem(VAD_SILENCE_STORAGE_KEY).then(saved => {
|
||||||
|
if (saved != null) {
|
||||||
|
const n = parseFloat(saved);
|
||||||
|
if (isFinite(n) && n >= VAD_SILENCE_MIN_SEC && n <= VAD_SILENCE_MAX_SEC) {
|
||||||
|
setVadSilenceSec(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
||||||
if (saved) setXttsVoice(saved);
|
if (saved) setXttsVoice(saved);
|
||||||
});
|
});
|
||||||
|
|
@ -555,6 +568,43 @@ const SettingsScreen: React.FC = () => {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* === Spracheingabe (geraetelokal) === */}
|
||||||
|
<Text style={styles.sectionTitle}>Spracheingabe</Text>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.toggleLabel}>Stille-Toleranz</Text>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
Wie lange du eine Sprechpause machen darfst, bevor die Aufnahme
|
||||||
|
automatisch beendet und gesendet wird. Hoeher = mehr Zeit zum
|
||||||
|
Nachdenken; niedriger = schnelleres Senden.
|
||||||
|
Default: {VAD_SILENCE_DEFAULT_SEC.toFixed(1)}s.
|
||||||
|
</Text>
|
||||||
|
<View style={styles.prerollRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.prerollButton}
|
||||||
|
onPress={() => {
|
||||||
|
const next = Math.max(VAD_SILENCE_MIN_SEC, Math.round((vadSilenceSec - 0.5) * 10) / 10);
|
||||||
|
setVadSilenceSec(next);
|
||||||
|
AsyncStorage.setItem(VAD_SILENCE_STORAGE_KEY, String(next));
|
||||||
|
}}
|
||||||
|
disabled={vadSilenceSec <= VAD_SILENCE_MIN_SEC}
|
||||||
|
>
|
||||||
|
<Text style={styles.prerollButtonText}>−0.5</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.prerollValue}>{vadSilenceSec.toFixed(1)} s</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.prerollButton}
|
||||||
|
onPress={() => {
|
||||||
|
const next = Math.min(VAD_SILENCE_MAX_SEC, Math.round((vadSilenceSec + 0.5) * 10) / 10);
|
||||||
|
setVadSilenceSec(next);
|
||||||
|
AsyncStorage.setItem(VAD_SILENCE_STORAGE_KEY, String(next));
|
||||||
|
}}
|
||||||
|
disabled={vadSilenceSec >= VAD_SILENCE_MAX_SEC}
|
||||||
|
>
|
||||||
|
<Text style={styles.prerollButtonText}>+0.5</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* === Sprachausgabe (geraetelokal) === */}
|
{/* === Sprachausgabe (geraetelokal) === */}
|
||||||
<Text style={styles.sectionTitle}>Sprachausgabe</Text>
|
<Text style={styles.sectionTitle}>Sprachausgabe</Text>
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,29 @@ const AUDIO_ENCODING = 'audio/wav';
|
||||||
|
|
||||||
// VAD (Voice Activity Detection) — Stille-Erkennung
|
// VAD (Voice Activity Detection) — Stille-Erkennung
|
||||||
const VAD_SILENCE_THRESHOLD_DB = -45; // dB unter dem als "Stille" gilt
|
const VAD_SILENCE_THRESHOLD_DB = -45; // dB unter dem als "Stille" gilt
|
||||||
const VAD_SILENCE_DURATION_MS = 2800; // ms Stille bevor Auto-Stop — laenger = mehr Toleranz fuer Sprechpausen
|
|
||||||
const VAD_SPEECH_THRESHOLD_DB = -28; // dB ueber dem als "Sprache" gilt (Sprach-Gate) — hoeher = weniger Umgebungsgeraeusche
|
const VAD_SPEECH_THRESHOLD_DB = -28; // dB ueber dem als "Sprache" gilt (Sprach-Gate) — hoeher = weniger Umgebungsgeraeusche
|
||||||
const VAD_SPEECH_MIN_MS = 500; // ms Sprache bevor Aufnahme zaehlt — laenger = keine Huestler/Klopfer mehr
|
const VAD_SPEECH_MIN_MS = 500; // ms Sprache bevor Aufnahme zaehlt — laenger = keine Huestler/Klopfer mehr
|
||||||
|
|
||||||
|
// VAD-Stille (in Sekunden) — wie lange Sprechpause toleriert wird, bevor
|
||||||
|
// die Aufnahme automatisch beendet wird. Einstellbar in den App-Settings.
|
||||||
|
export const VAD_SILENCE_DEFAULT_SEC = 2.8;
|
||||||
|
export const VAD_SILENCE_MIN_SEC = 1.0;
|
||||||
|
export const VAD_SILENCE_MAX_SEC = 8.0;
|
||||||
|
export const VAD_SILENCE_STORAGE_KEY = 'aria_vad_silence_sec';
|
||||||
|
|
||||||
|
async function loadVadSilenceMs(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const raw = await AsyncStorage.getItem(VAD_SILENCE_STORAGE_KEY);
|
||||||
|
if (raw != null) {
|
||||||
|
const n = parseFloat(raw);
|
||||||
|
if (isFinite(n) && n >= VAD_SILENCE_MIN_SEC && n <= VAD_SILENCE_MAX_SEC) {
|
||||||
|
return Math.round(n * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return Math.round(VAD_SILENCE_DEFAULT_SEC * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
// Max-Dauer einer Aufnahme (Notbremse gegen Runaway-Loops). Auf 2 Minuten
|
// Max-Dauer einer Aufnahme (Notbremse gegen Runaway-Loops). Auf 2 Minuten
|
||||||
// hochgezogen damit auch laengere Erklaerungen durchgehen.
|
// hochgezogen damit auch laengere Erklaerungen durchgehen.
|
||||||
const MAX_RECORDING_MS = 120000;
|
const MAX_RECORDING_MS = 120000;
|
||||||
|
|
@ -238,12 +257,14 @@ class AudioService {
|
||||||
// Andere Apps waehrend der Aufnahme pausieren (Musik, Videos etc.)
|
// Andere Apps waehrend der Aufnahme pausieren (Musik, Videos etc.)
|
||||||
AudioFocus?.requestExclusive().catch(() => {});
|
AudioFocus?.requestExclusive().catch(() => {});
|
||||||
|
|
||||||
// VAD aktivieren
|
// VAD aktivieren — Stille-Dauer aus AsyncStorage (Settings-konfigurierbar)
|
||||||
this.vadEnabled = autoStop;
|
this.vadEnabled = autoStop;
|
||||||
if (autoStop) {
|
if (autoStop) {
|
||||||
|
const vadSilenceMs = await loadVadSilenceMs();
|
||||||
|
console.log('[Audio] VAD-Stille:', vadSilenceMs, 'ms');
|
||||||
this.vadTimer = setInterval(() => {
|
this.vadTimer = setInterval(() => {
|
||||||
const silenceDuration = Date.now() - this.lastSpeechTime;
|
const silenceDuration = Date.now() - this.lastSpeechTime;
|
||||||
if (silenceDuration >= VAD_SILENCE_DURATION_MS) {
|
if (silenceDuration >= vadSilenceMs) {
|
||||||
console.log(`[Audio] VAD: ${silenceDuration}ms Stille — Auto-Stop`);
|
console.log(`[Audio] VAD: ${silenceDuration}ms Stille — Auto-Stop`);
|
||||||
this.silenceListeners.forEach(cb => cb());
|
this.silenceListeners.forEach(cb => cb());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue