fix(voice): stop dashboard from mangling the leading [감정] tag
The dashboard voice-turn path ran _speech_text() before MeloTTS.synth, which rewrote a leading "[힘차게] 안녕!" into "힘차게, 안녕!" — reading the first emotion aloud and destroying the tag before the TTS emotion parser could use it. Make _speech_text() a pass-through so every emotion tag (including the first) reaches synth intact and shapes pitch/speed instead of being spoken. Adds a regression test covering the leading-tag case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from wsai.backends.emotion import (
|
||||
match_emotion,
|
||||
parse_segments,
|
||||
)
|
||||
from wsai.dashboard import _speech_text
|
||||
|
||||
BASE = 1.3
|
||||
|
||||
@@ -74,3 +75,16 @@ def test_persona_examples_are_all_recognised():
|
||||
# read aloud instead of shaping the voice.
|
||||
for word in ["힘차게", "궁금", "반가움", "차분하게", "웃으며", "속상함"]:
|
||||
assert match_emotion(word) is not None, word
|
||||
|
||||
|
||||
def test_voice_turn_keeps_leading_emotion_tag_for_tts_parser():
|
||||
text = "[속상함] 정말 힘들었겠다. [힘차게] 하지만 넌 할 수 있어!"
|
||||
|
||||
spoken = _speech_text(text)
|
||||
segs = parse_segments(spoken, BASE)
|
||||
|
||||
assert spoken == text
|
||||
assert segs[0].text == "정말 힘들었겠다."
|
||||
assert segs[0].pitch == EMOTION_PARAMS["sad"][1]
|
||||
assert segs[1].text == "하지만 넌 할 수 있어!"
|
||||
assert segs[1].pitch == EMOTION_PARAMS["hopeful"][1]
|
||||
|
||||
@@ -18,7 +18,6 @@ from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
import queue
|
||||
import re
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
@@ -26,20 +25,14 @@ from .monitor import Monitor
|
||||
|
||||
log = logging.getLogger("wsai.dashboard")
|
||||
|
||||
_EMOTION_RE = re.compile(r"^\s*\[([^\]]+)\]\s*(.*)", re.S)
|
||||
|
||||
|
||||
def _speech_text(reply: str) -> str:
|
||||
"""Turn a reply like ``[힘차게] 안녕!`` into what the TTS should actually say.
|
||||
"""Return reply text exactly as authored for the TTS backend.
|
||||
|
||||
The bracketed emotion is spoken too (per user request), so the leading tag
|
||||
becomes a natural spoken word: ``[힘차게] 안녕!`` -> ``힘차게, 안녕!``. Replies
|
||||
without a tag are spoken as-is."""
|
||||
m = _EMOTION_RE.match(reply or "")
|
||||
if not m:
|
||||
The TTS backend itself understands bracketed emotion tags: known emotion
|
||||
tags steer delivery and are not spoken; non-emotion brackets are spoken.
|
||||
Do not rewrite a leading tag here, or the first emotion would be read aloud
|
||||
and lost before ``MeloTTS.synth`` can parse it."""
|
||||
return reply
|
||||
emotion, rest = m.group(1).strip(), m.group(2).strip()
|
||||
return f"{emotion}, {rest}" if rest else emotion
|
||||
|
||||
|
||||
def _make_handler(dash: "Dashboard"):
|
||||
|
||||
Reference in New Issue
Block a user