fix: 글자 속도를 생성 단계 length_scale(음절 발화 속도)로 변경
- 사후 배속(atempo) 대신 model.tts_to_file(speed=) 로 각 음절(안/녕/하)의 발음 속도 자체를 조절. 피치 보존, TSM 아티팩트 없음 - _synth_segment/_synth_natural 에 speed 인자 전달, synth_wav 의 사후 타임스트레치 제거. 단어·문장 간격은 여전히 발화 속도와 독립
This commit is contained in:
@@ -199,14 +199,15 @@ class MeloEngine(BaseEngine):
|
||||
return list(spk2id.values())[0]
|
||||
|
||||
def _synth_segment(
|
||||
self, text: str, lang: str, speaker: str | None
|
||||
self, text: str, lang: str, speaker: str | None, speed: float = 1.0
|
||||
) -> tuple[np.ndarray, int]:
|
||||
# 항상 자연 속도(1.0)로 합성 — 속도는 사후 타임스트레치로 처리해 발음 유지
|
||||
# 글자(음절) 발화 속도는 모델 length_scale(=1/speed)로 생성 단계에서 조절.
|
||||
# 각 음절의 발음 자체가 빨라/느려지며(피치 보존), 사후 배속이 아니다.
|
||||
model = self._get_model(lang)
|
||||
speaker_id = self._resolve_speaker(model, lang, speaker)
|
||||
with self._global_lock:
|
||||
audio = model.tts_to_file(
|
||||
text, speaker_id, output_path=None, speed=1.0, quiet=True
|
||||
text, speaker_id, output_path=None, speed=speed, quiet=True
|
||||
)
|
||||
sr = model.hps.data.sampling_rate
|
||||
return np.asarray(audio, dtype=np.float32), sr
|
||||
@@ -273,11 +274,12 @@ class MeloEngine(BaseEngine):
|
||||
return np.asarray(out, dtype=np.float32)
|
||||
|
||||
def _synth_natural(
|
||||
self, text: str, language: str, speaker: str | None
|
||||
self, text: str, language: str, speaker: str | None, speed: float = 1.0
|
||||
) -> tuple[np.ndarray, int]:
|
||||
"""자연 속도(1.0)로 텍스트 한 조각을 합성. AUTO 모드는 언어별로 나눠 합성.
|
||||
"""텍스트 한 조각을 합성. AUTO 모드는 언어별로 나눠 합성.
|
||||
|
||||
속도/문장·단어 간격 조절은 이 결과 위에서 처리한다(여기서는 하지 않음).
|
||||
speed 는 모델 length_scale 로 각 음절의 발화 속도를 조절한다(생성 단계).
|
||||
문장·단어 간격은 이 결과 위에서 삽입한다(여기서는 하지 않음).
|
||||
"""
|
||||
if language == "AUTO":
|
||||
# 영어 구간엔 사용자가 고른 억양, 한국어 구간엔 KR 음성
|
||||
@@ -291,7 +293,7 @@ class MeloEngine(BaseEngine):
|
||||
gap = None
|
||||
for seg_lang, seg_text in parts:
|
||||
seg_spk = spk_by_lang.get(seg_lang, en_speaker)
|
||||
audio, seg_sr = self._synth_segment(seg_text, seg_lang, seg_spk)
|
||||
audio, seg_sr = self._synth_segment(seg_text, seg_lang, seg_spk, speed)
|
||||
if sr is None:
|
||||
sr = seg_sr
|
||||
gap = np.zeros(int(sr * 0.06), dtype=np.float32)
|
||||
@@ -304,7 +306,7 @@ class MeloEngine(BaseEngine):
|
||||
audios.append(audio)
|
||||
audio = np.concatenate(audios) if audios else np.zeros(1, dtype=np.float32)
|
||||
return audio, (sr or self._get_model(DEFAULT_LANG).hps.data.sampling_rate)
|
||||
return self._synth_segment(text, language, speaker)
|
||||
return self._synth_segment(text, language, speaker, speed)
|
||||
|
||||
# ---- 합성 -----------------------------------------------------
|
||||
def synth_wav(
|
||||
@@ -318,10 +320,11 @@ class MeloEngine(BaseEngine):
|
||||
sentence_gap: float | None = None,
|
||||
) -> bytes:
|
||||
"""세 가지를 독립적으로 조절:
|
||||
- speed: 글자(음소) 발화 속도 — 자연 합성 후 타임스트레치(피치 보존)
|
||||
- speed: 글자(음절) 발화 속도 — 모델 length_scale 로 각 음절의 발음
|
||||
속도를 생성 단계에서 조절(피치 보존, 사후 배속 아님)
|
||||
- word_gap: 단어 사이 추가 무음(초). >0 이면 어절 단위로 나눠 합성
|
||||
- sentence_gap: 문장 사이 무음(초)
|
||||
간격은 타임스트레치의 영향을 받지 않아 글자속도와 독립적으로 유지된다.
|
||||
간격은 발화 속도와 무관하게 지정한 초만큼 유지되어 서로 독립적이다.
|
||||
"""
|
||||
text = (text or "").strip()
|
||||
if not text:
|
||||
@@ -338,11 +341,6 @@ class MeloEngine(BaseEngine):
|
||||
if language in ("AUTO", "KR"):
|
||||
text = normalize_korean(text)
|
||||
|
||||
def stretch(a: np.ndarray, sr: int) -> np.ndarray:
|
||||
if abs(speed - 1.0) > 1e-3:
|
||||
return self._time_stretch(a, sr, speed)
|
||||
return a
|
||||
|
||||
sentences = split_sentences(text) or [text]
|
||||
sr: int | None = None
|
||||
pieces: list[np.ndarray] = []
|
||||
@@ -353,10 +351,9 @@ class MeloEngine(BaseEngine):
|
||||
words = sent.split()
|
||||
wpieces: list[np.ndarray] = []
|
||||
for wi, word in enumerate(words):
|
||||
a, s = self._synth_natural(word, language, speaker)
|
||||
a, s = self._synth_natural(word, language, speaker, speed)
|
||||
if sr is None:
|
||||
sr = s
|
||||
a = stretch(a, sr)
|
||||
if wi > 0:
|
||||
wpieces.append(np.zeros(int(sr * word_gap), dtype=np.float32))
|
||||
wpieces.append(a)
|
||||
@@ -366,10 +363,10 @@ class MeloEngine(BaseEngine):
|
||||
else np.zeros(1, dtype=np.float32)
|
||||
)
|
||||
else:
|
||||
a, s = self._synth_natural(sent, language, speaker)
|
||||
a, s = self._synth_natural(sent, language, speaker, speed)
|
||||
if sr is None:
|
||||
sr = s
|
||||
sent_audio = stretch(a, sr)
|
||||
sent_audio = a
|
||||
|
||||
if pieces and sentence_gap > 1e-4:
|
||||
pieces.append(np.zeros(int(sr * sentence_gap), dtype=np.float32))
|
||||
|
||||
Reference in New Issue
Block a user