fix(tts): remove monster-voice artefact — disable librosa pitch shift, base speed 1.3→1.0
The emotional TTS path applied a librosa post-hoc pitch_shift (±1–3 semitones) on top of a 1.3x-fast Melo base, producing a robotic "monster" delivery. Zero out the pitch column for every emotion so pitch_shift is never invoked (the worker's _pitch_shift already no-ops on 0.0), and drop the default synthesis speed to 1.0. Emotion is now conveyed by speed alone — natural, artefact-free. The pitch column is retained so a proper pitch method can be re-enabled later. Verified: 29 tests pass; real 2-emotion synthesis on CUDA yields a clean wav with pitch=0.0 on all segments. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -23,27 +23,33 @@ from dataclasses import dataclass
|
||||
|
||||
# Canonical emotion -> (speed multiplier relative to base, pitch shift in semitones).
|
||||
# Kept deliberately modest so delivery stays natural, not cartoonish.
|
||||
#
|
||||
# Pitch is held at 0.0 for every emotion: librosa's post-hoc pitch_shift on Melo
|
||||
# output produced a robotic, "monster"-sounding artefact (worse when stacked on a
|
||||
# fast base speed). Emotion is therefore conveyed by speed only — natural and
|
||||
# artefact-free. The pitch column is retained (rather than removed) so the effect
|
||||
# can be re-enabled per-emotion later with a real, artefact-free pitch method.
|
||||
EMOTION_PARAMS: dict[str, tuple[float, float]] = {
|
||||
"happy": (1.08, 2.0), # 기쁨 / cheerful
|
||||
"excited": (1.15, 3.0), # 신남 / excited
|
||||
"hopeful": (1.10, 1.5), # 희망 / 힘차게
|
||||
"sad": (0.90, -2.5), # 슬픔 / sad
|
||||
"angry": (1.12, 1.0), # 화남 / angry
|
||||
"fearful": (1.12, 2.0), # 두려움 / terrified
|
||||
"surprised": (1.05, 3.0), # 놀람 / surprise
|
||||
"disgust": (0.96, -1.0), # 혐오 / disgust
|
||||
"calm": (0.95, -1.0), # 차분 / calm
|
||||
"friendly": (1.00, 1.0), # 다정 / friendly
|
||||
"serious": (0.97, -1.0), # 진지 / serious
|
||||
"disappointed":(0.92, -2.0), # 실망 / disappointed
|
||||
"tired": (0.90, -2.0), # 피곤 / 지침
|
||||
"affectionate":(0.98, 1.0), # 사랑스럽게 / affectionate
|
||||
"playful": (1.08, 2.0), # 장난스럽게 / playful
|
||||
"whisper": (0.92, -1.5), # 속삭임 / whispering
|
||||
"shout": (1.05, 2.5), # 외침 / shouting
|
||||
"determined": (1.05, 0.5), # 단호 / determined
|
||||
"relieved": (0.95, 0.5), # 안도 / relieved
|
||||
"curious": (1.03, 1.5), # 궁금 / curious
|
||||
"happy": (1.08, 0.0), # 기쁨 / cheerful
|
||||
"excited": (1.15, 0.0), # 신남 / excited
|
||||
"hopeful": (1.10, 0.0), # 희망 / 힘차게
|
||||
"sad": (0.90, 0.0), # 슬픔 / sad
|
||||
"angry": (1.12, 0.0), # 화남 / angry
|
||||
"fearful": (1.12, 0.0), # 두려움 / terrified
|
||||
"surprised": (1.05, 0.0), # 놀람 / surprise
|
||||
"disgust": (0.96, 0.0), # 혐오 / disgust
|
||||
"calm": (0.95, 0.0), # 차분 / calm
|
||||
"friendly": (1.00, 0.0), # 다정 / friendly
|
||||
"serious": (0.97, 0.0), # 진지 / serious
|
||||
"disappointed":(0.92, 0.0), # 실망 / disappointed
|
||||
"tired": (0.90, 0.0), # 피곤 / 지침
|
||||
"affectionate":(0.98, 0.0), # 사랑스럽게 / affectionate
|
||||
"playful": (1.08, 0.0), # 장난스럽게 / playful
|
||||
"whisper": (0.92, 0.0), # 속삭임 / whispering
|
||||
"shout": (1.05, 0.0), # 외침 / shouting
|
||||
"determined": (1.05, 0.0), # 단호 / determined
|
||||
"relieved": (0.95, 0.0), # 안도 / relieved
|
||||
"curious": (1.03, 0.0), # 궁금 / curious
|
||||
}
|
||||
|
||||
# Every spelling Claude might realistically emit, mapped to a canonical emotion.
|
||||
|
||||
@@ -12,7 +12,7 @@ Env:
|
||||
WSAI_MELO_DEVICE cpu | cuda | auto (default auto: GPU if torch sees one,
|
||||
else CPU; the worker falls back to CPU if CUDA fails)
|
||||
WSAI_TTS_OUT_DIR where wavs are written (default ~/.cache/wsai/tts)
|
||||
WSAI_TTS_SPEED synthesis speed multiplier (default 1.3)
|
||||
WSAI_TTS_SPEED synthesis speed multiplier (default 1.0)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -93,7 +93,7 @@ class MeloTTS:
|
||||
self.out_dir = Path(out_dir or os.environ.get("WSAI_TTS_OUT_DIR")
|
||||
or (Path.home() / ".cache/wsai/tts"))
|
||||
self.speed = float(speed if speed is not None
|
||||
else os.environ.get("WSAI_TTS_SPEED", "1.3"))
|
||||
else os.environ.get("WSAI_TTS_SPEED", "1.0"))
|
||||
self.sink = sink or _log_sink
|
||||
self._proc: asyncio.subprocess.Process | None = None
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
Reference in New Issue
Block a user