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:
EJClaw
2026-08-22 23:10:52 +09:00
parent 6e20f2cd79
commit 80a83d9944
2 changed files with 28 additions and 22 deletions

View File

@@ -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.

View File

@@ -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()