NEW코딩일지 · 11.05코퍼스 메인에 주장 연구실을 세웠다: 사유고2 주장 29만 개를 자동 등록하고 스파크 두 대가 반증 조건까지 검토한다
← 학습
·4분

로컬 TTS 뽑기 · 3화

나레이션 이음새는 무음 대신 방 소리로 메웠다: 경계 튐 0.087→0.005

2분 원고를 한 번에 읽히면 뒤로 갈수록 작아지고, 나눠 만들어 붙이면 뚝뚝 끊겼다. 문단 사이를 디지털 무음 대신 녹음에서 떠낸 방 소리로 채우고 경계에 15ms 크로스페이드를 걸어 이음새를 지웠다.

▸ 유튜브 자동화#fish-speech#tts#audio#room-tone

2화에서 고른 LoRA로 실제 영상용 2분 나레이션을 만들었다. 원고는 GPU 담보금융을 다룬 14문단, 25문장이다. 짧은 시험 문장에서는 잘 되던 것이 2분짜리에서는 두 번 무너졌다. 한 번은 음량이, 한 번은 이음새가 문제였다.

한 번에 넣으면 작아진다

2분 원고를 통째로 한 번에 넣으면 기본 모델도 뒤로 갈수록 작아졌다. 마지막 장면 음량이 첫 장면의 55% 수준까지 내려갔다. 긴 원고를 한 요청으로 보내면 Fish가 안에서 알아서 조각내 이어 읽는데, 이 방식도 시험해 봤다. 이음새는 자연스러웠지만 10초 구간 음량이 -16.4dB에서 -24.4dB까지 떨어졌다. 게다가 문단 경계 13곳 중 9곳은 쉼 없이 바로 다음 문단으로 넘어갔다. Fish에는 쉼 길이를 지정하는 태그가 없어서 이걸 대본으로 조절할 수도 없었다.

그래서 잘게 나눠 만들어 이어 붙이기로 했다. 요청을 매번 새로 시작하면 음량이 쌓여서 빠지는 일은 없다.

문장 단위: 음량은 지켰지만 뚝뚝

처음에는 25문장을 하나씩 따로 만들고 사이에 0.12초 무음을 넣었다. 마지막 장면 음량이 첫 장면의 100.45%로, 음량은 끝까지 유지됐다. 화자 유사도도 평균 0.813으로 목소리가 흔들리지 않았다. 자동 검사는 전부 통과였다.

들어 보니 뚝뚝 끊겼다. 문장마다 새로 말을 시작하는 느낌이었다. 문장과 문장 사이의 자연스러운 연결이 사라진 것이다.

문단 단위: 그래도 경계에서 끊긴다

다음에는 14개 문단 단위로 만들었다. 문단 안에서는 모델이 알아서 쉬며 읽게 두고, 무음은 문단 사이에만 넣었다. 가장 작은 장면도 첫 장면의 91.7%로 음량이 버텼고, 음성 인식으로 되돌린 원고 일치도도 0.993이었다. 문단 안은 자연스러웠다. 그런데 문단 경계에서 여전히 끊겼다.

문단을 따로 만든 것 자체는 원인이 아니었다. 이웃한 문단끼리의 음량 차이는 최대 1.9dB로 귀에 띌 정도가 아니었다.

원인: 진공 같은 무음

원인은 이음새를 만드는 방식에 있었다. 이 진단은 클로드 코드가 했다. 무손실 원본 파형에서 경계 앞뒤를 재서 Fish가 스스로 만든 쉼과 우리가 끼워 넣은 쉼을 비교했다.

Fish가 문단 안에서 만든 쉼 문단 사이에 넣은 쉼
바닥 소리 -55dB 안팎의 방 소리 -120dB, 완전한 디지털 무음
말끝이 잦아드는 시간 55~399ms 0~8ms
경계에서 튀는 정도 0.003~0.004 최대 0.0866

여기서 튀는 정도는 경계 앞뒤 3ms 안에서 파형이 한 샘플 사이에 가장 크게 바뀐 값이다. 녹음된 음성에는 말이 없을 때도 아주 작은 방 소리가 깔려 있다. 그 사이에 갑자기 진공 같은 무음이 끼고 말끝이 칼로 자른 듯 끊기니, 경계마다 딸깍 소리와 함께 소리가 꺼지는 것처럼 들렸다.

처방: 방 소리와 15ms

고친 방법은 이렇다.

  • 문단 사이를 무음 대신 룸톤(방 소리)으로 채운다. 룸톤은 문단 안에서 -55dB 아래로 가장 오래 이어진 쉼을 찾아 그 가운데 절반을 떠낸다.
  • 길이가 모자라면 떠낸 조각을 정방향과 역방향으로 번갈아 이어 붙인다. 같은 방향으로 반복하면 이어 붙인 자리마다 작은 불연속이 생기기 때문이다.
  • 음성과 룸톤이 만나는 모든 경계에 15ms 등파워 크로스페이드를 건다. 나가는 쪽은 cos², 들어오는 쪽은 sin²로 줄이고 키워 합한 크기가 일정하게 유지된다.
  • 문단 간격은 650ms로 두고, 마지막 문단 뒤에도 650ms 꼬리를 붙인다. 파일이 말끝에서 바로 끝나면 마지막 음절의 여운이 잘린다.
  • 발화 구간은 한 샘플도 건드리지 않는다. 음량 보정, 압축, 속도 조절은 하지 않고, 손대는 것은 갭과 경계 15ms뿐이다.

문단마다 떠낸 룸톤을 재 보니 서로 2.4dB쯤 달랐다. 경계마다 바닥 소리가 바뀌면 그것도 들릴 수 있어서, 한 곳에서 떠낸 룸톤으로 전부 통일했다.

이 처방은 먼저 데모로 만들어 들어 보고 승인한 뒤, 작업 지시서로 정리해 코덱스가 파이프라인에 넣었다. 지시서에는 이음새를 재는 검사도 새로 넣었다. 경계에서 튀는 정도 0.008 이하, 갭 바닥 소리 -50~-65dB(완전한 무음이 나오면 실패), 문단 간격 650±50ms, 말끝이 12ms 이상에 걸쳐 잦아들 것.

결과

같은 14개 문단 음성을 그대로 다시 조립했다. 문단 경계 13곳과 꼬리 1곳이 모두 새 검사를 통과했다. 경계에서 튀는 정도는 최대 0.0866에서 0.0049로 줄었고, 갭 바닥 소리는 -55dB로 Fish가 만든 쉼과 같은 수준이 됐다.

대신 길이가 늘었다. 650ms 간격을 넣자 2분 원고가 125.5초가 됐다. 예전처럼 간격을 줄여 2분에 억지로 맞추지 않았다. 120초를 넘으면 실패로 보고하고, 원고를 줄일지 영상을 늘릴지는 사람이 정한다. 이번에는 영상을 125.5초로 늘려 다시 렌더했다.

배운 것

이어 붙인 음성의 자연스러움은 무음이 아니라 그 사이를 채우는 방 소리에서 나왔다. 사람 귀는 소리가 났다가 사라지는 것보다, 배경이 갑자기 진공이 되는 것을 더 예민하게 잡았다.

뚝뚝 끊긴다는 말은 처음에는 숫자로 잡히지 않았다. 기존 검사는 음량과 화자와 원고 일치만 봤고 전부 통과였다. 귀에 들린 현상을 경계 튀는 정도, 바닥 소리, 말끝이 잦아드는 시간이라는 숫자로 옮기고 나서야 원인을 확정하고 검사로 막을 수 있었다.

그리고 시간을 맞추려고 음성을 깎지 않는다. 길이가 넘치면 넘쳤다고 보고하는 것이, 아무도 모르게 쉼을 줄여 다시 뚝뚝 끊기게 만드는 것보다 낫다.

로컬 TTS 뽑기 · 전 5화

  1. 1화첫 TTS LoRA는 긴 문단에서 음량이 5%까지 무너졌다: 단문만 배운 탓
  2. 2화로컬 TTS LoRA는 25스텝을 골랐다: 검증 손실이 좋을수록 장문 목소리가 작아졌다
  3. 3화나레이션 이음새는 무음 대신 방 소리로 메웠다: 경계 튐 0.087→0.005
  4. 4화Drama3는 LoRA 없이 18초 레퍼런스로 채널 목소리를 냈다: 걸린 것은 문단 속 긴 침묵이었다
  5. 5화Drama3는 맥스튜디오 4비트에서 가장 빨랐다: 음성 1초에 0.51초, 스파크 0.85초, 5090 3.36초