[结束提示音优化]:完成Codex通知音资产内置,包含音频提取、配置默认值和回归测试
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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"),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"},
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user