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