piper-plus / README.md
watchpen's picture
README: ๋น„์ƒ์—…์šฉ ์ „์šฉ(์ƒ์—…์  ์‚ฌ์šฉ ์‹œ ๋ณ„๋„ ํ—ˆ๊ฐ€ ํ•„์ˆ˜) ๋ฐ ์ €์ž‘์ž ํ‘œ์‹œ ์˜๋ฌด ์กฐํ•ญ ์—…๋ฐ์ดํŠธ
bb1e7ab verified
|
Raw History Blame Contribute Delete
47.1 kB
---
language:
- ko
license: other
license_name: piper-plus
pipeline_tag: text-to-speech
library_name: onnx
tags:
- text-to-speech
- tts
- piper
- vits
- onnx
- korean
- neural-resonance
- hunminjeongeum
---
# Piper Plus โ€” ๋œ์†Œ๋ฆฌ ๊ณผ์ž‰ ์ผ๋ฐ˜ํ™”๋ฅผ ์–ต์ œํ•˜๊ณ  ์‹ ๊ฒฝ๊ณต๋ช…์œผ๋กœ ๊ฐ์ •์„ ํ‘œํ˜„ํ•˜๋Š” ํ•œ๊ธ€ ์ž๋ชจ ์ŒํŒŒ ๋ฐœ์„ฑ ๋ชจ๋ธ
### *Piper Plus: Korean Neural-Resonance TTS with Fortis-Overgeneralization Suppression, Emotion-Expressive Neural Resonance & Acoustic Jamo Vocalization*
> ๐Ÿ“ข **[2026-10-24 ๋ชจ๋ธ ๊ฐ€์ค‘์น˜ ์ „๋ฉด ๊ต์ฒด ์•ˆ๋‚ด]**
> ์–ด์ ˆ ๊ฒฝ๊ณ„๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ๋œ์†Œ๋ฆฌ ๊ณผ์ž‰ ์ผ๋ฐ˜ํ™”๋ฅผ ์–ต์ œํ•œ ์ตœ์‹  **Epoch 880 EMA (`piper-plus.onnx`)** ๋ชจ๋ธ๋กœ ๊ต์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์— ๋‹ค์šด๋กœ๋“œํ•˜์‹  ๋ถ„๋“ค์€ ์ตœ์‹  ๋ชจ๋ธ๋กœ ๋‹ค์‹œ ๋ฐ›์•„์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
>
> ๐Ÿ“ข **[2026-10-24 Model Weights Updated]**
> Replaced with the latest **Epoch 880 EMA (`piper-plus.onnx`)** model (word-boundary `eojeol_boundary` CumSum completely removed & excessive fortis/tensification generalization structurally suppressed). If you downloaded an earlier version, please re-download `piper-plus.onnx`.
>
> โš ๏ธ **[๋น„์ƒ์—…์šฉ ์‚ฌ์šฉ ์ œํ•œ ๋ฐ ์ €์ž‘์ž ํ‘œ์‹œ ์˜๋ฌด (Non-Commercial Use Only & Mandatory Attribution)]**
> - **๋น„์ƒ์—…์šฉ ์ „์šฉ (์ƒ์—…์  ์‚ฌ์šฉ ์‹œ ๋ณ„๋„ ํ—ˆ๊ฐ€ ํ•„์ˆ˜)**: ๋ณธ ๋ชจ๋ธ(`piper-plus`)๊ณผ ํฌํ•จ๋œ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ํ‘œยท์ฝ”๋“œยท๊ฐ€์ค‘์น˜๋Š” **๋น„์ƒ์—…์šฉ(๊ฐœ์ธ ํ•™์Šตยทํ•™์ˆ  ์—ฐ๊ตฌยท๋น„์˜๋ฆฌ ๋ชฉ์ )์œผ๋กœ๋งŒ ๋ฌด๋ฃŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅ**ํ•˜๋ฉฐ, **์ƒ์—…์  ์‚ฌ์šฉ(์œ ๋ฃŒ ์•ฑยท์›น ์„œ๋น„์Šค ํƒ‘์žฌ, ์˜๋ฆฌ ๋ชฉ์  ์ฝ˜ํ…์ธ  ์ œ์ž‘ยท๋ฐฐํฌ, ๊ธฐ์—…์šฉ ํ™œ์šฉ ๋“ฑ)์€ ๋ฐ˜๋“œ์‹œ ์›์ €์ž‘์ž(`watchpen`)์˜ ๋ณ„๋„ ์‚ฌ์ „ ํ—ˆ๊ฐ€**๋ฅผ ๋ฐ›์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
> - **์ €์ž‘์ž ํ‘œ์‹œ ๋ฐ ๊นƒํ—ˆ๋ธŒ ์ฃผ์†Œ ์ฒจ๋ถ€ ์˜๋ฌด**: ๋ณธ ๋ชจ๋ธ(`piper-plus`), ์‹ ๊ฒฝ๊ณต๋ช…(๋ฐœ์Œยท์„ฑ์šฐยท๊ฐ์ • ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ํ‘œ), ์ž๋ชจ ์ŒํŒŒ ๋ฐœ์„ฑ ๊ตฌ์กฐ, ์ œ๋กœ๋“€๋ ˆ์ด์…˜ ํƒœ๊ทธ ์ฒด๊ณ„, ๋˜๋Š” ํฌํ•จ ์ฝ”๋“œ/๊ฐ€์ค‘์น˜๋ฅผ **์‘์šฉํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ยทํŒŒ์ƒยท์žฌ๊ฐœ๋ฐœํ•  ๊ฒฝ์šฐ, ๋ฐ˜๋“œ์‹œ ์›์ €์ž‘์ž ์ด๋‹ˆ์…œ(`watchpen`)๊ณผ ๊ณต์‹ ๊นƒํ—ˆ๋ธŒ ์ฃผ์†Œ(https://github.com/wacthpen/piper-plus)๋ฅผ ํ•จ๊ป˜ ์ฒจ๋ถ€(๋ช…์‹œ)**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
>
> * **Non-Commercial Use Only (Separate Permission Required for Commercial Use)**: This model (`piper-plus`), its neural resonance tables, weights, and codebase are provided **strictly for non-commercial use (personal, academic, and non-profit research)**. **Any commercial use requires prior separate permission from the original author (`watchpen`).**
> * **Mandatory Attribution**: If you adapt, modify, fine-tune, or redevelop this model, its neural resonance tables, or its codebase, you MUST explicitly credit and include the original author's handle/initials (`watchpen` / GitHub: `wacthpen`, Hugging Face: `watchpen`) AND attach the official GitHub repository URL (`https://github.com/wacthpen/piper-plus`) in your documentation, model card, and derivative works.
---
## ๐ŸŒŸ Piper Plus ํ•ต์‹ฌ ์ •์ฒด์„ฑ (Core Identities)
1. **๋œ์†Œ๋ฆฌ(๊ฒฝ์Œํ™”) ์ผ๋ฐ˜ํ™” ๊ณผ์ž‰ ์–ต์ œ *(Suppression of Excessive Fortis / Tensification Generalization)***
- ๊ธฐ์กด ํ•œ๊ตญ์–ด TTS์™€ ๋ถˆ์™„์ „ํ•œ ์™ธ๋ถ€ G2P๊ฐ€ ๋งŒ์—ฐํ•˜๊ฒŒ ์ผ์œผํ‚ค๋˜ ๊ณ ์งˆ๋ณ‘์ธ **'์˜ˆ์‚ฌ์†Œ๋ฆฌ์˜ ๊ณผ๋„ํ•œ ๋œ์†Œ๋ฆฌ(๊ฒฝ์Œํ™”) ์ผ๋ฐ˜ํ™”'**๋ฅผ ์›์ฒœ ์–ต์ œํ•œ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค.
- ์‹ ๊ฒฝ๋ง์˜ ๋ฌธ๋งฅยท์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต์ธต(Memory Layer)์„ ๊ทธ๋Œ€๋กœ ํ†ต๊ณผ์‹œํ‚ค๋ฉด ํ•™์Šต ๋ฐ์ดํ„ฐ ์† ๊ฒฝ์Œํ™” ํ˜„์ƒ์ด ๊ณผ์ž‰ ์ผ๋ฐ˜ํ™”๋˜์–ด ๋ฉ€์ฉกํ•œ ์˜ˆ์‚ฌ์†Œ๋ฆฌ(ํ‰์Œ `ใ„ฑ, ใ„ท, ใ…‚, ใ……, ใ…ˆ`)๊นŒ์ง€ ๋œ์†Œ๋ฆฌ(`ใ„ฒ, ใ„ธ, ใ…ƒ, ใ…†, ใ…‰`)๋กœ ์˜ค์—ผ๋ฉ๋‹ˆ๋‹ค. **Piper Plus๋Š” ์ถœ๋ ฅยทํ…์ŠคํŠธ ์ธ์ฝ”๋”์—์„œ '๋œ์†Œ๋ฆฌํ™”๋œ ๊ธฐ์–ต์ธต(`self.emb`)'์„ ํ†ต๊ณผํ•˜์ง€ ์•Š๋„๋ก(100% ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ๋Œ€์ฒด ๋ชจ๋“œ๋กœ ์šฐํšŒ) ์„ค๊ณ„**ํ•˜๊ณ  **์˜ˆ์‚ฌ์†Œ๋ฆฌ 24๋ฐฐ ๋ณดํ˜ธ ์„ธ๊ธฐํŒ๋ณ„๊ธฐ**๋ฅผ ๊ฒฐํ•ฉํ•ด ์‹ ๊ฒฝ๋ง์ด ์ž„์˜๋กœ ๋œ์†Œ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š” ํ˜„์ƒ์„ ๊ตฌ์กฐ์ ์œผ๋กœ ์™„์ „ํžˆ ํ”ผํ•ด๊ฐ”์œผ๋ฉฐ, ์‹ค์ œ๋กœ ์†Œ๋ฆฌ๊ฐ€ ๋ฐ”๋€Œ์–ด์•ผ ํ•˜๋Š” **๋ถˆ๊ทœ์น™ ๋ฐœ์Œ์€ ๋ฐœ์Œ์‚ฌ์ „(Dictionary)์œผ๋กœ 1:1 ์ •๋ฐ€ ๋Œ€์‘**ํ•ฉ๋‹ˆ๋‹ค.
2. **๊ฐ์ • ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•œ ์‹ ๊ฒฝ๊ณต๋ช… & ๋ฌธ๋งฅ ๊ฐ์ • ๋ชจ๋ธ ์ž๋™ ๋งคํ•‘ *(Emotion-Expressive Neural Resonance & Context-Aware Auto Emotion Mapping)***
- ๋‹จ์ˆœํ•œ ํ‰๋ฉด์  ํ…์ŠคํŠธ ๋‚ญ๋…์„ ๋„˜์–ด, 32๋Œ€ ๊ฐ์ •ยท๋งํˆฌ ์ฒด๊ณ„์™€ **5๋Œ€ ๋ฐœ์„ฑ ๋ฌผ๋ฆฌ ์ถ•(๊ณต๋ช… ๊ท ํ˜•, ์„ฑ๋ฌธ ๊ธด์žฅ๋„, ํ˜ธํก๊ธฐ์‹, ํ”ผ์น˜ยทํฌ๋จผํŠธ ์ด๋™, ๋ฐœํ™” ์†๋„)**์ด ๊ฒฐํ•ฉ๋œ **'์‹ ๊ฒฝ๊ณต๋ช…(Neural Resonance)'** ์•„ํ‚คํ…์ฒ˜๋กœ ์‚ด์•„ ์ˆจ ์‰ฌ๋Š” ๊ฐ์ • ์—ฐ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
- ํŠนํžˆ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ์ • ํ‘œํ˜„์„ ์œ ๋„ํ•˜๊ธฐ ์œ„ํ•ด **์ „์šฉ ํ•œ๊ตญ์–ด ๋ฌธ๋งฅ ๊ฐ์ • ๋ถ„๋ฅ˜ ๋ชจ๋ธ(`emotion/emotion_int8.onnx`)์„ ์ง์ ‘ ํ•™์Šต**ํ•˜์—ฌ ํƒ‘์žฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ผ์ผ์ด ๊ฐ์ • ํƒœ๊ทธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ ์ง€ ์•Š์•„๋„, ๊ฐ์ • ๋ชจ๋ธ์ด **๋ฌธ์žฅ์˜ ์•ž๋’ค ๋ฌธ๋งฅ(Context)์„ ์ฝ๊ณ  ์ž๋™์œผ๋กœ ๊ฐ์ • ํƒœ๊ทธยท๋งํˆฌยท๊ฐ์ • ์„ธ๊ธฐยท๋ฐœํ™” ์†๋„ ๋ฐ ๊ฐ์ • ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜(`res`)๋ฅผ ๋งคํ•‘**ํ•ฉ๋‹ˆ๋‹ค.
- ๋˜ํ•œ ๋Œ€๋ณธ์— ์‚ฝ์ž…๋œ ๊ฐ์ •ยท๋ฐœ์„ฑยท์†Œ๋ฆฌ ํƒœ๊ทธ๋Š” **`_ํƒœ๊ทธ์นธ๋–ผ๊ธฐ` (Zero-Duration Scatter Compaction)**๋ฅผ ํ†ตํ•ด **ํ”„๋ ˆ์ž„ ์ˆ˜ 0(์ œ๋กœ๋“€๋ ˆ์ด์…˜, Zero-Duration)**์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด, **ํƒœ๊ทธ ์ž์ฒด๊ฐ€ ๊ธ€์ž๋‚˜ ๊ตฐ์†Œ๋ฆฌ๋กœ ๋ฐœ์Œ๋˜๋Š” ์ผ ์—†์ด ์˜ค์ง ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๊ฐ์ •๊ณผ ๋ฐœ์„ฑ ํšจ๊ณผ๋งŒ** ๋ณธ๋ฌธ ์Œ์„ฑ์— ์ž…ํ˜€์ง‘๋‹ˆ๋‹ค.
3. **์ž๋ชจ ํ•™์Šต์„ ํ†ตํ•œ ์ŒํŒŒ ๊ธ€์ž ๋ฐœ์„ฑ *(Acoustic Character Vocalization via Direct Jamo Learning)***
- **"ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์— ๋”ฐ๋ผ ์ŒํŒŒ(๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜)๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ดˆยท์ค‘ยท์ข…์„ฑ์„ ๋ฐœ์„ฑํ•˜๋‹ˆ ํ•œ๊ตญ์–ด ๋ฐœ์Œ์ด ๊ทธ๋Œ€๋กœ ๋‚˜์˜จ๋‹ค."**
- ์˜์–ด์‹ IPA ์Œ์†Œ ๋ณ€ํ™˜(eSpeak ๋“ฑ)์ด๋‚˜ ์™œ๊ณก๋œ ์Œ์šด ๊ทœ์น™์„ ๊ฑฐ์น˜์ง€ ์•Š๊ณ , **ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์˜ ์ œ์ž ์›๋ฆฌ์— ๋”ฐ๋ผ ํ•œ๊ธ€ ์ดˆยท์ค‘ยท์ข…์„ฑ ์ž๋ชจ(NFD) ์ž์ฒด๋ฅผ ์ŒํŒŒ(104์ฐจ์› ๊ณ ์œ  ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜)์™€ 1:1๋กœ ์ง์ ‘ ํ•™์Šต**์‹œ์ผœ ๊ธ€์ž ๊ทธ๋Œ€๋กœ์˜ ๋ง‘๊ณ  ์ˆœ์ˆ˜ํ•œ ์ŒํŒŒ ๋ฐœ์„ฑ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
- *(When we construct the acoustic soundwaves / vocal resonance frequencies according to the Hunminjeongeum Haeryebon and vocalize the initial, medial, and final Jamo, authentic Korean pronunciation emerges just as written.)*
4. **์ดˆ๊ฒฝ๋Ÿ‰ ๊ตฌ๋™ & ๋ฐœ์—ด์ด ๊ฑฐ์˜ ์—†์Œ *(Ultra-Light & Near-Zero Heat on Mobile / CPU)***
- ๋ฌด๊ฑฐ์šด ๊ฑฐ๋Œ€ ์‹ ๊ฒฝ๋ง ๊ธฐ์–ต์ธต์ด๋‚˜ ํ’€๋ฐด๋“œ HiFi-GAN ๋ณด์ฝ”๋” ๋Œ€์‹ , **์ž๋ชจยท์„ฑ์šฐยท๊ฐ์ • ์ฃผํŒŒ์ˆ˜์˜ ๋‹จ์ผ ์„ ํ˜• ํˆฌ์˜(`emb_f`, `emb_g_res`, `emb_res`) + 4-๋ถ€๋Œ€์—ญ MB-iSTFT-VITS(PQMF) ํ•ฉ์„ฑ ๊ตฌ์กฐ**๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์—ฐ์‚ฐ๋Ÿ‰์ด ๊ทน๋„๋กœ ๊ฐ€๋ฒผ์›Œ ์Šค๋งˆํŠธํฐ(๋ชจ๋ฐ”์ผ)์ด๋‚˜ ์ผ๋ฐ˜ ๋…ธํŠธ๋ถ CPU์—์„œ๋„ **๋ฐœ์—ด์ด ๊ฑฐ์˜ ์—†์ด ๊ฐ€๋ณ๊ณ  ๋น ๋ฅด๊ฒŒ ์‹ค์‹œ๊ฐ„ ๊ตฌ๋™**๋ฉ๋‹ˆ๋‹ค.
---
## ๊ธฐ์กด ํŒŒ์ดํผ(Piper)๋ณด๋‹ค ๋‚˜์•„๊ฐ„ ์ 
| ๊ตฌ๋ถ„ | ๊ธฐ์กด Piper | **Piper Plus (Epoch 880 EMA)** |
|---|---|---|
| **๋œ์†Œ๋ฆฌ(๊ฒฝ์Œํ™”) ์ œ์–ด** | ๋ฌธ๋งฅ ๊ธฐ์–ต์ธต์ด ๊ฒฝ์Œํ™”๋ฅผ ๊ณผ์ž‰ ์ผ๋ฐ˜ํ™”ํ•˜์—ฌ ํ‰์Œ์ด ๋œ์†Œ๋ฆฌ๋กœ ๋ฌด๋„ˆ์ง | **์ถœ๋ ฅ ์ธ์ฝ”๋” ๊ธฐ์–ต์ธต(`self.emb`) 100% ๋ฏธํ†ต๊ณผ(๋Œ€์ฒด) + ์„ธ๊ธฐํŒ๋ณ„๊ธฐ(์˜ˆ์‚ฌ์†Œ๋ฆฌ ๋ณดํ˜ธ 24๋ฐฐ) + ํ‰์„ฑ ๊ฐ€์ค‘์น˜ ์–ต์ œ(`0,2,4`)**๋กœ ์˜ˆ์‚ฌ์†Œ๋ฆฌ ๋ณด์กด, **๋ถˆ๊ทœ์น™ ๋ฐœ์Œ์€ ์‚ฌ์ „์œผ๋กœ 1:1 ๋Œ€์‘** |
| **ํ•œ๊ตญ์–ด ์ž…๋ ฅ ๋ฐ ๋ฐœ์„ฑ** | eSpeak IPA ์Œ์†Œ ๋ณ€ํ™˜ (ํ•œ๊ตญ์–ด ๋ฐœ์Œ ๊ทœ์น™ ๋ถ€์ •ํ™•ยท์Œ์†Œ ์™œ๊ณก) | **ํ•œ๊ธ€ NFD ์ž๋ชจ ์ง๊ฒฐ ์ŒํŒŒ ๋ฐœ์„ฑ** โ€” ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€ ์ œ์ž ์›๋ฆฌ๋ฅผ ๋”ฐ๋ฅธ 104์ฐจ์› ์„ฑ๋„ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ์ง์ ‘ ์ž…๋ ฅ (๊ธ€์ž ๊ธฐ์–ต ์นธ 0) |
| **์™ธ๊ตญ์–ดยท์˜์–ด ํ™•์žฅ** | ๋ฏธ์ง€์› ๋˜๋Š” ์–ด์ƒ‰ํ•œ ์•ŒํŒŒ๋ฒณ ์ฒ ์ž ์ฝ๊ธฐ | **์˜›๋ชจ์Œยท์˜›์ž์Œ(`แ„ซ, แ…€, แ„ฝ, แ…—, แ„ผ, ใ†, ใ†Ž` ๋“ฑ) ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜**๋ฅผ ํ†ตํ•œ ์›์–ด๊ธ‰ ๋งˆ์ฐฐ์Œ/๋ชจ์Œ ๋ฐœ์„ฑ + **๋ฏธํ•™์Šต ์˜๋‹จ์–ด ํ•œ๊ตญ์‹ ๋ฐœ์Œ(์ฝฉ๊ธ€๋ฆฌ์‰ฌ/์•ŒํŒŒ๋ฒณ ์Œ์ฐจ)** ์ž๋™ ๋ณ€ํ™˜ |
| **์ถœ๋ ฅ ๋””์ฝ”๋” & ๋ฐœ์—ด** | ๋ฌด๊ฑฐ์šด HiFi-GAN ์ƒ์„ฑ๊ธฐ (๋ชจ๋ฐ”์ผ ๋ฐœ์—ดยท์ง€์—ฐ ๋ฐœ์ƒ) | **๋‹จ์ผ ์„ ํ˜• ๊ณต๋ช… ํˆฌ์˜ + 4๋Œ€์—ญ MB-iSTFT ๋””์ฝ”๋”**(PQMF 4๋ถ€๋Œ€์—ญ ยท `n_fft` 16 ยท `hop` 4) โ€” **์Šค๋งˆํŠธํฐยทCPU์—์„œ ๋ฐœ์—ด์ด ๊ฑฐ์˜ ์—†์ด ์ดˆ๊ฒฝ๋Ÿ‰ ์‹ค์‹œ๊ฐ„ ๊ตฌ๋™** |
| **์„ฑ์šฐ ํ‘œํ˜„** | ํ™”์ž ID๋งˆ๋‹ค ์™ธ์šด ๊ณ ์ • ์ž„๋ฒ ๋”ฉ(`emb_g`) | **์„ฑ์šฐ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ 31๊ฐ€์ง€**(`spk_feat 28ร—31 โ†’ Linear 31โ†’512`) โ€” ์Œ๋†’์ดยท์„ฑ๋„ยทํฌ๋งŒํŠธยท์Œ์ƒ‰ ๊ป์งˆ z ์ ์ˆ˜๋กœ ๋ชฉ์†Œ๋ฆฌ ์ƒ์„ฑ |
| **๊ฐ์ • ํ‘œํ˜„ & ํƒœ๊ทธ** | ์—†์Œ (๋‹จ์กฐ๋กœ์šด ๋‚ญ๋…) | **์ „์šฉ ๋ฌธ๋งฅ ๊ฐ์ • ๋ชจ๋ธ(`emotion_int8.onnx`) ์ž๋™ ๋งคํ•‘** + **์‹ ๊ฒฝ๊ณต๋ช… `res`(๋…ธ๋ธŒ 3 + ๊ผฌ๋ฆฌ 15)** + **๊ฐ์ • ํƒœ๊ทธ ์ œ๋กœ๋“€๋ ˆ์ด์…˜(๊ธธ์ด 0, ์†Œ๋ฆฌ ์—†์ด ๊ฐ์ •๋งŒ ์ฃผ์ž…)** |
| **ํ˜ธํก ๋ฐ ๋Š์–ด ์ฝ๊ธฐ** | ์–ด์ ˆ๋งˆ๋‹ค ๊ธฐ๊ณ„์  ๋‹จ์ ˆ ๋˜๋Š” ๊ธด ๋ฌธ์žฅ ํ†ต์งธ ํ•ฉ์„ฑ ์‹œ ์–ต์–‘ ๋ถ•๊ดด | **์–ด์ ˆ ๊ฒฝ๊ณ„(`eojeol_boundary`) ์™„์ „ ์ œ๊ฑฐ** + **ํ‘œ์ค€ ์ •์† `0.85` & ๊ฐ์ • ๋น ๋ฅด๊ธฐ(`1.08/1.0/0.93`)** + **์ข…๊ฒฐํ˜•ยท์—ฐ๊ฒฐํ˜• ์–ด๋ฏธ ๊ฐˆ๋ผ์ฝ๊ธฐ(`0.01์ดˆ` ์ด์Œ)** |
---
## ๐Ÿ›๏ธ ํ•ต์‹ฌ ๊ธฐ์ˆ  ์•„ํ‚คํ…์ฒ˜ (Core Technical Architecture)
### 1. 3๋Œ€ ์™„์ „๊ณต๋ช… ์•„ํ‚คํ…์ฒ˜ (๋ฐœ์Œ๊ณต๋ช… ยท ์„ฑ์šฐ๊ณต๋ช… ยท ๊ฐ์ •๊ณต๋ช…) & ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€ ์ฃผํŒŒ์ˆ˜ ๋งคํ•‘
> **"ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์— ๋”ฐ๋ผ ์ŒํŒŒ(๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜)๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ดˆยท์ค‘ยท์ข…์„ฑ์„ ๋ฐœ์„ฑํ•˜๋‹ˆ ํ•œ๊ตญ์–ด ๋ฐœ์Œ์ด ๊ทธ๋Œ€๋กœ ๋‚˜์˜จ๋‹ค."**
> *"ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์ด ๋ฐํžŒ ๋ฐœ์„ฑ๋ฒ•์„ ๊ทธ๋Œ€๋กœ ์ฃผํŒŒ์ˆ˜๋กœ ๋ณธ๋–  ๋งž์ถ˜ ์ŒํŒŒ ๋ฐœ์„ฑ ๋ชจ๋ธ"*
> *(When we construct the acoustic soundwaves / vocal resonance frequencies according to the Hunminjeongeum Haeryebon and vocalize the initial, medial, and final Jamo, authentic Korean pronunciation emerges just as written.)*
๋ณดํ†ต์˜ ์Œ์„ฑ ํ•ฉ์„ฑ ๋ชจ๋ธ์€ ๊ธ€์ž ํ•˜๋‚˜, ํ™”์ž ํ•˜๋‚˜, ๊ฐ์ • ํ•˜๋‚˜๋งˆ๋‹ค ์ž„๋ฒ ๋”ฉ ์นธ์„ ๋‘๊ณ  ๊ทธ ๊ฐ’์„ ๋‹จ์ˆœ ์•”๊ธฐํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด **Piper Plus (`์™„์ „๊ณต๋ช…ํŒ` Epoch 880 EMA)**๋Š” ๊ธ€์žยท์„ฑ์šฐยท๊ฐ์ •์˜ ๊ฐœ๋ณ„ ๊ธฐ์–ต ์นธ์„ ๋ชจ๋‘ ๋น„์šฐ๊ณ , ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธ(่จ“ๆฐ‘ๆญฃ้Ÿณ ่งฃไพ‹ๆœฌ)ใ€์ด ๋ฐํžŒ ๋ฐœ์„ฑ ๊ธฐ๊ด€์˜ ๊ณผํ•™์  ์ œ์ž ์›๋ฆฌ๋ฅผ ํ˜„๋Œ€ ์Œํ–ฅ๋ฌผ๋ฆฌํ•™์˜ **์„ฑ๋„(Vocal Tract) ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜**๋กœ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธด **3๋Œ€ ์™„์ „๊ณต๋ช… ์•„ํ‚คํ…์ฒ˜**๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
1. **๋ฐœ์Œ๊ณต๋ช… (`jamo_feat` `544ร—104` โ†’ `emb_f` `Linear(104 โ†’ 192, bias=False)`)**:
- ๊ธ€์ž๋ณ„ ์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต ์นธ(`self.emb`)์„ ์“ฐ์ง€ ์•Š๊ณ , ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์˜ ์ œ์ž ์›๋ฆฌ๋ฅผ ๋ณธ๋–  ๋…น์Œ์—์„œ ์‹ค์ธกํ•œ **104์ฐจ์› ์ž๋ชจ ์„ฑ์งˆ ํ–‰๋ ฌ(`jamo_feat`: ์•ž 80์นธ์€ `ํ‘œ_๋ฐ”ํƒ•.json` ์ž๋ชจ ๋ฐœ์Œ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜, ๋’ค 24์นธ์€ `ํ‘œ_ํƒœ๊ทธ.json` ์†Œ๋ฆฌยท๋ฐœ์„ฑยท์ˆ˜์‹ ํƒœ๊ทธ ์ฃผํŒŒ์ˆ˜)**์— ๋‹จ์ผ ๊ณต์œ  ์„ ํ˜• ๋ณ€ํ™˜(`emb_f`)๋งŒ ๊ฑฐ์ณ ํ…์ŠคํŠธ ์ธ์ฝ”๋”๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค(`resonance/jamo_features.json`).
2. **์„ฑ์šฐ๊ณต๋ช… (`spk_feat` `28ร—31` โ†’ `emb_g_res` `Linear(31 โ†’ 512)`)**:
- ์„ฑ์šฐ๋ณ„ ์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต ์นธ(`emb_g`)์„ ์•„์˜ˆ ๋งŒ๋“ค์ง€ ์•Š๊ณ , ์„ฑ์šฐ๋งˆ๋‹ค ๋…น์Œ์—์„œ ์‹ค์ธกํ•œ **31๊ฐ€์ง€ ๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜ ๋ฒกํ„ฐ(`z`: ์Œ๋†’์ด, F1~F4, B1~B3, ์„ฑ๋„ ๊ธธ์ด, ์ €์—ญ/ํ‰์„ฑ, ๋ฐฐ์šฐ ํฌ๋งŒํŠธ, ๋ฐ๊ธฐ ์ค‘์‹ฌ, ๊ธฐ์šธ๊ธฐ, H1-H2, CPP, HNR, MFCC ๊ป์งˆ 1~12)**์— ๋‹จ์ผ ๊ณต์œ  ์„ ํ˜• ์‹(`Wยทz + b`)๋งŒ ์ ์šฉํ•ด ์Œ์ƒ‰ ์กฐ๊ฑด `g`๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค(`resonance/speaker_features.json`).
3. **๊ฐ์ •๊ณต๋ช… (`tag_dummy=True` ๋นˆ์นธ `250` + `์ฃผํŒŒ์ˆ˜ํƒœ๊ทธ์นธ` + `res` 34์ฐจ์›)**:
- ๊ฐ์ •ยท๋งํˆฌ ํƒœ๊ทธ ๋ฒˆํ˜ธ๋งˆ๋‹ค ๋”ฐ๋กœ ์™ธ์šฐ๋Š” ๊ธฐ์–ต ์นธ์„ ์—†์• ๊ณ  ๋ชจ๋‘ ๋นˆ ์นธ(`tag_dummy_id = 250`)์œผ๋กœ ๋ณด๋‚ธ ๋’ค, **`res` 34์ฐจ์› ์‹ ๊ฒฝ๊ณต๋ช… ๋ฒกํ„ฐ** โ€” **`res_g_dims = 3` ๊ณต๋ช… ๋…ธ๋ธŒ**(์„ฑ๋„ ์ฒ™๋„ ยท ์ €์—ญ/ํ‰์„ฑ ยท ๋ฐฐ์šฐ ํฌ๋งŒํŠธ) + **`res_g_tail = 15` ๊ฐ์ • ๊ณต๋ช… ๊ผฌ๋ฆฌ**(eGeMAPS 14๊ฐ€์ง€ ์Œ์งˆ ์ฃผํŒŒ์ˆ˜ ๋ณ€ํ™”๋Ÿ‰ + ์ฝง์†Œ๋ฆฌ ์„ž์ž„ `A1-P0`) โ€” ๋กœ๋งŒ ์ž‘๋™ํ•˜๊ฒŒ ํ•˜์—ฌ ๋ชจ๋“  ์„ฑ์šฐ์—๊ฒŒ ๊ฐ์ • ํ‘œํ˜„์ด ๊ณ ๋ฅด๊ฒŒ ์ „์ด๋˜๋„๋ก ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค(`emotion/emotion_resonance.json`).
| ๊ณต๋ช… ๊ฐˆ๋ž˜ | ๋ชจ๋ธ ๋‚ด๋ถ€ ๋ณ€ํ™˜ ๊ตฌ์กฐ | ์ฐจ์›(์นธ ์ˆ˜) | ์„ค์ • ํ‘œ ํŒŒ์ผ |
|---|---|---|---|
| **๋ฐœ์Œ๊ณต๋ช… (์ž๋ชจ)** | `jamo_feat` (`544ร—104`) โ†’ `emb_f` (`Linear 104โ†’192, bias=False`) โ€” `self.emb` 100% ๋Œ€์ฒด | 104 (๋ฐœ์Œ 80 + ํƒœ๊ทธ 24) | `resonance/jamo_features.json` |
| **์„ฑ์šฐ๊ณต๋ช… (ํ™”์ž)** | `spk_feat` (`28ร—31`) โ†’ `emb_g_res` (`Linear 31โ†’512`) โ€” ํ™”์ž ์ž„๋ฒ ๋”ฉ `emb_g` ์—†์Œ | 31 | `resonance/speaker_features.json` |
| **๊ฐ์ •๊ณต๋ช… (๊ฐ์ •)** | `res` (`[b, 34]`): ๊ณต๋ช… ๋…ธ๋ธŒ `3` (`[0:3]`) + ๊ฐ์ • ๊ผฌ๋ฆฌ `15` (`[19:34]`, eGeMAPS 14 + ์ฝง์†Œ๋ฆฌ `A1-P0`) | 3 + 15 (`res` 34) | `emotion/emotion_resonance.json` |
---
### 1-1. ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€ ์ŒํŒŒ ๋ฐœ์„ฑ์˜ ์ฝ”๋“œ์ ยท์ˆ˜ํ•™์  ์‹ค์ฆ (`ํ‘œ_๋ฐ”ํƒ•.json` ยท `ํ•™์Šต์ฃผํŒŒ์ˆ˜ํ‘œ๋งŒ๋“ค๊ธฐ.py` ยท `ํ•ด๋ก€๋ณธ๋ฐ›์นจ.py`)
๋น„์œ ๊ฐ€ ์•„๋‹ˆ๋ผ **์‹ค์ œ ์‹ ๊ฒฝ๋ง์˜ ์ˆ˜ํ•™์  ์—ฐ์‚ฐ ์ž์ฒด๊ฐ€ ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์˜ ์›๋ฆฌ ๊ทธ๋Œ€๋กœ๋งŒ ๋Œ์•„๊ฐ€๋„๋ก** ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
#### โ‘  ๊ธ€์ž ์ด๋ฆ„ํ‘œ(0/1 ๊ธฐํ˜ธ ์นธ) 49๊ฐœ ์ „๋Ÿ‰ `0` ์ œ๊ฑฐ โ€” ์˜ค์ง 31๊ฐœ ๋ฌผ๋ฆฌ์  ์ŒํŒŒ ์ฃผํŒŒ์ˆ˜๋งŒ ์ž‘๋™
- `resonance/jamo_features.json`(`ํ‘œ_๋ฐ”ํƒ•.json` 80์—ด + ํƒœ๊ทธ ์ฃผํŒŒ์ˆ˜ 24์—ด = 104์—ด)์˜ ๊ธฐ๋ณธ 80๊ฐœ ์—ด์„ ๊ฒ€์‚ฌํ•˜๋ฉด, ์ดˆ๊ธฐ ์„ค๊ณ„์— ์žˆ๋˜ `๊ธ€์žใ„ฑ~ใ…Ž`, `์ž๋ฆฌ:์–ด๊ธˆ๋‹ˆ/ํ˜€/์ž…์ˆ `, `๋ฐฉ์‹:๋ง‰ํž˜/๊ฐˆ๋ฆผ`, `์„ธ๊ธฐ:์˜ˆ์‚ฌ/๊ฑฐ์„ผ/๋œ` ๋“ฑ **๊ธฐํ˜ธ ํ‘œ์‹œ(One-Hot) ์นธ 49๊ฐœ๋Š” ์ „๋Ÿ‰ `0.0`**์œผ๋กœ ๋น„์›Œ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค (`assert all(v == 0 ... if ์ด๋ฆ„[i] not in ์žฐ)`).
- ๋™์‹œ์— `models.py`(`jamo_feature_mode = "๋Œ€์ฒด"`)์—์„œ ๋ชจ๋“  ์ž๋ชจ ํ† ํฐ์€ ๊ฐœ๋ณ„ ๊ธ€์ž ์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต ์นธ(`self.emb(x)`)์„ **100% ๋ฒ„๋ฆฌ๊ณ **, ์˜ค์ง `self.emb_f(jamo_feat[x])`๋งŒ ์ธ์ฝ”๋”๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.
- ์ฆ‰, ์‹ ๊ฒฝ๋ง์€ ์ง€๊ธˆ ๋“ค์–ด์˜จ ๊ธ€์ž๊ฐ€ `'ใ„ฑ'`์ธ์ง€ `'ใ…'`์ธ์ง€ ๋ฒˆํ˜ธ๋‚˜ ๊ธฐํ˜ธ๋กœ๋Š” ์ „ํ˜€ ์•Œ ์ˆ˜ ์—†๊ณ , **์˜ค์ง 31๊ฐœ์˜ ๋ฌผ๋ฆฌ์  ์ŒํŒŒ ์ฃผํŒŒ์ˆ˜ ์ˆ˜์น˜๋งŒ ๋ณด๊ณ  ์†Œ๋ฆฌ๋ฅผ ํ•ฉ์„ฑํ•ฉ๋‹ˆ๋‹ค.**
#### โ‘ก ์ดˆ์„ฑยท์ค‘์„ฑยท์ข…์„ฑ 31๊ฐœ ์ฃผํŒŒ์ˆ˜ ์นธ๊ณผ ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€ ๊ตฌ์ ˆ์˜ 1:1 ์ง๊ฒฐ
1. **์ดˆ์„ฑ 16์นธ โ€” ํ•ด๋ก€๋ณธ `ๅˆ่ฒๆœ‰็™ผๅ‹•ไน‹็พฉ` (์ดˆ์„ฑ์€ ์†Œ๋ฆฌ๋ฅผ ์ผ์œผํ‚จ๋‹ค) & ์˜ค์Œ(์•„ยท์„คยท์ˆœยท์น˜ยทํ›„)ยท๊ฐ€ํš**
- ์กฐ์Œ ์œ„์น˜(์ž…์ˆ ์†Œ๋ฆฌ `ใ…‚/ใ…`์€ ์ €์—ญ์— ํผ์ง€๊ณ  ์–ด๊ธˆ๋‹ˆ์†Œ๋ฆฌ `ใ„ฑ/ใ…‹`์€ ์ค‘์—ญ์— ๋ชฐ๋ฆฌ๋Š” `์ดˆ์„ฑํ„ฐ์ง์ €์ค‘๋น„ยท์ค‘๊ณ ๋น„`, `์ดˆ์„ฑํ„ฐ์งkHz`, `์ดˆ์„ฑํ„ฐ์ง๋ด‰์šฐ๋ฆฌ/ํผ์งkHz`, `์ดˆ์„ฑF2/F3ํœจ`)์™€ ๊ฐ€ํš(์ „์ฒญยท์ฐจ์ฒญยท์ „ํƒ์— ๋”ฐ๋ฅธ `์ดˆ์„ฑ์ˆจ`, `์ดˆ์„ฑ์ˆจ์†Œ๋ฆฌkHz/๋ด‰์šฐ๋ฆฌ/ํผ์ง/์ €์ค‘๋น„/์ค‘๊ณ ๋น„`, `์ดˆ์„ฑ์„ธ๊ธฐ`, `์ดˆ์„ฑ๋’ค์Œ๋†’์ด`, `์ดˆ์„ฑ์šธ๋ฆผkHz`) ์‹ค์ธก์น˜๋งŒ ์ž…๋ ฅ๋ฉ๋‹ˆ๋‹ค.
- ํŠนํžˆ ์ดˆ์„ฑ `ใ…‡`์„ `0`์œผ๋กœ ๋น„์› ์„ ๋•Œ ๊ธฐ์–ต ์นธ(`self.emb`)์œผ๋กœ ๋น ์ ธ ๋ชจ์Œ ์—ฐ์† ๋ฐœ์Œ(`์•„์•„โ†’์•„`, `์œผ์•—โ†’์™€`)์ด ๋ญ‰๊ฐœ์ง€๋˜ ํ˜„์ƒ์„, ํ•ด๋ก€๋ณธ ์ œ์žํ•ด ์›๋ฌธ **`ใ€Œใ…‡ใ€‚ๅ–‰้Ÿณใ€‚ๅฆ‚ๆฌฒๅญ—ๅˆ็™ผ่ฒใ€`**(๋ชฉ์ฒญ์—์„œ ์†Œ๋ฆฌ๋ฅผ ์—ฌ๋Š” ํ›„์Œ) ๊ทธ๋Œ€๋กœ ์ฝ”๋“œ์— ์ ์šฉํ•ด ์‹ค์ œ ๋ชฉ์ฒญ ๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜๋ฅผ ๋„ฃ์–ด ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.
2. **์ค‘์„ฑ 7์นธ โ€” ์ฒœยท์ง€ยท์ธ ๋‹จ๋ชจ์Œ๊ณผ ํ•ฉ์šฉ(์ด์ค‘๋ชจ์Œ)์˜ ํฌ๋จผํŠธ ์ถœ๋ฐœยท๋„์ฐฉ ๊ถค์ **
- ๋ชจ์Œ ์‹œ์ž‘ 10% ์ง€์ (`๊ณต๋ช…F1ยทF2ยทF3์ถœ๋ฐœ`)๊ณผ 75% ์ง€์ (`๊ณต๋ช…F1ยทF2ยทF3๋„์ฐฉ`), `๋ชจ์Œ๊ธธ์ด` 7์นธ๋งŒ์œผ๋กœ ๋‹จ๋ชจ์Œ๊ณผ ํ•ฉ์šฉ ์ด์ค‘๋ชจ์Œ(`ใ…˜, ใ…™, ใ…, ใ…ž, ใ…ข` ๋“ฑ)์˜ ์„ฑ๋„ ๊ณต๋ช… ์ด๋™ ๊ถค์ ์„ ๊ทธ๋Œ€๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.
3. **์ข…์„ฑ(๋ฐ›์นจ) 8์นธ + ์—ฐ์Œ ๋„˜์–ด๊ฐ 16์นธ โ€” ํ•ด๋ก€๋ณธ ์ข…์„ฑํ•ดยทํ•ฉ์žํ•ด ์›๋ฌธ ๊ตฌํ˜„์œผ๋กœ ๋ฐ›์นจ ๋ฏธ๋ถ„ํ™” ํ•ด๊ฒฐ (`ํ•ด๋ก€๋ณธ๋ฐ›์นจ.py`)**
- ์—ํญ 632 ๋‹น์‹œ ๋‹จ์ˆœ ๋ชจ์Œ ์ค‘์•™๊ฐ’ ๋น„์œจ์„ ๋„ฃ์—ˆ์„ ๋•Œ๋Š” `๊ฐ„ยท๊ฐยท๊ฐ•`์ด ์†Œ๋ฆฌ๋กœ ๊ฐˆ๋ฆฌ์ง€ ์•Š๊ณ  ๋ชจ๋‘ `์นธ`์œผ๋กœ ๋ฐœ์Œ๋˜์—ˆ์œผ๋‚˜(๋ 40% ์ฐจ์ด 3~8dB), ํ•ด๋ก€๋ณธ์˜ 5๊ฐ€์ง€ ์›๋ฆฌ๋ฅผ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•ด ๋„ฃ์ž ๋ฐ›์นจ ๋ฐœ์„ฑ์ด ์ •ํ™•ํžˆ ๋ถ„๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค(์ž…๋ ฅ ๊ฑฐ๋ฆฌ `ใ„ดโ†”ใ…‡` 0.98โ†’1.95, `ใ„ฑโ†”ใ…‚` 1.45โ†’3.72):
- **`็ต‚่ฒๅพฉ็”จๅˆ่ฒ` & `ใ†่ˆ‡ใ„ฑ็ˆฒๅฐ ยท ่ˆŒไน‹ใ„ดใ„ท ยท ่„ฃไน‹ใ…ใ…‚` (์ดˆ์„ฑ์„ ๋‹ค์‹œ ์“ฐ๋ฉฐ ์ฝง์†Œ๋ฆฌ์™€ ๋ง‰ํž˜์†Œ๋ฆฌ๋Š” ์กฐ์Œ ์ž๋ฆฌ๊ฐ€ ๊ฐ™๋‹ค)**: ์–ด๊ธˆ๋‹ˆ(`ใ„ฑยทใ†` `1,503Hz`), ํ˜€(`ใ„ดยทใ„ท` `1,875Hz`, `ใ„น` `1,813Hz`), ์ž…์ˆ (`ใ…ยทใ…‚` `559Hz`) ์ง์„ ๋ฌถ์–ด ๊ตฌํ•œ ๋กœ์ปค์Šค ์ž๋ฆฌ ์ฃผํŒŒ์ˆ˜(`z2์ง`, `z3์ง`)๋ฅผ `๋ฐ›์นจF2ํœจยทF3ํœจ`์— ์ž…๋ ฅ.
- **`ๅ…ซๅญ—ๅฏ่ถณ็”จ` (8์ข…์„ฑ ๊ฐ€์กฑ์šฉ)**: `ใ„ฒยทใ…‹โ†’ใ„ฑ`, `ใ……ยทใ…†ยทใ…ˆยทใ…Šยทใ…Œโ†’ใ„ท`, `ใ…โ†’ใ…‚`์œผ๋กœ 8์ข…์„ฑ ๋Œ€ํ‘œ ์ž๋ฆฌ ์ฃผํŒŒ์ˆ˜๋ฅผ ๋งคํ•‘(`ใ…Ž`์€ ํ›„์Œ์ด๋ผ ์ž…์•ˆ ์ž๋ฆฌ ํœจ `0`).
- **`็ต‚่ฒๆœ‰ๆญขๅฎšไน‹็พฉ` & `ไธๆทธไธๆฟไน‹ๅญ— ๅ…ถ่ฒไธๅŽฒ(่ˆ’็ทฉ) / ๅ…จๆทธโ€ฆๅ…ถ่ฒ็ˆฒๅŽฒ(ไฟƒๆ€ฅ)`**: ๋ฐ›์นจ ์นธ ์ž์ฒด์—๋Š” ํ„ฐ์งยท์ˆจ์„ ๋‘์ง€ ์•Š๊ณ , ๋ถˆ์ฒญ๋ถˆํƒ(`ใ„ดยทใ„นยทใ…ยทใ…‡`)์—๋งŒ ์šธ๋ฆผ(`๋ฐ›์นจ์šธ๋ฆผkHz`)๊ณผ ๋น„๊ฐ• ๋ฐ˜๊ณต๋ช…(`๋ฐ›์นจ์ฝง์†Œ๋ฆฌ๊ณจkHz`: `ใ„ด 1.38 / ใ… 1.30 / ใ…‡ 1.73kHz`)์„ ๋ถ€์—ฌํ•˜๋ฉฐ ๋ง‰ํž˜ ๋ฐ›์นจ(`๋‹ซํž˜๋น„ > 0.5`)์€ ์šธ๋ฆผ์„ `0`์œผ๋กœ ์„ค์ •.
- **`็ต‚่ฒไบŒๅญ—ไธ‰ๅญ—ๅˆ็”จ ่‡ชๅทฆ่€Œๅณ` (ํ•ฉ์žํ•ด)**: ๊ฒน๋ฐ›์นจ(`ใ„ณ, ใ„ต, ใ„ถ, ใ„บ, ใ„ป, ใ„ผ, ใ„ฝ, ใ„พ, ใ„ฟ, ใ…€, ใ…„`)์€ ์™ผ์ชฝ ์ฒซ ์ž์Œ ์ž๋ฆฌ(`๋ฐ›์นจF2ํœจยทF3ํœจ`)๋กœ ๋“ค์–ด๊ฐ€ ์˜ค๋ฅธ์ชฝ ๋‘˜์งธ ์ž์Œ ์ž๋ฆฌ(`๋ฐ›์นจ๋‘˜์งธF2ํœจยทF3ํœจ`)๋กœ ๋‹ซํžˆ๊ณ , ๊ธธ์ด๋Š” ๋‘ ์†Œ๋ฆฌ์˜ ํ•ฉ์œผ๋กœ ๊ณ„์‚ฐํ•˜๋ฉฐ, ๋ชจ์Œ ์•ž์—์„œ ๋‹ค์Œ ์Œ์ ˆ ์ฒซ์†Œ๋ฆฌ๋กœ ๋ฐœ๋™ํ•˜๋Š” ์—ฐ์Œ(๋„˜์–ด๊ฐ)์€ ๋ฐ›์นจ ์ค„์˜ ์ดˆ์„ฑ 16์นธ์— ํ•จ๊ป˜ ์‹ค์–ด ๋ชจ๋ธ์ด ๋ฌธ๋งฅ์— ๋”ฐ๋ผ ์„ ํƒํ•˜๊ฒŒ ํ•จ.
#### โ‘ข ์˜›๊ธ€์ž(์˜์–ด ๋งˆ์ฐฐ์Œ)๊นŒ์ง€ ๋™์ผํ•œ ์ฃผํŒŒ์ˆ˜ ์‹ ํ•˜๋‚˜๋กœ ๋ฐœ์„ฑ
- ํ•œ๊ตญ์–ด ๋ณธ๋ฌธ ๋…น์Œ์— ์—†๋Š” ์˜›์ž์Œ `แ…—[f]`, `แ„ซ[v]`, `แ…€[z]`, `แ„ผ[ฮธ]`, `แ„ฝ[รฐ]`(๋ฒˆํ˜ธ `384~388`) ์—ญ์‹œ ๊ธ€์ž ๊ธฐ์–ต ์นธ ์—†์ด ์ดˆ์„ฑ 16์นธ์— ์˜์–ด ๋งˆ์ฐฐ์Œ ์‹ค์ธก ์ฃผํŒŒ์ˆ˜๋งŒ ์ฑ„์›Œ ๋„ฃ์—ˆ์„ ๋•Œ ๋™์ผํ•œ `emb_f` ์‹ ํ•˜๋‚˜๋กœ ๋ฐœ์„ฑ๋ฉ๋‹ˆ๋‹ค.
---
### 2. ์™œ ๋œ์†Œ๋ฆฌ๋ฅผ ํ”ผํ•ด๊ฐˆ ์ˆ˜ ์žˆ์—ˆ๋Š”๊ฐ€? โ€” ์ž๋ชจ ๊ธฐ์–ต์ธต(`self.emb`) 100% ์šฐํšŒ & ์„ธ๊ธฐํŒ๋ณ„๊ธฐ 24๋ฐฐ ๋ณดํ˜ธ & ๋ถˆ๊ทœ์น™ ๋ฐœ์Œ ์‚ฌ์ „ ๋Œ€์‘
๊ธฐ์กด ํ•œ๊ตญ์–ด ์‹ ๊ฒฝ๋ง TTS๋“ค์ด ์˜ˆ์‚ฌ์†Œ๋ฆฌ๋ฅผ ๊ณผ๋„ํ•˜๊ฒŒ ๋œ์†Œ๋ฆฌ๋กœ ๋ฐœ์Œํ•˜๋Š” ๊ทผ๋ณธ ์›์ธ์€ **๊ธ€์ž๋ณ„ ์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต์ธต(`self.emb`)๊ณผ ๋ฌธ๋งฅ ๊ธฐ์–ต์˜ ๊ฒฝ์Œํ™” ๊ณผ์ž‰ ์ผ๋ฐ˜ํ™”**์— ์žˆ์Šต๋‹ˆ๋‹ค. ํ•™์Šต ๋ฐ์ดํ„ฐ์— `์ธ๊ถŒ[์ธ๊ฟ˜]`, `์†๊ฐ€๋ฝ[์†๊นŒ๋ฝ]`์ฒ˜๋Ÿผ ํ‘œ๊ธฐ๋Š” ์˜ˆ์‚ฌ์†Œ๋ฆฌ(`ใ„ฑ`)์ธ๋ฐ ๋…น์Œ์€ ๋œ์†Œ๋ฆฌ(`[ใ„ฒ]`)์ธ ๋‹จ์–ด๊ฐ€ ๋“ค์–ด ์žˆ์œผ๋ฉด, ๊ธฐ์–ต์ธต์„ ํ†ต๊ณผํ•˜๋Š” ์‹ ๊ฒฝ๋ง์€ *"์šธ๋ฆผ ๋ฐ›์นจ ๋’ค์˜ ํ‰์Œ์€ ๋œ์†Œ๋ฆฌ๋กœ ์ฝ๋Š”๋‹ค"*๋Š” ๋ฒ„๋ฆ‡์„ ์™ธ์›Œ `์†๊ถŒ`๊นŒ์ง€ `[์†๊ฟ˜]`์œผ๋กœ ์ฝ๊ณ  ๋ฌธ์žฅ ๊ณณ๊ณณ์˜ ํ‰์Œ์„ ๊ฒฝ์Œ์œผ๋กœ ์˜ค์—ผ์‹œํ‚ต๋‹ˆ๋‹ค.
**Piper Plus (`์™„์ „๊ณต๋ช…ํŒ` Epoch 880 EMA)**๋Š” ์ด๋ฅผ ๋‹ค์Œ 4๋‹จ๊ณ„ ์•„ํ‚คํ…์ฒ˜์™€ ์—ญํ•  ๋ถ„๋‹ด์œผ๋กœ ์™„๋ฒฝํžˆ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค:
1. **์ถœ๋ ฅยทํ…์ŠคํŠธ ์ธ์ฝ”๋”์—์„œ '๋œ์†Œ๋ฆฌํ™”๋œ ์ž๋ชจ ๊ธฐ์–ต์ธต(`self.emb`)' 100% ๋ฏธํ†ต๊ณผ(๋Œ€์ฒด) ์„ค๊ณ„ (`jamo_feature_mode = "๋Œ€์ฒด"`)**:
- `models.py` (`TextEncoder.forward` 297~302ํ–‰, `_๋ฐœ์„ฑ์ž์งˆ` 1453~1456ํ–‰):
```python
๋ชซ = self.emb_f(F.embedding(x, self.jamo_feat)) # [544, 104] ์ฃผํŒŒ์ˆ˜ ์„ฑ์งˆ ํ–‰๋ ฌ -> Linear(104, 192, bias=False)
x = torch.where(์ž๋ชจ์นธ.unsqueeze(-1), ๋ชซ, x) # ์ž๋ชจ ์นธ์€ ๊ธ€์ž๋ณ„ ๊ธฐ์–ต ์นธ(self.emb)์„ 100% ๋ฒ„๋ฆฌ๊ณ  ์ฃผํŒŒ์ˆ˜ ์„ฑ์งˆ ๋ชซ์œผ๋กœ ๋Œ€์ฒด!
```
- ๋ชจ๋“  ํ•œ๊ธ€ ์ž๋ชจ(ํ˜„๋Œ€ ์ž๋ชจ + ์˜›๋ชจ์Œยท์˜›์ž์Œ)๋Š” ๋œ์†Œ๋ฆฌ ๋ฒ„๋ฆ‡์ด ํ•™์Šต๋œ **๊ธ€์ž๋ณ„ ์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต ์นธ(`self.emb`)์„ ์•„์˜ˆ ํ†ต๊ณผํ•˜์ง€ ์•Š๊ณ **, ์˜ค์ง ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์„ ๋ณธ๋–  ์‹ค์ธกํ•œ **104์ฐจ์› ๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜ ํ–‰๋ ฌ(`jamo_feat`)**์— ๋‹จ์ผ ๊ณต์œ  ์„ ํ˜• ๋ณ€ํ™˜(`emb_f`)๋งŒ ๊ฑฐ์ณ ์ธ์ฝ”๋”๋กœ ์ง๊ฒฐ๋ฉ๋‹ˆ๋‹ค.
- ๋˜ํ•œ `_๋ฌธ๋งฅ์„ฑ์งˆ(x)`(1427~1435ํ–‰)์—์„œ ์ธ์ฝ”๋” ์•ž๋‹จ์˜ ์ธ์œ„์  ๋ฐœ์Œ ๊ทœ์น™์„ ์™„์ „ํžˆ ๋„๊ณ (*"์ž…๋ ฅ ์ธ์ฝ”๋”๋Š” ์•ž๋’ค ๊ธ€์ž๋ฅผ ๋ณด๊ณ  ์„ฑ์งˆ์„ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ๋ƒฅ ์ฃผํŒŒ์ˆ˜๋กœ ์ฝ๋Š”๋‹ค"*), ์—ํญ 437์—์„œ ๋œ์†Œ๋ฆฌ๊ฐ€ ๊ตณ์–ด์ง„ ๊ธฐ์กด ํ…์ŠคํŠธ ์ธ์ฝ”๋”(`enc_p.*` 112๊ฐœ ํ‚ค)๋ฅผ ํ†ต์งธ๋กœ ๋น„์šด ๋’ค **๋ฐœ์Œ๊ณต๋ช…ยท์„ฑ์šฐ๊ณต๋ช…ยท๊ฐ์ •๊ณต๋ช… ์„ธ ๊ฐ€์ง€๋งŒ ๊ธฐ์–ตํ•˜๋Š” '์™„์ „๊ณต๋ช…ํŒ'**์œผ๋กœ ์—ํญ 880๊นŒ์ง€ ์žฌํ•™์Šตํ–ˆ์Šต๋‹ˆ๋‹ค. (์•„์šธ๋Ÿฌ `TextEncoder` ๋‚ด๋ถ€์—๋Š” ๋ฐœ์Œ ์‚ฌ์ „๋ถ„ํฌ `m_p, logs_p`๊ฐ€ ๋ฌธ๋งฅ ์ธ์ฝ”๋”๋ฅผ ์šฐํšŒํ•˜๋Š” `pron_context_free` / `_๋ฐœ์Œ์ถœ๊ตฌ` ๋ชจ๋“ˆ๋„ ํ•จ๊ป˜ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.)
2. **ํ•œ๊ตญ์–ด ์„ธ๊ธฐํŒ๋ณ„๊ธฐ(`tense_clf=True`) & ์˜ˆ์‚ฌ์†Œ๋ฆฌ 24๋ฐฐ ๋ณดํ˜ธ ๊ฐ€์ค‘์น˜(`tense_plain_weight=24.0`)**:
- ํ•™์Šต ์‹œ ์ƒ์„ฑ๋œ ์Œ์„ฑ ํŒŒํ˜•์—์„œ ์˜ˆ์‚ฌ์†Œ๋ฆฌยท๋œ์†Œ๋ฆฌยท๊ฑฐ์„ผ์†Œ๋ฆฌ๋ฅผ ํŒ๋ณ„ํ•˜๋Š” **์„ธ๊ธฐํŒ๋ณ„๊ธฐ(`tense_clf=True`, `c_tense_gen=8.0`)**๋ฅผ ๋‘๊ณ , ํŠนํžˆ **์˜ˆ์‚ฌ์†Œ๋ฆฌ ๋ณดํ˜ธ ๊ฐ€์ค‘์น˜(`tense_plain_weight=24.0`)**๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ ๋ชจ๋ธ์ด ์˜ˆ์‚ฌ์†Œ๋ฆฌ ๊ธ€์ž๋ฅผ ์ž„์˜๋กœ ๋œ์†Œ๋ฆฌ๋กœ ๋ฐœ์„ฑํ•˜๋ฉด **24๋ฐฐ์˜ ๊ฐ•๋ ฅํ•œ ๋ฒŒ์ (Loss)**์„ ๋ฐ›๋„๋ก ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค.
3. **๊ฐ€์Šด(ํ‰์„ฑ) ๊ณต๋ช… ๋Œ€์—ญ ๊ณผ์ž‰ ๊ฐ€์ค‘์น˜ ์–ต์ œ (`res_band_weights = "0,2,4"`)**:
- ์ €์—ญ(ํ‰์„ฑ) ๋Œ€์—ญ ๊ฐ€์ค‘์น˜๋ฅผ `0`์œผ๋กœ ์–ต์ œ(`res_band_weights='0,2,4'`)ํ•˜์—ฌ, ์ €์—ญ ์—๋„ˆ์ง€๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ๋ถ€ํ’€์–ด ์˜ˆ์‚ฌ์†Œ๋ฆฌ๊ฐ€ ๋ฌด๊ฒ๊ณ  ๋”ฑ๋”ฑํ•œ ๋œ์†Œ๋ฆฌ์ฒ˜๋Ÿผ ๋“ค๋ฆฌ๋Š” ํ˜„์ƒ์„ ์›์ฒœ ์ฐจ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.
4. **๋ถˆ๊ทœ์น™ ๋ฐœ์Œ์€ ๋ฐœ์Œ์‚ฌ์ „(Dictionary)์œผ๋กœ 1:1 ์ •๋ฐ€ ๋Œ€์‘**:
- ๊ธฐ์–ต์ธต์„ ์šฐํšŒํ•˜์—ฌ ๋ชจ๋ธ์ด ๊ธ€์ž ๊ทธ๋Œ€๋กœ์˜ ๋ง‘์€ ์˜ˆ์‚ฌ์†Œ๋ฆฌ๋ฅผ 100% ๋ฐœ์„ฑํ•˜๊ฒŒ ๋งŒ๋“  ๋Œ€์‹ , ์‹ค์ œ ํ•œ๊ตญ์–ด์—์„œ ์†Œ๋ฆฌ๊ฐ€ ๋ฐ”๋€Œ์–ด์•ผ ํ•˜๋Š” **๋ถˆ๊ทœ์น™ ๋ฐœ์Œ(์˜ˆ์™ธ์  ๊ฒฝ์Œํ™”, ํ•œ์ž์–ด ๋œ์†Œ๋ฆฌ `์ธ๊ถŒโ†’์ธ๊ฟ˜`, ์‚ฌ์ด์‹œ์˜ท `์†๊ฐ€๋ฝโ†’์†๊นŒ๋ฝ`, ์—ฐ์Œยท`ใ„ด/ใ„น ์ฒจ๊ฐ€` ๋“ฑ)**์€ ์‹ ๊ฒฝ๋ง์˜ ๋ถˆํ™•์‹คํ•œ ์ถ”์ธก์— ๋งก๊ธฐ์ง€ ์•Š๊ณ  ์ถ”๋ก  ์•ž๋‹จ์˜ **๋ฐœ์Œ์‚ฌ์ „(๋ถˆ๊ทœ์น™๋‹จ์–ด์‚ฌ์ „)**์œผ๋กœ 1:1 ์ •๋ฐ€ ๋Œ€์‘ํ•˜๋„๋ก ์—ญํ•  ๋ถ„๋‹ด์„ ์™„์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
---
### 3. ๋ฌธ๋งฅ(Context) ๊ธฐ๋ฐ˜ ์ „์šฉ ๊ฐ์ • ๋ชจ๋ธ ์ž๋™ ๋งคํ•‘ & ์ œ๋กœ๋“€๋ ˆ์ด์…˜(Zero-Duration) ๊ฐ์ • ํƒœ๊ทธ
1. **๋ฌธ๋งฅ์„ ์ฝ์–ด ์ž๋™์œผ๋กœ ๊ฐ์ • ํƒœ๊ทธยท๊ณต๋ช… ์ฃผํŒŒ์ˆ˜๋ฅผ ๋งคํ•‘ํ•˜๋Š” ์ „์šฉ ๊ฐ์ • ๋ชจ๋ธ (`emotion/emotion_int8.onnx`)**:
- ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ์ • ํ‘œํ˜„์„ ์œ ๋„ํ•˜๊ธฐ ์œ„ํ•ด, ๋ฌธ์žฅ์˜ ์•ž๋’ค ๋งฅ๋ฝ(Context)๊ณผ ํ™”ํ–‰์„ ๋ถ„์„ํ•˜๋Š” **์ „์šฉ ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ชจ๋ธ(`emotion/emotion_int8.onnx`, KoELECTRA-small ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ ๋จธ๋ฆฌ ๋ถ„๋ฅ˜๊ธฐ)**์„ ์ง์ ‘ ํ•™์Šตํ•˜์—ฌ ๊ฒฐํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค.
- ์‚ฌ์šฉ์ž๊ฐ€ ๋Œ€๋ณธ์— ์ผ์ผ์ด ๊ฐ์ • ํƒœ๊ทธ๋ฅผ ๋‹ฌ์ง€ ์•Š์•„๋„, ๊ฐ์ • ๋ชจ๋ธ์ด ์ž…๋ ฅ ๋ฌธ์žฅ์˜ ๋ฌธ๋งฅ์„ ๋ถ„์„ํ•˜์—ฌ **๊ฐ์ • ๊ฐˆ๋ž˜(32์ข… ๊ฐ์ •ยท๋งํˆฌ ํƒœ๊ทธ ์ฒด๊ณ„), ๊ฐ์ • ์„ธ๊ธฐ(ํฌ๊ธฐ), ๋งํ•˜๊ธฐ ๋น ๋ฅด๊ธฐ(`๋А๋ฆฌ๊ฒŒ 1.08 / ๋ณดํ†ต 1.0 / ๋น ๋ฅด๊ฒŒ 0.93`), ์ˆจ๊ฒฐยท์Œ๋†’์ดยท์—๋„ˆ์ง€**๋ฅผ ์ž๋™์œผ๋กœ ํŒ๋ณ„ํ•˜๊ณ , ์ด๋ฅผ `emotion/emotion_resonance.json`์˜ **34์ฐจ์› ์‹ ๊ฒฝ๊ณต๋ช… ๋ฒกํ„ฐ(`res`)**๋กœ ์ฆ‰์‹œ ๋งคํ•‘ํ•˜์—ฌ ์ƒ์ƒํ•œ ๊ฐ์ • ์—ฐ๊ธฐ๋ฅผ ์ด๋Œ์–ด๋ƒ…๋‹ˆ๋‹ค.
2. **๊ฐ์ • ํƒœ๊ทธ ์ œ๋กœ๋“€๋ ˆ์ด์…˜ (`_ํƒœ๊ทธ์นธ๋–ผ๊ธฐ` Zero-Duration Scatter Compaction) โ€” ์†Œ๋ฆฌ๋Š” ์•ˆ ๋‚ด๊ณ  ๊ฐ์ •๋งŒ ์ฃผ์ž…**:
- ๋ฌธ์žฅ ๋จธ๋ฆฌ๋‚˜ ์ค‘๊ฐ„์— ์‚ฝ์ž…๋œ ๊ฐ์ •ยท๋ฐœ์„ฑยท์†Œ๋ฆฌ ํƒœ๊ทธ๋Š” ํ…์ŠคํŠธ ์ธ์ฝ”๋” ๋‚ด๋ถ€์—์„œ ์ฃผ๋ณ€ ๋ณธ๋ฌธ ์ž๋ชจ์— ๊ฐ์ • ์ฃผํŒŒ์ˆ˜์™€ ์–ต์–‘ยท๋ฐœ์„ฑ ํšจ๊ณผ๋งŒ ์ž…ํž™๋‹ˆ๋‹ค.
- ์ดํ›„ ๊ธธ์ด ์˜ˆ์ธก๊ธฐ(Duration Predictor)์™€ ๋””์ฝ”๋”๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์ง์ „, ๋ชจ๋ธ ๋‚ด๋ถ€์˜ **`_ํƒœ๊ทธ์นธ๋–ผ๊ธฐ`(Zero-Duration Scatter Compaction)**๊ฐ€ ํƒœ๊ทธ ์นธ์„ 100% ๋–ผ์–ด๋‚ด์–ด **ํ”„๋ ˆ์ž„ ์ˆ˜ 0(์ œ๋กœ๋“€๋ ˆ์ด์…˜, Zero-Duration)**์œผ๋กœ ์†Œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
- ๋•๋ถ„์— **ํƒœ๊ทธ ๊ธฐํ˜ธ ์ž์ฒด๊ฐ€ ๊ธ€์ž ์†Œ๋ฆฌ๋‚˜ ๊ธฐ๊ดดํ•œ ๊ตฐ์†Œ๋ฆฌ๋กœ ๋ฐœ์Œ๋˜๋Š” ์ผ์ด ์ ˆ๋Œ€ ์—†์œผ๋ฉฐ, ์˜ค์ง ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๊ฐ์ •๊ณผ ๋ฐœ์„ฑ ๊ณต๋ช… ํšจ๊ณผ๋งŒ** ์Œ์„ฑ์— ๋‚จ์Šต๋‹ˆ๋‹ค.
---
### 4. ์˜›๋ชจ์Œยท์˜›์ž์Œ(์˜›ํ•œ๊ธ€)์„ ํ™œ์šฉํ•œ ์™ธ๊ตญ์–ด ๋ฐœ์Œ ๊ตฌํ˜„ & ๋ฏธํ•™์Šต ์˜๋‹จ์–ด ํ•œ๊ตญ์‹ ๋ฐœ์Œ(์ฝฉ๊ธ€๋ฆฌ์‰ฌ) ์ฒ˜๋ฆฌ
1. **ํ›ˆ๋ฏผ์ •์Œ ์˜›์ž์Œยท์˜›๋ชจ์Œ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜๋ฅผ ํ†ตํ•œ ์˜์–ดยท์™ธ๊ตญ์–ด ์›์Œ ํ™•์žฅ**:
- ๊ฐœ๋ณ„ ๊ธ€์ž ๊ธฐ์–ต ์นธ(`self.emb`)์„ ์“ฐ์ง€ ์•Š๊ณ  `jamo_feat`(`544ร—104`) ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ํ–‰๋ ฌ๊ณผ ๊ณต์œ  ์„ ํ˜• ๋ณ€ํ™˜(`emb_f`)๋งŒ์œผ๋กœ ์†Œ๋ฆฌ๋ฅผ ์ง“๊ธฐ ๋•Œ๋ฌธ์—, ํ•™์Šต ์ฝ”ํผ์Šค์— ๋“ฑ์žฅ ๋นˆ๋„๊ฐ€ ์ ๊ฑฐ๋‚˜ ๋ฏธํ•™์Šต๋œ **ํ›ˆ๋ฏผ์ •์Œ์˜ ์˜›์ž์Œ โ€” ์ˆœ๊ฒฝ์Œ ํ”ผ์– `แ…—`(U+1157, ID 384 `[f]`), ์ˆœ๊ฒฝ์Œ ๋น„์ `แ„ซ`/`ใ…ธ`(U+112B, ID 385 `[v]`), ๋ฐ˜์น˜์Œ `แ…€`/`ใ…ฟ`(U+1140, ID 386 `[z]`), ์น˜๋‘์Œ ์‹œ์˜ท `แ„ผ`(U+113C, ID 387 `[ฮธ]`), ์ •์น˜์Œ ์‹œ์˜ท `แ„ฝ`(U+113D, ID 388 `[รฐ]`), ์—ฌ๋ฆฐํžˆ์— `ใ††`, ์˜›์ด์‘ `ใ†` โ€” ๋ฐ ์˜›๋ชจ์Œ(์•„๋ž˜์•„ `ใ†`, `ใ†Ž` ๋“ฑ)**๋„ ์ฃผํŒŒ์ˆ˜ ๊ฐ’๋งŒ์œผ๋กœ ์ฆ‰์‹œ ์ •ํ™•ํ•œ ์ŒํŒŒ ๊ธ€์ž ๋ฐœ์„ฑ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
- ์ด๋ฅผ ํ†ตํ•ด ํ˜„๋Œ€ ํ•œ๊ธ€ ํ‘œ๊ธฐ๋งŒ์œผ๋กœ๋Š” ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์—†๋Š” ์˜์–ดยท์™ธ๊ตญ์–ด์˜ ์œ ์„ฑ ๋งˆ์ฐฐ์Œ(`[v] โ†’ แ„ซ`, `[z] โ†’ แ…€`, `[รฐ] โ†’ แ„ฝ`)๊ณผ ๋ฏธ์„ธํ•œ ๋ชจ์Œ ์Œ๊ฐ€๋ฅผ ์˜›์ž์Œยท์˜›๋ชจ์Œ์˜ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ์กฐํ•ฉ์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
2. **๋ฏธํ•™์Šต ์˜๋‹จ์–ด(OOV English) ๋ฐ ์•ฝ์–ด์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์‹ ๋ฐœ์Œ(์ฝฉ๊ธ€๋ฆฌ์‰ฌ / ์•ŒํŒŒ๋ฒณ-ํ•œ๊ธ€ ์Œ์ฐจ) ์ฒ˜๋ฆฌ**:
- ๋ณ„๋„์˜ ์˜์–ด ์›์–ด๋ฏผ ์Œ์„ฑ ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ์ง์ ‘ ํ•™์Šตํ•˜์ง€ ์•Š์€ ๋ฏธํ•™์Šต ์˜๋‹จ์–ด๋‚˜ ์˜๋ฌธ ์•ฝ์–ด๊ฐ€ ์ž…๋ ฅ๋˜๋”๋ผ๋„ ๊นจ์ง€๊ฑฐ๋‚˜ ์นจ๋ฌตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
- CMU ๋ฐœ์Œ๊ธฐํ˜ธ(ARPAbet)์™€ ๊ตญ๋ฆฝ๊ตญ์–ด์› ์™ธ๋ž˜์–ด ํ‘œ๊ธฐ๋ฒ• ๋ฐ ์˜›๊ธ€์ž ์šธ๋ฆผ ๊ณต๋ช…(`แ„ซยทแ…€ยทแ„ฝ`)์„ ๊ฒฐํ•ฉํ•œ **ํ•œ๊ตญ์‹ ์˜์–ด ๋ฐœ์Œ(์ฝฉ๊ธ€๋ฆฌ์‰ฌ) ๋ณ€ํ™˜**์„ ํ†ตํ•ด `coffee โ†’ ์นดํ”ผ`, `thank โ†’ ์ƒํฌ`, `very โ†’ แ„ซแ…ฆ๋ฆฌ(๋ฒ ๋ฆฌ)`, `music โ†’ ๋ฎคแ…€แ…ตแ†จ(๋ฎค์ง)`์ฒ˜๋Ÿผ ํ•œ๊ตญ์–ด ํ™”์ž์—๊ฒŒ ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฐœ์Œ์œผ๋กœ ๋ฐ”๊พธ์–ด ์ฝ์œผ๋ฉฐ, ๋ฏธ๋“ฑ๋ก ์˜๋‹จ์–ด๋‚˜ ๋Œ€๋ฌธ์ž ์•ฝ์–ด(`SNS โ†’ ์—์Šค์—”์—์Šค`, `TV โ†’ ํ‹ฐแ„ซใ…ฃ`, `OK โ†’ ์˜ค์ผ€์ด`)๋Š” ์•ŒํŒŒ๋ฒณ ํ•œ๊ธ€ ์Œ์ฐจ ๊ทœ์น™์œผ๋กœ ๋งค๋„๋Ÿฝ๊ฒŒ ๋ฐœ์„ฑํ•ฉ๋‹ˆ๋‹ค.
---
### 5. ์ดˆ๊ฒฝ๋Ÿ‰ยท๋ฌด๋ฐœ์—ด ๊ตฌ๋™ & Epoch 880 EMA์˜ ํ˜ธํกยท์†๋„ยท๊ฐˆ๋ผ์ฝ๊ธฐ ํ˜์‹ 
1. **์ดˆ๊ฒฝ๋Ÿ‰ ๊ตฌ๋™ & ์Šค๋งˆํŠธํฐยทCPU ๋ฌด๋ฐœ์—ด ์„ค๊ณ„ (Ultra-Light & Near-Zero Heat)**:
- ๊ฑฐ๋Œ€ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฐ€์ง„ ์ž„๋ฒ ๋”ฉ ๊ธฐ์–ต์ธต์ด๋‚˜ ์—ฐ์‚ฐ ์ง‘์•ฝ์ ์ธ ํ’€๋ฐด๋“œ HiFi-GAN ๋ณด์ฝ”๋”๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , **์ž๋ชจยท์„ฑ์šฐยท๊ฐ์ • ์ฃผํŒŒ์ˆ˜์˜ ๊ฐ€๋ฒผ์šด ๋‹จ์ผ ์„ ํ˜• ํˆฌ์˜(`emb_f`, `emb_g_res`, `emb_res`) + 4-๋ถ€๋Œ€์—ญ MB-iSTFT-VITS(PQMF ์—ญํ‘ธ๋ฆฌ์— ๋ณ€ํ™˜) ๋””์ฝ”๋”**๋กœ ํŒŒํ˜•์„ ์ง์ ‘ ํ•ฉ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ(์Šค๋งˆํŠธํฐ)๋‚˜ ์ €์ „๋ ฅ CPU์—์„œ๋„ ์žฅ์‹œ๊ฐ„ ์—ฐ์† ๋‚ญ๋… ์‹œ **๋ฐœ์—ด์ด ๊ฑฐ์˜ ์—†์ด ๋น ๋ฅด๊ณ  ์พŒ์ ํ•˜๊ฒŒ ๊ตฌ๋™**๋ฉ๋‹ˆ๋‹ค.
2. **์–ด์ ˆ ๊ฒฝ๊ณ„(`eojeol_boundary`) ์™„์ „ ์ œ๊ฑฐ**:
- ๋„์–ด์“ฐ๊ธฐ(์–ด์ ˆ)๋งˆ๋‹ค ๋ˆ„์ ํ•ฉ(CumSum)์œผ๋กœ ์–ด์ ˆ ๋ฒˆํ˜ธ๋ฅผ ์„ธ์–ด ์ธ์ฝ”๋” ์–ดํ…์…˜์„ ๊ฐ€๋‘๋˜ ์–ด์ ˆ ๊ฒฝ๊ณ„ ๊ธฐ๋Šฅ์„ ๋ชจ๋ธ์—์„œ ์™„์ „ํžˆ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์–ด์ ˆ๋งˆ๋‹ค ๊ธฐ๊ณ„์ ์œผ๋กœ ๋š๋š ๋Š์–ด ์ฝ๊ฑฐ๋‚˜ ์–ด์ ˆ ๊ฒฝ๊ณ„์—์„œ ๋ฐœ์Œ์ด ํ”๋“ค๋ฆฌ๋˜ ํ˜„์ƒ์ด ์‚ฌ๋ผ์ง€๊ณ , ๋ฌธ์žฅ ์ „์ฒด์˜ ํ˜ธํก๊ณผ ์—ฐ์Œ์ด ๋ฌผ ํ๋ฅด๋“ฏ ์ด์–ด์ง‘๋‹ˆ๋‹ค.
3. **๋งํ•˜๊ธฐ ์†๋„ ํ‘œ์ค€ํ™” (`๋ฐ”ํƒ• ๋ฐฐ์† 0.85` + ๊ฐ์ •๋ณ„ ๋น ๋ฅด๊ธฐ `1.08 / 1.0 / 0.93`)**:
- ์ด ๋ชจ๋ธ์˜ ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ‘œ์ค€ ์ •์†(`base length_scale`)์€ **`0.85`** (`scales = [0.667, 0.85, 0.8]`)์ž…๋‹ˆ๋‹ค.
- ์—ฌ๊ธฐ์— KoELECTRA ๊ฐ์ • ๋ถ„๋ฅ˜๊ธฐ์˜ `๋น ๋ฅด๊ธฐ` ๋จธ๋ฆฌ ์˜ˆ์ธก๊ฐ’(`๋А๋ฆฌ๊ฒŒ: 1.08`, `๋ณดํ†ต: 1.0`, `๋น ๋ฅด๊ฒŒ: 0.93`, ํ™•์‹ ๋„ `โ‰ฅ 0.4`์ผ ๋•Œ ์ ์šฉ)์„ ๊ณฑํ•˜์—ฌ ์Šฌํ””ยทํšŒ์ƒ ์žฅ๋ฉด์—์„œ๋Š” ์ฐจ๋ถ„ํ•˜๊ฒŒ(`0.85 ร— 1.08 = 0.918`), ๊ธด๋ฐ•ยท๋ถ„๋…ธ ์žฅ๋ฉด์—์„œ๋Š” ์†๋„๊ฐ ์žˆ๊ฒŒ(`0.85 ร— 0.93 = 0.7905`) ์™„๊ธ‰์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.
4. **์ข…๊ฒฐํ˜•ยท์—ฐ๊ฒฐํ˜• ์–ด๋ฏธ์™€ ๋ฌธ์žฅ๋ถ€ํ˜ธ์—์„œ ๋Š์–ด ์ฝ๊ธฐ (`๊ฐˆ๋ผ์ฝ๊ธฐ`)**:
- ๊ธด ๋ฌธ์žฅ์„ ๊ธ€์ž ์ˆ˜๋‚˜ ์–ด์ ˆ ๋‹จ์œ„๋กœ ๋ฌด์ž‘์ • ์ž๋ฅด๋ฉด ์–ต์–‘์ด ํ† ๋ง‰ ๋‚˜๊ณ , ๋ฐ˜๋Œ€๋กœ ๊ธด ๋ฌธ๋‹จ์„ ํ†ต์งธ๋กœ ํ•œ ๋ฒˆ์— ํ•ฉ์„ฑํ•˜๋ฉด ๋’ค์ชฝ ํ˜ธํก์ด ๋ฌด๋„ˆ์ง€๊ฑฐ๋‚˜ ์ธ์ฝ”๋”๊ฐ€ ๊ฒฝ๊ณ„๋ฅผ ๋„˜์–ด ๋ฐœ์Œ์„ ํ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
- **๊ฐˆ๋ผ์ฝ๊ธฐ**๋Š” ์˜ค์ง **๋ฌธ์žฅ๋ถ€ํ˜ธ(`. ! ? , โ€ฆ ; โ€”`)** ๋ฐ **ํ•œ๊ตญ์–ด ์—ฐ๊ฒฐํ˜• ์–ด๋ฏธ(`~๊ณ `, `~๋ฉฐ`, `~๋ฉด์„œ`, `~๋Š”๋ฐ`, `~์ง€๋งŒ`, `~์„œ`[์กฐ์‚ฌ `์—์„œยท๊ป˜์„œยท๋กœ์„œ` ์ œ์™ธ], `~๋‹ˆ๊นŒ`, `~๋‹ค๊ฐ€`, `~๊ฑฐ๋‚˜`, `~๋“ ์ง€`, `~๋ ค๊ณ `, `~์ž๋งˆ์ž` ๋“ฑ โ€” ์ ‘์†๋ถ€์‚ฌ `๊ทธ๋ฆฌ๊ณ ยท๊ทธ๋ž˜์„œยทํ•˜์ง€๋งŒ` ๋“ฑ์€ ์ œ์™ธ)**์™€ **์ข…๊ฒฐํ˜• ์–ด๋ฏธ(`~๋‹ˆ๋‹ค`, `~์–ด์š”`, `~์•„์š”`, `~ํ•ด์š”`, `~๋Š”๋‹ค`, `~์—ˆ๋‹ค`, `~์•˜๋‹ค`, `~๋‹ค`, `~์š”`, `~๊นŒ`, `~๋„ค` ๋“ฑ)** ๋’ค์—์„œ๋งŒ ํ˜ธํก ๋‹จ์œ„๋กœ ๋‚˜๋ˆ„์–ด ๊ฐ๊ฐ ํ•ฉ์„ฑํ•ฉ๋‹ˆ๋‹ค.
- ๊ฐ ์กฐ๊ฐ ์•ž๋’ค์— ๋ถ™์ธ ๊ฒฝ๊ณ„ ๊ธฐํ˜ธ(`^ _`, `$`)์˜ ์†Œ๋ฆฌ๋Š” ๋ชจ๋ธ์ด ๋ฐ˜ํ™˜ํ•œ **`durations` ๊ธธ์ดํ‘œ๋กœ ์ •ํ™•ํžˆ ์ž˜๋ผ๋‚ด๊ณ **, ์กฐ๊ฐ ์‚ฌ์ด์— **`0.01์ดˆ(10ms)`**์˜ ๋ฏธ์„ธํ•œ ์ˆจํ‹ˆ์„ ๋‘์–ด ์ด์–ด ๋ถ™์ž„์œผ๋กœ์จ ํ•œ ํ˜ธํก์ฒ˜๋Ÿผ ๋งค๋„๋Ÿฝ๊ณ  ์„ ๋ช…ํ•œ ๋‚ญ๋…์„ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค.
---
## ๐Ÿ“ ํŒŒ์ผ ๊ตฌ์„ฑ
| ํŒŒ์ผ | ๋‚ด์šฉ |
|---|---|
| `piper-plus.onnx` | **๋‚ญ๋… ๋ชจ๋ธ (Epoch 880 EMA, 85.6MB)** โ€” ์–ด์ ˆ ๊ฒฝ๊ณ„ ์ œ๊ฑฐ ยท ๋œ์†Œ๋ฆฌ ๊ณผ์ž‰ ์ผ๋ฐ˜ํ™” ์–ต์ œ ยท ์™„์ „๊ณต๋ช…ํŒ |
| `piper-plus.onnx.json` | ๋ชจ๋ธ ์„ค์ • โ€” ํ‘œ๋ณธ์œจ(`22,050Hz`), ๊ธ€์žยท์˜›๊ธ€์žยทํƒœ๊ทธ ๋ฒˆํ˜ธํ‘œ(`phoneme_id_map`), ํ™”์ž ๋ฒˆํ˜ธํ‘œ(`speaker_id_map`), ๊ธฐ๋ณธ ์ถ”๋ก  ๋ฐฐ์†(`length_scale: 0.85`) |
| `resonance/jamo_features.json` | ์ž๋ชจ ์„ฑ์งˆํ‘œ (ํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธ ๊ธฐ๋ฐ˜ ๋ฐœ์Œ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ 80์นธ + ํƒœ๊ทธ ์˜ˆ๋น„ 24์นธ = 104์นธ) |
| `resonance/speaker_features.json` | ์„ฑ์šฐ ๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜ 31๊ฐ€์ง€ (ํ‰๊ท ยทํ‘œ์ค€ํŽธ์ฐจ ๋ฐ ํ™”์ž๋ณ„ z ์ ์ˆ˜) |
| `emotion/emotion_resonance.json` | ๊ฐ์ •ยท๋งํˆฌ ์ด๋ฆ„ โ†’ `res` ์‹ ๊ฒฝ๊ณต๋ช… ์ž…๋ ฅ ๊ฐ’ (๊ณต๋ช… ๋…ธ๋ธŒ 3์นธ + ๊ฐ์ • ๊ผฌ๋ฆฌ 15์นธ) |
| `emotion/emotion_int8.onnx` | ๋ฌธ๋งฅ ๊ธฐ๋ฐ˜ ๋ฌธ์žฅ ๊ฐ์ •ยท๋น ๋ฅด๊ธฐยท๋งํˆฌ ์ž๋™ ๋ถ„๋ฅ˜ ๋ชจ๋ธ (KoELECTRA-small v3 ๊ธฐ๋ฐ˜, int8, 14.6MB) |
| `emotion/vocab.txt` | ๊ฐ์ • ๋ชจ๋ธ ํ† ํฌ๋‚˜์ด์ € ์–ดํœ˜ (WordPiece) |
| `emotion/emotion_labels.json` ยท `emotion_labels_full.json` | ๊ฐ์ • ๋ชจ๋ธ ์ถœ๋ ฅ ๋จธ๋ฆฌ ๋ผ๋ฒจ ์ด๋ฆ„๊ณผ ์ฐจ๋ก€ |
| `emotion/emotion_config.json` | ๊ฐ์ • ๋ชจ๋ธ ์„ค์ • (`max_len` 192 ๋“ฑ) |
| `emotion/emotion_tokens.json` | ๊ฐ์ • ์ด๋ฆ„ยท๋งํˆฌ ๋‚ฑ๋ง โ†’ ๋ฒˆํ˜ธ ๋งคํ•‘ ํ‘œ |
---
## ๐Ÿš€ ๋‚ญ๋… ๋ชจ๋ธ ์“ฐ๋Š” ๋ฒ•
### ์ž…๋ ฅ ํ…์„œ ๊ทœ๊ฒฉ
| ์ด๋ฆ„ | ๋ชจ์–‘ | ๋œป |
|---|---|---|
| `input` | `[1, n]` int64 | NFD ์ž๋ชจ ๋ฒˆํ˜ธ์—ด (`^`, `_`, ์ž๋ชจ๋“ค, `$`) |
| `input_lengths` | `[1]` int64 | `n` |
| `scales` | `[3]` float32 | `[noise_scale, length_scale, noise_w]` โ€” **ํ‘œ์ค€ ์ •์†์€ `[0.667, 0.85, 0.8]`** (`length_scale = 0.85 ร— ๋น ๋ฅด๊ธฐ๋ฐฐ์ˆ˜`) |
| `sid` | `[1]` int64 | ํ™”์ž ๋ฒˆํ˜ธ (`speaker_id_map`) โ€” ๋ชจ๋ธ ๋‚ด๋ถ€ ์„ฑ์šฐ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ํ‘œ์˜ ํ–‰ ๋ฒˆํ˜ธ |
| `res` | `[1, 34]` float32 | ์‹ ๊ฒฝ๊ณต๋ช… ์ž…๋ ฅ. `[0:3]` ๊ณต๋ช… ๋…ธ๋ธŒ, `[19:34]` ๊ฐ์ • ๊ผฌ๋ฆฌ, `[3:19]` ๋ฏธ์‚ฌ์šฉ(0). ๋ชจ๋‘ 0์ด๋ฉด ๋ฌด๊ฐ์ • ๊ธฐ๋ณธ ๋ชฉ์†Œ๋ฆฌ |
์ถœ๋ ฅ `output`์€ 22,050Hz ํŒŒํ˜•(`-1.0 ~ 1.0`), `durations`๋Š” ๊ฐ ์ž…๋ ฅ ํ† ํฐ์˜ ํ”„๋ ˆ์ž„ ๊ธธ์ด(`hop_length = 256` ํ‘œ๋ณธ ๋‹จ์œ„, ํƒœ๊ทธ ์นธ์€ ์ œ๋กœ๋“€๋ ˆ์ด์…˜์œผ๋กœ ์ž๋™ ์ œ๊ฑฐ๋จ)์ž…๋‹ˆ๋‹ค.
### ํŒŒ์ด์ฌ ์ „์ฒด ์˜ˆ์ œ (๋ถˆ๊ทœ์น™ ๋ฐœ์Œยท์˜์–ด ์Œ์ฐจ + ๊ฐˆ๋ผ์ฝ๊ธฐ + 0.85 ๋ฐฐ์† & ๊ฐ์ • ๋น ๋ฅด๊ธฐ + ์‹ ๊ฒฝ๊ณต๋ช…)
```python
import json, re, unicodedata, wave
import numpy as np, onnxruntime as ort
cfg = json.load(open("piper-plus.onnx.json", encoding="utf-8"))
emo = json.load(open("emotion/emotion_resonance.json", encoding="utf-8"))
ids_of = cfg["phoneme_id_map"]
sr = cfg["audio"]["sample_rate"] # 22050
hop = cfg.get("hop_length", 256) # 256
# 1. ๋ถˆ๊ทœ์น™ ๋ฐœ์Œ ์‚ฌ์ „(๋œ์†Œ๋ฆฌ ๋“ฑ) ๋ฐ ์˜์–ด/์ฝฉ๊ธ€๋ฆฌ์‰ฌยท์˜›๊ธ€์ž(แ„ซยทแ…€ยทแ„ฝ) ์Œ์ฐจ ์˜ˆ์‹œ
IRREGULAR_DICT = {"์ธ๊ถŒ": "์ธ๊ฟ˜", "์ •๊ถŒ": "์ •๊ฟ˜", "์†๊ฐ€๋ฝ": "์†๊นŒ๋ฝ", "๋ฌผ๊ฐ€": "๋ฌผ๊นŒ", "์ธ๊ธฐ": "์ธ๋ผ"}
ENGLISH_DICT = {"coffee": "์นดํ”ผ", "thank": "์ƒํฌ", "very": "แ„ซแ…ฆ๋ฆฌ", "music": "๋ฎคแ…€แ…ตแ†จ", "SNS": "์—์Šค์—”์—์Šค", "TV": "ํ‹ฐแ„ซใ…ฃ"}
ALPHA_NAMES = {
"A": "์—์ด", "B": "๋น„", "C": "์‹œ", "D": "๋””", "E": "์ด", "F": "์—ํ”„", "G": "์ง€",
"H": "์—์ด์น˜", "I": "์•„์ด", "J": "์ œ์ด", "K": "์ผ€์ด", "L": "์—˜", "M": "์— ", "N": "์—”",
"O": "์˜ค", "P": "ํ”ผ", "Q": "ํ", "R": "์•„๋ฅด", "S": "์—์Šค", "T": "ํ‹ฐ", "U": "์œ ",
"V": "แ„ซแ…ด", "W": "๋”๋ธ”์œ ", "X": "์—‘์Šค", "Y": "์™€์ด", "Z": "แ…€แ…ต",
}
def normalize_text(text):
for k, v in sorted(IRREGULAR_DICT.items(), key=lambda x: -len(x[0])):
text = text.replace(k, v)
def repl_en(m):
w = m.group(0)
if w in ENGLISH_DICT:
return ENGLISH_DICT[w]
if w.lower() in ENGLISH_DICT:
return ENGLISH_DICT[w.lower()]
return "".join(ALPHA_NAMES.get(c.upper(), c) for c in w)
return re.sub(r"[A-Za-z]+", repl_en, text)
# 2. ๊ฐˆ๋ผ์ฝ๊ธฐ: ๋ฌธ์žฅ๋ถ€ํ˜ธ ๋ฐ ํ•œ๊ตญ์–ด ์—ฐ๊ฒฐํ˜•ยท์ข…๊ฒฐํ˜• ์–ด๋ฏธ ์ž๋ฆฌ์—์„œ๋งŒ ํ˜ธํก ๋ถ„ํ• 
CONN_EOMI = ("๋ฉด์„œ", "๋Š”๋ฐ", "์ง€๋งŒ", "๋‹ˆ๊นŒ", "๋‹ค๊ฐ€", "๊ฑฐ๋‚˜", "๋“ ์ง€", "๋ ค๊ณ ", "์ž๋งˆ์ž", "๊ณ ", "๋ฉฐ", "์„œ")
FINAL_EOMI = (
"๋‹ˆ๋‹ค", "์–ด์š”", "์•„์š”", "์—์š”", "์˜ˆ์š”", "ํ•ด์š”", "์„ธ์š”", "๋„ค์š”", "๊ตฐ์š”", "์ง€์š”",
"๋Š”๋‹ค", "์—ˆ๋‹ค", "์•˜๋‹ค", "์˜€๋‹ค", "๊ฒ ๋‹ค", "ํ•œ๋‹ค", "ํ–ˆ๋‹ค", "๋‹ค", "์š”", "๊นŒ", "๋„ค",
)
CONJ_ADV = {"๊ทธ๋ฆฌ๊ณ ", "๊ทธ๋Ÿฌ๊ณ ", "๊ทธ๋ž˜์„œ", "๊ทธ๋Ÿฌ๋‹ˆ๊นŒ", "ํ•˜์ง€๋งŒ", "๊ทธ๋ ‡์ง€๋งŒ", "๊ทธ๋Ÿฌ๋‹ค๊ฐ€", "๊ทธ๋Ÿฌ๋ฉด์„œ"}
PUNCT_CHARS = set(".!?,โ€ฆ;โ€”โ€•โ€“ใ€‚๏ผ๏ผŸ")
def should_split_eojeol(eojeol):
if eojeol in CONJ_ADV:
return False
if any(ch in PUNCT_CHARS for ch in eojeol):
return True
hangul = re.sub(r"[^๊ฐ€-ํžฃ]+$", "", eojeol)
if len(hangul) < 2:
return False
for e in CONN_EOMI:
if hangul.endswith(e) and len(hangul) > len(e):
if e == "์„œ" and hangul[-2] in ("์—", "๊ป˜", "๋กœ"): # ์กฐ์‚ฌ(์—์„œยท๊ป˜์„œยท๋กœ์„œ) ์ œ์™ธ
continue
return True
for e in FINAL_EOMI:
if hangul.endswith(e) and len(hangul) > len(e):
return True
return False
def split_by_eomi(text):
words = text.strip().split()
if len(words) <= 1:
return [text.strip()] if text.strip() else []
chunks, cur = [], []
for i, w in enumerate(words):
cur.append(w)
if i < len(words) - 1 and should_split_eojeol(w):
chunks.append(" ".join(cur))
cur = []
if cur:
chunks.append(" ".join(cur))
return chunks
# 3. NFD ์ž๋ชจ ๋ฒˆํ˜ธ์—ด ๋ฐ ์‹ ๊ฒฝ๊ณต๋ช…(res) ์ƒ์„ฑ
def to_ids(text):
ids = ids_of["^"] + ids_of["_"]
for ch in unicodedata.normalize("NFD", text):
if ch in ids_of:
ids += ids_of[ch] + ids_of["_"]
return ids + ids_of["$"]
def res_for(emotion=None, strength=1.0):
r = np.zeros((1, emo["res"]["๊ธธ์ด"]), dtype=np.float32)
if emotion:
key = str(emo["๊ฐ์ •"][emotion]) # ๋ฌด๊ฐ์ •(250)์€ ํ‘œ์— ์—†์Œ โ†’ 0
knob, tail = emo["res"]["๋…ธ๋ธŒ"], emo["res"]["๊ฐ์ •๊ผฌ๋ฆฌ"]
r[0, knob["์ž๋ฆฌ"]:knob["์ž๋ฆฌ"] + knob["์นธ"]] = np.array(emo["๋…ธ๋ธŒํ‘œ"].get(key, [0.0] * knob["์นธ"])) * knob["๋ฐฐ์ˆ˜"] * strength
r[0, tail["์ž๋ฆฌ"]:tail["์ž๋ฆฌ"] + tail["์นธ"]] = np.array(emo["๊ผฌ๋ฆฌํ‘œ"].get(key, [0.0] * tail["์นธ"])) * tail["๋ฐฐ์ˆ˜"] * strength
return r
# 4. ํ‘œ์ค€ ์ •์†(0.85) + ๊ฐ์ •๋ณ„ ๋น ๋ฅด๊ธฐ(1.08 / 1.0 / 0.93, ํ™•์‹ ๋„ >= 0.4) + ๊ฐˆ๋ผ์ฝ๊ธฐ(0.01์ดˆ ์ด์Œ) ํ•ฉ์„ฑ
BASE_SPEED = 0.85
SPEED_MULT = {"๋А๋ฆฌ๊ฒŒ": 1.08, "๋ณดํ†ต": 1.0, "๋น ๋ฅด๊ฒŒ": 0.93}
sess = ort.InferenceSession("piper-plus.onnx", providers=["CPUExecutionProvider"])
def speak(text, speaker="nangdok", emotion=None, strength=1.0, speed_label="๋ณดํ†ต", speed_conf=1.0, gap_sec=0.01):
text = normalize_text(text)
chunks = split_by_eomi(text)
length_scale = BASE_SPEED * (SPEED_MULT.get(speed_label, 1.0) if speed_conf >= 0.4 else 1.0)
scales = np.array([0.667, length_scale, 0.8], dtype=np.float32)
sid = np.array([cfg["speaker_id_map"][speaker]], dtype=np.int64)
res = res_for(emotion, strength)
gap = np.zeros(int(sr * gap_sec), dtype=np.float32)
head_len = len(ids_of["^"] + ids_of["_"])
tail_len = len(ids_of["$"])
pieces = []
for idx, chunk in enumerate(chunks):
chunk_str = chunk + (" " if idx < len(chunks) - 1 else "")
x = np.array([to_ids(chunk_str)], dtype=np.int64)
out, durs = sess.run(None, {
"input": x,
"input_lengths": np.array([x.shape[1]], dtype=np.int64),
"scales": scales,
"sid": sid,
"res": res,
})
wav = out.squeeze()
d = durs.squeeze()
# ์กฐ๊ฐ ๊ฒฝ๊ณ„์— ๋ง๋ถ™์ธ ๋จธ๋ฆฌ(^ _)ยท๊ผฌ๋ฆฌ($) ๊ตฌ๊ฐ„์„ durations๋กœ ์ •๋ฐ€ํ•˜๊ฒŒ ์ž˜๋ผ๋ƒ„
trim_head = 0 if idx == 0 else int(round(float(d[:head_len].sum()) * hop))
trim_tail = len(wav) if idx == len(chunks) - 1 else int(round(float(d[:-tail_len].sum()) * hop))
trim_head = max(0, min(trim_head, len(wav)))
trim_tail = max(trim_head, min(trim_tail, len(wav)))
if idx > 0 and len(gap) > 0:
pieces.append(gap)
pieces.append(wav[trim_head:trim_tail])
return np.concatenate(pieces) if pieces else np.zeros(0, dtype=np.float32)
# ๋ฌด๊ฐ์ • ๊ธฐ๋ณธ ๋‚ญ๋… (์–ด๋ฏธยท๋ฌธ์žฅ๋ถ€ํ˜ธ ๊ฐˆ๋ผ์ฝ๊ธฐ + 0.85 ์ •์†)
audio = speak("์•ˆ๋…•ํ•˜์„ธ์š”. ์˜ค๋Š˜์€ ๋‚ ์”จ๊ฐ€ ์ฐธ ์ข‹์•˜๊ณ , coffee ํ•œ ์ž” ๋งˆ์‹œ๋ฉฐ ์‚ฐ์ฑ…ํ•˜๊ธฐ์—๋„ ์•„์ฃผ ์ข‹๋„ค์š”.")
# ๊ฐ์ • ์‹ ๊ฒฝ๊ณต๋ช… + ๋น ๋ฅด๊ธฐ ๊ฒฐํ•ฉ ๋‚ญ๋…
angry = speak("๋‹น์žฅ ์—ฌ๊ธฐ์„œ ๋‚˜๊ฐ€!", speaker="seungwoo", emotion="๋ถ„๋…ธ", speed_label="๋น ๋ฅด๊ฒŒ")
with wave.open("out.wav", "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr)
w.writeframes((np.clip(audio, -1, 1) * 32767).astype(np.int16).tobytes())
```
### ํ™”์ž ์•ˆ๋‚ด (`speaker_id_map`)
`speaker_id_map`์— 26์นธ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ญ๋…(ํ•ด์„ค)์€ `nangdok`(1), ์ฃผ์š” ์„ฑ์šฐ ๋ชฉ์†Œ๋ฆฌ๋Š” `seungwoo`(0) ยท `joyjjang`(2) ยท `seungchul`(3) ยท `์†Œ๋ผ`(6) ยท `์ง€์šฐ`(23) ๋“ฑ์ž…๋‹ˆ๋‹ค. ๊ฐ ํ™”์ž๊ฐ€ ์–ด๋–ค 31์ฐจ์› ๋ฐœ์„ฑ ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜๋กœ ๋“ค์–ด๊ฐ€๋Š”์ง€๋Š” `resonance/speaker_features.json`์— ์ˆ˜๋ก๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
## ๋ฌธ๋งฅ ๊ธฐ๋ฐ˜ ์ „์šฉ ๊ฐ์ • ๋ชจ๋ธ ์“ฐ๋Š” ๋ฒ• (`emotion/emotion_int8.onnx`)
์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜๋™์œผ๋กœ ๊ฐ์ • ํƒœ๊ทธ๋ฅผ ์ž…๋ ฅํ•˜์ง€ ์•Š์•„๋„ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ์ • ํ‘œํ˜„์ด ์œ ๋„๋˜๋„๋ก ์ง์ ‘ ํ•™์Šตํ•œ ๋ฌธ๋งฅ ๊ธฐ๋ฐ˜ ๊ฐ์ • ๋ถ„๋ฅ˜ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค:
- **ํ† ํฌ๋‚˜์ด์ €**: `emotion/vocab.txt`์˜ WordPiece (KoELECTRA-small v3 ๋™์ผ). `[CLS] ๋ฌธ์žฅ [SEP]`, ์ตœ๋Œ€ 192 ํ† ํฐ. ์•ž๋’ค ๋ฌธ์žฅ ๋งฅ๋ฝ์„ ํ•จ๊ป˜ ์ค„ ๋•Œ๋Š” `tokenizer(๋งฅ๋ฝ๊ธ€, ๋ฌธ์žฅ, truncation="only_first", max_length=192)` ํ˜•์‹(์˜ˆ: `"์•ž: ๋น„ํƒ„. ๋’ค: ๋ถ„๋…ธ."`)์œผ๋กœ ๋„ฃ์Šต๋‹ˆ๋‹ค.
- **์ž…๋ ฅ**: `input_ids`, `attention_mask`, `token_type_ids` (`[b, t]` int64).
- **์ถœ๋ ฅ ๋จธ๋ฆฌ** (`emotion/emotion_labels_full.json` ์ฐธ์กฐ):
- ๋ฌธ์žฅ ๋จธ๋ฆฌ `[b, C]`: `๊ฐ์ •`(16) ยท `ํฌ๊ธฐ`(3) ยท `๋น ๋ฅด๊ธฐ`(3: `๋А๋ฆฌ๊ฒŒ`, `๋ณดํ†ต`, `๋น ๋ฅด๊ฒŒ`) ยท `์ˆจ๊ฒฐ`(4) ยท `๋งํˆฌ`(46)
- ํ† ํฐ ๋จธ๋ฆฌ `[b, t, C]`: `๋ผ์šธํƒœ๊ทธ`(27) ยท `๊ตฌ๊ฐ„`(11)
- **์ž๋™ ๋งคํ•‘ ํ๋ฆ„**: `๊ฐ์ •` ๋ฐ `๋งํˆฌ` ๋จธ๋ฆฌ๊ฐ€ ๋ฌธ๋งฅ์—์„œ ์ถ”๋ก ํ•œ ๊ฐ์ •ยท๋งํˆฌ ๋ผ๋ฒจ์„ `emotion_resonance.json`์„ ํ†ตํ•ด 34์ฐจ์› `res` ๊ณต๋ช… ๋ฒกํ„ฐ๋กœ ์ž๋™ ๋ณ€ํ™˜ํ•˜๊ณ , `๋น ๋ฅด๊ธฐ` ๋จธ๋ฆฌ์˜ ํ™•์‹ ๋„๊ฐ€ `0.4` ์ด์ƒ์ด๋ฉด `SPEED_MULT` ๋ฐฐ์ˆ˜(`๋А๋ฆฌ๊ฒŒ: 1.08`, `๋ณดํ†ต: 1.0`, `๋น ๋ฅด๊ฒŒ: 0.93`)๋ฅผ ๊ธฐ๋ณธ ๋ฐฐ์† `0.85`์— ๊ณฑํ•ด `scales`์˜ `length_scale`๋กœ ์ž๋™ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.
## โš ๏ธ ๋ผ์ด์„ ์Šค ๋ฐ ์ €์ž‘์ž ํ‘œ์‹œ ์˜๋ฌด (License, Attribution & Citation)
- **์‘์šฉยทํŒŒ์ƒยท์žฌ๊ฐœ๋ฐœ ์‹œ ์›์ €์ž‘์ž ์ด๋‹ˆ์…œ(`watchpen`) ๋ฐ ๊ณต์‹ ๊นƒํ—ˆ๋ธŒ ์ฃผ์†Œ ์ฒจ๋ถ€ ์˜๋ฌด**:
- ๋ณธ ๋ชจ๋ธ(`piper-plus`), 3๋Œ€ ์‹ ๊ฒฝ๊ณต๋ช… ์ฃผํŒŒ์ˆ˜ ํ‘œ(`jamo_features.json`, `speaker_features.json`, `emotion_resonance.json`), ์ž๋ชจ ์ŒํŒŒ ๋ฐœ์„ฑ ๊ตฌ์กฐ, ์ œ๋กœ๋“€๋ ˆ์ด์…˜ ๊ฐ์ • ํƒœ๊ทธ ์ฒด๊ณ„, ๋˜๋Š” ํฌํ•จ๋œ ์ฝ”๋“œ/๊ฐ€์ค‘์น˜๋ฅผ **์‘์šฉํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ยทํŒŒ์ƒยท์žฌ๊ฐœ๋ฐœํ•˜์—ฌ ๋ฐฐํฌ/๊ณต๊ฐœํ•  ๊ฒฝ์šฐ, ๋ฐ˜๋“œ์‹œ ์›์ €์ž‘์ž ์ด๋‹ˆ์…œ(`watchpen`)๊ณผ ๊ณต์‹ ๊นƒํ—ˆ๋ธŒ ์ฃผ์†Œ(`https://github.com/wacthpen/piper-plus`)๋ฅผ ํ•จ๊ป˜ ์ฒจ๋ถ€(๋ช…์‹œ)**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- **Mandatory Attribution Requirement (English)**: *If you adapt, modify, fine-tune, or redevelop this model (`piper-plus`), its neural resonance frequency tables, its Jamo acoustic vocalization architecture, or its codebase, you **MUST** explicitly credit the original author's handle/initials (**`watchpen`** / GitHub: `wacthpen`, Hugging Face: `watchpen`) **AND** attach the official GitHub repository URL (**`https://github.com/wacthpen/piper-plus`**) in your documentation, model card, and derivative works.*
- ์‹ค์ œ ์‚ฌ๋žŒ ๋ชฉ์†Œ๋ฆฌ๋ฅผ ์‚ฌ์นญํ•˜๊ฑฐ๋‚˜ ์†์ด๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”.
- ๊ฐ์ • ๋ถ„๋ฅ˜ ๋ชจ๋ธ์˜ ๊ธฐ๋ฐ˜ ๋ชจ๋ธ์€ [monologg/koelectra-small-v3-discriminator](https://huggingface.co/monologg/koelectra-small-v3-discriminator)์ž…๋‹ˆ๋‹ค.
### ์ธ์šฉ (Citation / BibTeX)
```bibtex
@misc{watchpen2026piperplus,
author = {watchpen},
title = {Piper Plus: Korean Neural-Resonance TTS with Fortis-Overgeneralization Suppression, Emotion-Expressive Neural Resonance & Acoustic Jamo Vocalization},
year = {2026},
publisher = {GitHub / Hugging Face},
url = {https://github.com/wacthpen/piper-plus},
howpublished = {\url{https://github.com/wacthpen/piper-plus}, \url{https://huggingface.co/watchpen/piper-plus}}
}
```
---
## English Summary
**Piper Plus (Epoch 880 EMA)** is a Korean multi-speaker neural text-to-speech model in the Piper (VITS) family exported to ONNX (22,050 Hz), engineered around four core innovations:
1. **Suppression of Excessive Fortis / Tensification Generalization**:
- Traditional Korean TTS models allow the text encoder's embedding/context memory layer (`self.emb`) to over-generalize tensification from training words (e.g., learning fortis `[kk]` from `์ธ๊ถŒ [inkkwon]` and falsely tensifying lax consonants across ordinary words like `์†๊ถŒ`).
- **Piper Plus structurally bypasses the fortis-contaminated embedding memory layer (`jamo_feature_mode = "๋Œ€์ฒด"`, `x = torch.where(jamo_mask, emb_f(jamo_feat[x]), x)`)**, driving synthesis directly from 104-dimensional measured acoustic resonance vectors, paired with a **Korean Tenseness Discriminator (`tense_clf=True`, `tense_plain_weight=24.0` penalty for false tensification)** and **chest-resonance weight suppression (`res_band_weights='0,2,4'`)**. Lax consonants remain pure, while genuine irregular pronunciations are handled deterministically via a **1:1 Irregular Pronunciation Dictionary**.
2. **Emotion-Expressive Neural Resonance, Context-Aware Auto Emotion Model & Zero-Duration Tags**:
- Emotions are injected via a 34-dimensional **Neural Resonance vector (`res`)** combining 3 vocal-tract resonance knobs (vocal-tract scale, chest resonance, actor's formant) and 15 measured voice-quality shifts (eGeMAPS 14 parameters + nasal coupling `A1-P0`).
- A custom-trained **Context-Aware Korean Emotion Classifier (`emotion/emotion_int8.onnx`, KoELECTRA-small int8)** automatically analyzes sentence context to map emotion tags, speaking rate (`base length_scale = 0.85` ร— `1.08 / 1.0 / 0.93`), and resonance parameters without manual tagging.
- Any inline emotion/phonation tags impart their acoustic resonance onto surrounding Jamo inside the encoder and are then stripped with **Zero-Duration (`_ํƒœ๊ทธ์นธ๋–ผ๊ธฐ` scatter compaction, 0 frames)** before the Duration Predictor โ€” **producing pure emotional expression with zero spoken tag artifacts**.
3. **Acoustic Character Vocalization via Direct Jamo Learning & *Hunminjeongeum Haeryebon* Mathematical Realization**:
- **49 Symbolic Columns Zeroed Out โ€” Only 31 Measured Acoustic Frequencies Active**: In `resonance/jamo_features.json` (80 base columns + 24 tag columns = 104 columns), all 49 symbolic/one-hot indicator columns (`letter ID`, `place`, `manner`, `tenseness`) are strictly set to `0.0` (`assert all(v == 0 ...)`), and `models.py` replaces `self.emb(x)` 100% with `self.emb_f(jamo_feat[x])`. The neural network never sees symbolic letter identities โ€” it synthesizes speech solely from **31 physical acoustic frequency measurements**:
- **Initial Consonants (16 cols โ€” `ๅˆ่ฒๆœ‰็™ผๅ‹•ไน‹็พฉ` & Five Sounds / Stroke Addition)**: Burst frequency/peak/spread, low-mid/mid-high energy ratios, aspiration duration/spectrum, intensity, F2/F3 locus shifts, voicing F1, and post-consonantal pitch. Initial `ใ…‡` is explicitly populated with measured glottal initiation frequencies per Haeryebon's `ใ€Œใ…‡ใ€‚ๅ–‰้Ÿณใ€‚ๅฆ‚ๆฌฒๅญ—ๅˆ็™ผ่ฒใ€` so consecutive vowels (`์•„์•„`, `์œผ์•—`) never fall back to `self.emb` or collapse.
- **Medial Vowels (7 cols โ€” Heaven-Earth-Human `ใ†, ใ…ก, ใ…ฃ` & Diphthong Trajectories)**: Formant frequencies at 10% (`F1ยทF2ยทF3 start`) and 75% (`F1ยทF2ยทF3 end`) plus vowel duration capture both monophthongs and compound diphthong trajectories.
- **Final Codas (8 cols + 16 Liaison cols โ€” `็ต‚่ฒๅพฉ็”จๅˆ่ฒ`, `ๅ…ซๅญ—ๅฏ่ถณ็”จ`, `ไธๆทธไธๆฟ`, `่‡ชๅทฆ่€Œๅณ` in `ํ•ด๋ก€๋ณธ๋ฐ›์นจ.py`)**: At Epoch 632, naive median ratios caused `๊ฐ„ยท๊ฐยท๊ฐ•` to collapse into `[์นธ]`. Implementing Haeryebon's five coda laws โ€” pairing homorganic stops & nasals (`ใ„ฑยทใ†` 1,503 Hz, `ใ„ดยทใ„ท` 1,875 Hz, `ใ„น` 1,813 Hz, `ใ…ยทใ…‚` 559 Hz) for F2/F3 locus targets (`z2์ง, z3์ง`), mapping the 8-coda neutralization (`ๅ…ซๅญ—ๅฏ่ถณ็”จ`), assigning voicing & nasal anti-resonance zeros (`๋ฐ›์นจ์ฝง์†Œ๋ฆฌ๊ณจkHz`: `ใ„ด 1.38 / ใ… 1.30 / ใ…‡ 1.73 kHz`) exclusively to `ไธๆทธไธๆฟ` sonorants (`ใ„ดยทใ„นยทใ…ยทใ…‡`), and encoding double codas (`ใ„ณ~ใ…„`) left-to-right (`่‡ชๅทฆ่€Œๅณ`: 1st consonant closure locus โ†’ 2nd consonant locus + prevocalic liaison in the 16 initial columns) โ€” cleanly separated coda distances (`ใ„ดโ†”ใ…‡` `0.98 โ†’ 1.95`, `ใ„ฑโ†”ใ…‚` `1.45 โ†’ 3.72`).
- **Archaic Hangul Jamo for Foreign/English Sounds & Konglish OOV Handling**: Archaic Hangul consonants (`แ…—` `[f]`, `แ„ซ`/`ใ…ธ` `[v]`, `แ…€`/`ใ…ฟ` `[z]`, `แ„ผ` `[ฮธ]`, `แ„ฝ` `[รฐ]`, IDs `384~388`) are populated with measured English fricative frequencies in the exact same 16 initial-consonant columns and vocalized through the single shared `emb_f` projection, while unseen English words and acronyms are seamlessly converted into natural Korean-style pronunciation (Konglish / ARPAbet-to-Hangul + alphabet vocalization).
4. **Ultra-Light, Near-Zero Heat on Mobile/CPU & Prosodic Ending Splitting (`๊ฐˆ๋ผ์ฝ๊ธฐ`)**:
- Combining single linear resonance projections (`emb_f`, `emb_g_res`, `emb_res`) with a **4-subband MB-iSTFT-VITS (PQMF) decoder** makes inference extremely lightweight, running in real time on mobile phones and standard CPUs with **near-zero thermal output**.
- Removes mechanical word-boundary (`eojeol_boundary`) CumSum breaks and splits long sentences only at punctuation and Korean connective/sentence-final verb endings (`split_by_eomi`), trimming boundary tokens precisely via `durations` and joining chunks with a `10 ms (0.01 s)` breath gap.
---
## ๐Ÿ“œ ํ—Œ์ • ๋ฐ ๊ธฐ๋… (Dedication)
> **"์˜ฌํ•ด๋Š” ํ›ˆ๋ฏผ์ •์Œ ๋ฐ˜ํฌ 580๋Œ์ž…๋‹ˆ๋‹ค. ์„ธ์ข…๋Œ€์™•์€ ์œ„๋Œ€ํ•ฉ๋‹ˆ๋‹ค. ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธใ€์„ ๋ณธ๋–  ๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜๋ฅผ ๋งž์ถ”์–ด ์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์™„์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค."**
>
> ๋ฐœ์„ฑ ๊ธฐ๊ด€์˜ ๋ชจ์–‘๊ณผ ์†Œ๋ฆฌ ์ด์น˜๋ฅผ ๋ณธ๋œฌ ใ€Žํ›ˆ๋ฏผ์ •์Œ ํ•ด๋ก€๋ณธ(่จ“ๆฐ‘ๆญฃ้Ÿณ ่งฃไพ‹ๆœฌ)ใ€์˜ ์œ„๋Œ€ํ•œ ์ œ์ž ์›๋ฆฌ๋ฅผ ํ˜„๋Œ€ ์‹ ๊ฒฝ๋ง ์ŒํŒŒ ๋ฐœ์„ฑ ์ฃผํŒŒ์ˆ˜๋กœ ๊ณ ์Šค๋ž€ํžˆ ์˜ฎ๊ฒจ ๋งž์ถ”๋ฉฐ, ํ›ˆ๋ฏผ์ •์Œ ๋ฐ˜ํฌ 580๋Œ(1446~2026)์„ ๋งž์•„ ์„ธ์ข…๋Œ€์™•์˜ ์• ๋ฏผ ์ •์‹ ๊ณผ ๊ณผํ•™์  ์„ฑ์Œ(่ฒ้Ÿณ) ์ฒ ํ•™์— ๊นŠ์€ ๊ฒฝ์˜๋ฅผ ํ‘œํ•ฉ๋‹ˆ๋‹ค.
>
> *This year marks the 580th anniversary of the promulgation of Hunminjeongeum (1446โ€“2026). King Sejong the Great is truly great. We conducted this project by aligning vocal resonance frequencies modeled after the Hunminjeongeum Haeryebon (่จ“ๆฐ‘ๆญฃ้Ÿณ ่งฃไพ‹ๆœฌ).*