feat: improve Korean neural voice quality for v0.1.1
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# 설계
|
||||
# Find-Word v0.1.1 설계
|
||||
|
||||
## 데이터와 출제
|
||||
|
||||
@@ -67,6 +67,11 @@ Promise를 무시한다. 질문과 보기는 재생 완료 후 DOM에 생성되
|
||||
|
||||
## 오디오
|
||||
|
||||
101개 저장 WAV는 edge-tts 7.3.1의 `ko-KR-HyunsuMultilingualNeural`로
|
||||
빌드 전에 생성한다. 24kHz mono PCM16이며 원문·도구·해시는 manifest에 남긴다.
|
||||
생성기는 임시 디렉터리에서 전체 검증 후 원자 교체한다. 생성 절차는
|
||||
[AUDIO_PROVENANCE](AUDIO_PROVENANCE.md)에 있으며 앱은 provider에 연결하지 않는다.
|
||||
|
||||
`src/audio/dsp.ts`는 순수 수치 함수이며 장치가 없어도 테스트할 수 있다.
|
||||
정수 seed 잡음에 저역 통과 필터, 사인파, 진폭 변조를 조합하고 양 끝을 페이드한다.
|
||||
8개 프리셋은 실제 현장 녹음이 아니라 환경을 추상적으로 표현한 합성 신호다.
|
||||
|
||||
+84
-64
@@ -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 신호·무결성·재생 흐름을 확인한다. 한국어 발음, 유사 자모의
|
||||
구분, 웃음의 자연스러움과 실제 사용자의 난이도는 청취 평가가 필요하다.
|
||||
대표 샘플과 게임 내 소음 혼합 상태를 직접 들어 인수 판단한다. 배경은 장소를
|
||||
추상화한 합성 신호다. 이 게임의 수치는 임상 검증·치료·청력 판정용이 아니다.
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
# 정적 배포 인계
|
||||
# Find-Word v0.1.1 정적 배포 인계
|
||||
|
||||
이 문서는 **Neo가 검토 후 수행할 절차**다. Codex는 서버, DNS, 인증정보에
|
||||
접근하지 않았고 배포·서비스 생성·Git commit/push도 하지 않았다.
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
Node.js 최소 **22.22.2** (`>=22.22.2`)의 지원 LTS 환경에서 `npm ci`, `npm run lint`, `npm test`,
|
||||
`npm run build`를 실행한다. `dist/` 전체를 하나의 릴리스로 전달한다.
|
||||
`dist/audio/`의 WAV 101개와 manifest가 반드시 포함되어야 한다. 런타임은
|
||||
`dist/audio/`의 24kHz neural WAV 101개와 version 0.1.1 manifest가 반드시 포함되어야 한다. 런타임은
|
||||
Nginx 정적 파일만 필요하며 Node 서버·TTS 엔진·DB·환경변수·API 키는 필요 없다.
|
||||
잠긴 jsdom 계열 의존성 때문에 24.x는 24.15.0 이상, 26.x는 26.0.0 이상이어야 한다.
|
||||
브라우저 검증의 Chromium sandbox는 기본 활성화다. sandbox를 지원하지 않는
|
||||
@@ -15,6 +15,8 @@ Nginx 정적 파일만 필요하며 Node 서버·TTS 엔진·DB·환경변수·A
|
||||
|
||||
Canonical repository는 [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word)다.
|
||||
프로젝트 자체는 private / all-rights-reserved이며 [LICENSE](../LICENSE)를 따른다.
|
||||
생성 음성은 프로젝트의 독점 권리 주장 대상이 아니며 provider 조건은
|
||||
[AUDIO_PROVENANCE](AUDIO_PROVENANCE.md)에 기록한다.
|
||||
React, ReactDOM, scheduler의 MIT 전문을 담은 `dist/THIRD_PARTY_NOTICES.md`도
|
||||
반드시 배포한다. `build` 검증기는 고지 파일이 없거나 축약·변조되면 실패한다.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user