feat: improve Korean neural voice quality for v0.1.1

This commit is contained in:
oz
2026-10-14 07:15:07 +00:00
parent 1c0069c367
commit a92b2ba1e9
161 changed files with 13727 additions and 454 deletions
+84 -64
View File
@@ -1,83 +1,103 @@
# 오디오 출처와 재현
# Find-Word v0.1.1 오디오 출처와 재현
## 포함 자산
## 포함 자산과 생성 설정
| 대상 | 생성 방식 | 원문/설정 | 배포 방식 |
| --- | --- | --- | --- |
| `public/audio/intro.wav` | 로컬 eSpeak NG 한국어 포먼트 합성 | `src/content.ts`의 INTRO | PCM WAV |
| 목표 음성 100개 | 같은 로컬 엔진 | 10 패턴 × 10 단어 | PCM WAV |
| 길거리·쇼핑몰·극장·백화점·시장·지하철·카페·비 오는 거리 | 직접 작성한 결정론적 DSP | `ENVIRONMENTS`, 고정 seed | 브라우저 메모리에서 합성 |
| 화면 파형·S 캐릭터 | 직접 작성한 CSS | `App.tsx`, `styles.css` | CSS, 외부 이미지 없음 |
| `public/audio/intro.wav` | Microsoft Edge online TTS neural 음성 | `src/content.ts`의 INTRO | 24kHz mono PCM16 WAV |
| 목표 음성 100개 | 같은 provider·voice | 10 패턴 × 10 단어, `renderSentence` | 같은 WAV |
| 8개 배경 환경 | 직접 작성한 결정론적 DSP | `ENVIRONMENTS`, 고정 seed | 브라우저 메모리에서 합성 |
| 화면 파형·S 캐릭터 | 직접 작성한 CSS | `App.tsx`, `styles.css` | CSS |
모든 한국어 문장은 이번 앱을 위해 직접 작성했다. 다른 사람의 녹음, 노래,
샘플 라이브러리, 음성 복제, 신경망 모델을 포함하지 않는다. 웃음도 INTRO의
‘으하하하~~’ 문자열을 동일 엔진으로 합성한 것이다. 효과를 위해 타인 음원을
다운로드하지 않았다. 런타임에 speechSynthesis 또는 네트워크 TTS도 호출하지 않는다.
voice는 **`ko-KR-HyunsuMultilingualNeural`**, rate는 **`-6%`**, pitch는
**`-4Hz`**, 생성 클라이언트는 **edge-tts 7.3.1**이다. 직접 작성한 한국어
문장을 온라인 provider에 보내 MP3 응답을 받은 뒤 FFmpeg로 PCM을 만든다.
음성 복제나 사람의 녹음 샘플을 사용하지 않는다. 웃음은 INTRO의 ‘으하하하~~’를
같은 voice로 합성한다. 앱은 저장한 WAV만 같은 출처에서 가져오며 런타임 외부
TTS/API, speechSynthesis, 인증정보가 필요하지 않다.
WAV는 16kHz, mono, signed 16-bit little-endian PCM이다. 101개 합계는
**20,497,222바이트**다. 파일별 원문·바이트 수·SHA-256은
`public/audio/manifest.json`에 있다. 생성 시 실행 도구에서 읽은 eSpeak NG 1.51,
FFmpeg 6.1.1-3ubuntu5의 실제 버전도 기록했다. `tests/assets.test.ts`는 원문 대응, PCM
포맷, 비무음 신호, 20MiB 이내 크기, gain 적용 후 피크 여유를 검사한다.
빌드 마지막에는 `dist/audio` 파일의 해시도 확인한다.
청취 연산 변경에 맞춰 101개 WAV와 manifest를 모두 재생성했다. 정답과 경쟁
단어는 `renderSentence`로 채우며, 순서·반복·정정·조건·방향 단서가 실제 음성에 들어간다.
청취용 샘플은 [intro.wav](../public/audio/intro.wav)와
[luck-apple.wav](../public/audio/luck-apple.wav)이다. 101개 전체 생성 결과의
크기·길이 범위와 독립 검증은 [v0.1.1 RESULT](../.agent/RESULT-v0.1.1.md)에 기록한다.
`public/audio/manifest.json`은 version, provider, voice, rate, pitch, sampleRate,
생성 시각, 실측 edge-tts/FFmpeg 버전, 파일별 원문·SHA-256·bytes·duration·RMS·peak를 담는다.
## 사용 도구와 라이선스
## 설치와 생성
- [eSpeak NG 공식 저장소](https://github.com/espeak-ng/espeak-ng): GPL-3.0-or-later.
이번에는 Ubuntu 24.04 패키지 `1.51+dfsg-12build1`, `ko` 포먼트 음성을 사용했다.
[공식 언어 목록](https://github.com/espeak-ng/espeak-ng/blob/master/docs/languages.md)에 한국어가 있다.
- [FFmpeg 공식 라이선스 안내](https://ffmpeg.org/legal.html): 빌드 구성에 따라
LGPL/GPL. 생성 과정에서 PCM 리샘플링만 사용했으며 실행 파일을 배포하지 않는다.
- 합성 엔진의 프로그램 라이선스와 그 출력물은 구분한다.
[GPLv3 §2](https://www.gnu.org/licenses/gpl-3.0.html#section2) 및
[GNU 출력물 설명](https://www.gnu.org/licenses/gpl-faq.html#GPLOutput)에 따르면
출력이 그 프로그램을 바탕으로 한 저작물인 경우에만 프로그램 라이선스가
출력에 적용된다. 이 작업의 산출물은 직접 작성한 텍스트의 포먼트 합성 파형이며,
프로그램 코드·외부 녹음·모델을 포함하지 않는 자체 생성 오디오로 취급한다.
- 엔진·라이브러리·다운로드한 Debian 패키지는 `.cache/`에서만 생성 도구로
사용하며 Git 대상과 `dist/`에 들어가지 않는다. 별도 외부 음원 라이선스는 없다.
프로젝트 자체의 소스·문서·자체 생성 자산은 private / all-rights-reserved다.
[루트 LICENSE](../LICENSE)를 따른다. 별도로 번들에 포함되는 React, ReactDOM,
scheduler의 MIT 전문은 [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md)에
있으며 `dist/THIRD_PARTY_NOTICES.md`로 함께 배포한다.
## 생성 절차
일반적인 Ubuntu 개발 환경에는 `espeak-ng`, `ffmpeg`, Python 3, Node.js가
필요하다. **이미 포함된 WAV로 빌드·실행할 때는 이 도구가 필요 없다.**
생성 환경은 Linux (기존 디렉터리 교체에 `renameat2(RENAME_EXCHANGE)` 필요),
Python 3.11 이상, FFmpeg, Node.js 22.22.2 이상이다. **이미 포함된 WAV를
빌드·실행할 때는 Python/FFmpeg/provider 연결이 필요 없다.** 생성기 계약 테스트는
Python과 FFmpeg를 사용하되 네트워크 없이 동작한다.
```bash
python3 -m venv .venv-audio
.venv-audio/bin/python -m pip install -r requirements-audio.txt
.venv-audio/bin/python -m pip check
npm run audio:generate
npm test -- tests/assets.test.ts
npm test
npm run build
```
현재 작업에서는 시스템 설치 없이 다음 명령으로 worktree 내부에 도구를 풀었다.
이 명령은 문서화용이며 서버에서 실행한 이력이 없다.
`audio:generate`는 `.venv-audio/bin/python scripts/generate_audio.py`로 고정되어
전역 Python으로 대체되지 않는다. `requirements-audio.txt`는 edge-tts==7.3.1과
전이 의존성 버전을 고정한다. 생성기는 실제 설치 버전을 조회하고 7.3.1이 아니면
외부 요청 전에 실패한다. `.venv-audio/`는 Git과 배포물에서 제외한다.
```bash
mkdir -p .cache/espeak
cd .cache/espeak
apt download espeak-ng espeak-ng-data libespeak-ng1 libpcaudio0 libsonic0
for pkg in *.deb; do dpkg-deb -x "$pkg" root; done
cd ../..
npm run audio:generate
```
원문을 Node type stripping으로 앱 데이터에서 읽고, 101개 모두 같은 파일시스템의
`public/.audio-stage-*/`에 순차 생성한다. 각 provider 요청은 최대 60초, 최대
3회 시도이며 실패 사이 1초/2초를 기다린다. FFmpeg도 60초 제한이 있다.
재시도 전 불완전 MP3를 삭제하고 변환·검증 실패는 즉시 중단한다. 로그에는
파일명·시도 횟수만 남기고 provider stderr/응답·쿠키·인증 헤더를 저장하지 않는다.
`scripts/generate_audio.py`가 시스템 엔진 또는 `.cache/espeak/root/usr`를
자동 선택한다. `ko`, 속도 145, 피치 45, 진폭 100을 고정하고 FFmpeg로 16kHz
PCM을 만든 뒤 Python wave 모듈로 메타데이터 없는 WAV를 쓴다. 런타임 데이터는
Node의 type stripping으로 읽어 텍스트 중복을 피한다. 같은 엔진·버전·플랫폼에서는
해시로 재현 여부를 비교할 수 있다. 다른 버전은 새 출처와 해시를 검토해야 한다.
전체 WAV와 manifest를 검증한 뒤 기존 `public/audio`와 완성 디렉터리를 단일
원자 교환으로 바꾼다. 실패·Ctrl-C는 기존 디렉터리의 바이트를 그대로 보존한다.
원자 교환을 지원하지 않는 파일시스템에서는 실패하며 비원자 방식으로 우회하지 않는다.
성공 후 이전 자산과 임시 MP3를 정리한다. 강제 종료(SIGKILL)·전원 손실 때는
숨김 staging 폴더가 남을 수 있으나 공개 디렉터리는 교환 전 또는 후의 전체 집합이다.
이는 전원 손실 시 저장장치 내구성 보장을 뜻하지 않는다.
## 음량·품질 한계
고정 클라이언트와 설정으로 **생성 절차**를 재현할 수 있다. 원격 모델·서비스 버전은
고정할 수 없어 재생성 WAV의 바이트 동일성은 보장하지 않는다. 새 실행의 manifest를
검토해야 한다. 빌드는 이미 생성한 WAV의 해시를 보존하고 101개 모두 포함됐는지 확인한다.
보이스와 배경은 같은 디지털 RMS로 정규화되고 음성 경로만 dB gain을 적용한다.
안내의 gain은 1.8에서 0.12로 감소한다. 기본 전체 음량은 35%이다.
물리적 음압을 측정하거나 장치 출력을 보정하지 않는다.
## 레벨 처리와 검증
포먼트 합성 특성상 한국어 발음·억양과 웃음이 기계적으로 들린다. 배경도 실제
장소 녹음처럼 세밀하지 않다. 객관식의 자모 차이와 단어 친숙도, 발음의 자연스러움은
검수자가 직접 들어 평가해야 한다. 현재 단계 수치는 임상 검증이나 청력 판정용이 아니다.
MP3를 24kHz mono signed 16-bit little-endian PCM으로 디코드한다. 파일 전체에
동일한 선형 gain을 적용해 RMS 0.10을 목표로 하며 증폭은 최대 2배, peak는
0.89로 제한한다. 동적 압축·노이즈 게이트·침묵 제거·시간 변형은 하지 않는다.
원래 신호의 자음과 억양을 보존하면서 파일 간 레벨 차이와 피크를 완화한다.
생성기와 독립 자산 테스트는 101개 파일의 원문·집합·SHA-256·bytes·duration,
24kHz mono PCM16, 1–20초 길이, RMS > 100/32768, full-scale 미도달,
총 48MiB 미만을 검사한다. 런타임 RMS 0.03과 최대 +6dB에서도 peak < 0.8을
요구한다. 기존 앱은 Web Audio에서 배경·음성을 RMS 정규화한 뒤 상대 SNR을 적용한다.
물리적 음압이나 주파수별 청감을 보정한 값은 아니다.
## 도구 라이선스와 서비스 조건
- [edge-tts 7.3.1 LICENSE](https://github.com/rany2/edge-tts/blob/7.3.1/LICENSE):
대부분의 코드에 LGPLv3, `srt_composer.py`에만 MIT가 적용된다.
[공식 프로젝트](https://github.com/rany2/edge-tts)는 Microsoft Edge의 온라인
TTS를 이용하는 클라이언트다. 클라이언트 라이선스가 provider 사용·음성 출력의
재배포 권한을 대신 부여하지 않는다. 이 프로젝트는 클라이언트/모델을 번들하지 않는다.
- [FFmpeg 공식 라이선스](https://ffmpeg.org/legal.html): 빌드 옵션에 따라 LGPL/GPL이
적용된다. 실제 사용 버전은 manifest에 남기며 실행 파일은 배포하지 않는다.
- [Microsoft Services Agreement](https://www.microsoft.com/en-us/servicesagreement)의
AI Services 등 해당 서비스 조건은 도구의 오픈소스 라이선스와 별개다.
위 문서 §13(s)(vi)는 출력의 권리·사용 가능성을 이용자가 판단하도록 설명한다.
API 키 없이 호출된다는 사실이 상업 사용·재배포의 포괄적 허락이나 SLA를 뜻하지 않는다.
배포자는 자신의 이용 상황에 적용되는 최신 조건을 확인해야 한다.
- 프로젝트는 생성 음성 자체에 **독점 권리를 주장하지 않는다**. [LICENSE](../LICENSE)의
소스·문서 권리 유보와 provider 및 제3자의 권리는 구분한다. 런타임 React,
ReactDOM, scheduler의 MIT 전문은 [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md)에 있다.
문서 확인일: 2026-10-14 UTC. provider의 음성 목록, rate/pitch 지원, 가용성,
접근 제한, 조건은 바뀔 수 있다. 온라인 생성이 중단되어도 포함된 WAV의 빌드·재생에는
영향이 없다. 실패 시 다른 voice로 자동 대체하지 않는다.
## 청감과 평가의 한계
자동 검증은 PCM 신호·무결성·재생 흐름을 확인한다. 한국어 발음, 유사 자모의
구분, 웃음의 자연스러움과 실제 사용자의 난이도는 청취 평가가 필요하다.
대표 샘플과 게임 내 소음 혼합 상태를 직접 들어 인수 판단한다. 배경은 장소를
추상화한 합성 신호다. 이 게임의 수치는 임상 검증·치료·청력 판정용이 아니다.