[结束提示音优化]:完成Codex通知音资产内置,包含音频提取、配置默认值和回归测试

This commit is contained in:
mkbk
2026-06-18 14:10:34 +08:00
parent 80549fb312
commit d83f5a19cb
14 changed files with 102 additions and 8 deletions
+2 -1
View File
@@ -27,7 +27,7 @@ from .llm import MockLlmProvider, OpenAICompatibleLlmProvider
from .pipeline import PipelineResult, VoicePipeline
from .runtime import LiveVoiceRuntime, RuntimeSummary, TerminalRuntimeReporter, TurnResult, build_live_runtime
from .simulation import run_simulated_live
from .tts import CloudTtsProvider, MacSayTtsProvider, SentenceBuffer, SineTtsProvider, make_prompt_chime, sanitize_tts_text
from .tts import CloudTtsProvider, MacSayTtsProvider, SentenceBuffer, SineTtsProvider, make_end_chime, make_prompt_chime, sanitize_tts_text
from .assets import validate_pet_assets
from .ui import ConsolePetWindow, PetStateController, PetVisualState
@@ -70,6 +70,7 @@ __all__ = [
"MacSayTtsProvider",
"SentenceBuffer",
"SineTtsProvider",
"make_end_chime",
"make_prompt_chime",
"sanitize_tts_text",
"validate_pet_assets",
+3 -2
View File
@@ -47,7 +47,7 @@ from .protocols import (
WakeWordProvider,
)
from .stt import is_valid_transcript_text
from .tts import SentenceBuffer, make_prompt_chime, sanitize_tts_text
from .tts import SentenceBuffer, make_end_chime, sanitize_tts_text
from .vad import VadRecorder
@@ -491,7 +491,8 @@ class TurnController:
def _play_end_chime(self) -> None:
if not self.config.end_chime_enabled:
return
segment = make_prompt_chime(
segment = make_end_chime(
file_path=self.config.end_chime_file,
frequency_hz=self.config.end_chime_frequency_hz,
duration_ms=self.config.end_chime_duration_ms,
sample_rate=self.config.sample_rate,
+1
View File
@@ -105,6 +105,7 @@ def main(argv: list[str] | None = None) -> int:
"barge_in_min_speech_ms": config.barge_in_min_speech_ms,
"barge_in_echo_guard_ms": config.barge_in_echo_guard_ms,
"end_chime_enabled": config.end_chime_enabled,
"end_chime_file": str(config.end_chime_file),
"end_chime_frequency_hz": config.end_chime_frequency_hz,
"end_chime_duration_ms": config.end_chime_duration_ms,
},
+5
View File
@@ -60,6 +60,7 @@ class AppConfig:
barge_in_min_speech_ms: int = 250
barge_in_echo_guard_ms: int = 500
end_chime_enabled: bool = True
end_chime_file: Path = Path("assets/sounds/codex-notification.wav")
end_chime_frequency_hz: int = 880
end_chime_duration_ms: int = 140
@@ -134,6 +135,10 @@ class AppConfig:
barge_in_min_speech_ms=int(get("BARGE_IN_MIN_SPEECH_MS", "250") or "250"),
barge_in_echo_guard_ms=int(get("BARGE_IN_ECHO_GUARD_MS", "500") or "500"),
end_chime_enabled=(get("END_CHIME_ENABLED", "1") or "1").lower() not in {"0", "false", "no"},
end_chime_file=Path(
get("END_CHIME_FILE", "assets/sounds/codex-notification.wav")
or "assets/sounds/codex-notification.wav"
),
end_chime_frequency_hz=int(get("END_CHIME_FREQUENCY_HZ", "880") or "880"),
end_chime_duration_ms=int(get("END_CHIME_DURATION_MS", "140") or "140"),
)
+3 -2
View File
@@ -33,7 +33,7 @@ from .models import AudioSegment, ErrorCode, PipelineState, ProviderError
from .protocols import AudioTransport, LlmProvider, RealtimeSttProvider, SttProvider, TtsProvider, WakeWordProvider
from .stt import CloudAsrSttProvider, SherpaOnnxSttProvider, is_valid_transcript_text
from .transport import SoundDeviceAudioTransport
from .tts import CloudTtsProvider, MacSayTtsProvider, SentenceBuffer, make_prompt_chime, sanitize_tts_text
from .tts import CloudTtsProvider, MacSayTtsProvider, SentenceBuffer, make_end_chime, sanitize_tts_text
from .vad import EnergyVadProvider, HybridVadProvider, PrimarySpeakerVadRecorder, SherpaOnnxVadProvider, VadRecorder
from .wakeword import SherpaOnnxKeywordWakeWordProvider
@@ -343,7 +343,8 @@ class LiveVoiceRuntime:
def _play_end_chime(self) -> None:
if not self.config.end_chime_enabled:
return
segment = make_prompt_chime(
segment = make_end_chime(
file_path=self.config.end_chime_file,
frequency_hz=self.config.end_chime_frequency_hz,
duration_ms=self.config.end_chime_duration_ms,
sample_rate=self.config.sample_rate,
+49 -1
View File
@@ -170,7 +170,55 @@ def make_prompt_chime(
channels,
0,
duration_ms,
{"chime": "end", "text": "end_chime"},
{"chime": "end", "text": "end_chime", "source": "synthetic"},
)
def make_end_chime(
*,
file_path: str | Path | None = None,
frequency_hz: int = 880,
duration_ms: int = 140,
sample_rate: int = 16000,
channels: int = 1,
) -> AudioSegment:
if file_path is not None:
segment = _load_chime_file(Path(file_path))
if segment is not None:
return segment
return make_prompt_chime(
frequency_hz=frequency_hz,
duration_ms=duration_ms,
sample_rate=sample_rate,
channels=channels,
)
def _load_chime_file(path: Path) -> AudioSegment | None:
if not path.is_file():
return None
data = path.read_bytes()
if not data:
return None
suffix = path.suffix.lower().lstrip(".") or "wav"
sample_rate = 16000
channels = 1
duration_ms = 120
if suffix == "wav":
try:
with wave.open(str(path), "rb") as handle:
sample_rate = handle.getframerate()
channels = handle.getnchannels()
duration_ms = int(handle.getnframes() / max(1, sample_rate) * 1000)
except wave.Error:
pass
return AudioSegment(
data,
sample_rate,
channels,
0,
max(20, duration_ms),
{"chime": "end", "text": "end_chime", "format": suffix, "path": str(path), "source": "file"},
)