{"_id":"@dannyyoo/korean-tts","_rev":"7-5766a96d9d5b5af00a2c84529d20a008","name":"@dannyyoo/korean-tts","dist-tags":{"latest":"1.2.8"},"versions":{"1.2.2":{"name":"@dannyyoo/korean-tts","version":"1.2.2","license":"MIT","_id":"@dannyyoo/korean-tts@1.2.2","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"homepage":"https://github.com/dyoo/korean-tts#readme","bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"dist":{"shasum":"9361d0260b2bde09c97fb74798aa97bbbe8f633f","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.2.tgz","fileCount":11,"integrity":"sha512-3BG8rhwLMsMtiAsHo+s5RH5LQb2ll23VKJ18Zrv8j5+aNGcIb0rIKe56a9S4JKE5pIxcbdOFPnC2zRm3RXaCHw==","signatures":[{"sig":"MEQCIEbQvmybZuKfMDDjVzAqsZutIFlXxwvE7o57DZ1N/I75AiAwl1/Xa8Thnsb9PVSzYZQaTIC8a9Q4GSpVIxtwvoZTQw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118218},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"35579b8f109177350d77b4347241fccc58da6365","scripts":{"dev":"vite --port 5173","test":"node --experimental-strip-types --test test/**/*.test.ts","build":"npm run build:lib","preview":"vite preview","build:lib":"tsc --noEmit && vite build --mode lib","typecheck":"tsc --noEmit","build:demo":"tsc --noEmit && vite build","prepublishOnly":"npm run typecheck && npm run build:lib"},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"repository":{"url":"git+https://github.com/dyoo/korean-tts.git","type":"git"},"_npmVersion":"11.16.0","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","directories":{},"_nodeVersion":"24.18.1","dependencies":{"kokoro-js":"^1.2.1","@huggingface/transformers":"^3.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.2.1","typescript":"^7.0.2","@types/node":"^26.2.0","vite-plugin-dts":"^5.0.3","@typescript/typescript6":"^6.0.2"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/korean-tts_1.2.2_1787361773904_0.06481887895387684","host":"s3://npm-registry-packages-npm-production"}},"1.2.3":{"name":"@dannyyoo/korean-tts","version":"1.2.3","license":"MIT","_id":"@dannyyoo/korean-tts@1.2.3","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"homepage":"https://github.com/dyoo/korean-tts#readme","bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"dist":{"shasum":"46476e4cecde5c442b77d28240244d3c2cd1f6d8","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.3.tgz","fileCount":11,"integrity":"sha512-9LQwkuFwqXHv+qP5erMX5OXREKutT3sEbyF1//F27m2KciZpN9ArgNR7yky/WrXBd/OYE1kVvWPDXpKlzKLMsw==","signatures":[{"sig":"MEYCIQDHHt91ylFTCG/75DJUYqZaBe90wQTqWT4grEQY2u8IQAIhALaf7xuOHabD4fFdKe7YrnyX2ieGGfU0Tri3Bob9zSUG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118242},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"d63f48716ca6f44084419902c8bae1f32ddc5d85","scripts":{"dev":"vite --port 5173","test":"node --experimental-strip-types --test test/**/*.test.ts","build":"npm run build:lib","preview":"vite preview","build:lib":"tsc --noEmit && vite build --mode lib","typecheck":"tsc --noEmit","build:demo":"tsc --noEmit && vite build","prepublishOnly":"npm run typecheck && npm run build:lib"},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"repository":{"url":"git+https://github.com/dyoo/korean-tts.git","type":"git"},"_npmVersion":"11.16.0","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","directories":{},"_nodeVersion":"24.18.1","dependencies":{"kokoro-js":"^1.2.1","@huggingface/transformers":"^3.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.2.1","typescript":"^7.0.2","@types/node":"^26.2.0","vite-plugin-dts":"^5.0.3","@typescript/typescript6":"^6.0.2"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/korean-tts_1.2.3_1787362332919_0.5281194325174825","host":"s3://npm-registry-packages-npm-production"}},"1.2.4":{"name":"@dannyyoo/korean-tts","version":"1.2.4","license":"MIT","_id":"@dannyyoo/korean-tts@1.2.4","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"homepage":"https://github.com/dyoo/korean-tts#readme","bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"dist":{"shasum":"13518500cc9a58f9847183991f65c94c82d9dbc6","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.4.tgz","fileCount":11,"integrity":"sha512-pP4Jt6hoykbkei8Yavv6f+BjQPKbwtr/b8GhYG2l+kvxg8dHUbHdVNbUpXvSVbNBFAfLFlnlP3WCVpPsI9XX+w==","signatures":[{"sig":"MEYCIQDXLdC+8VolaLp0wbmDuGhVk/rND6ysC8S4pZdSjCzmkgIhAKjvxxt+E2kWac4eMY7vyW6owLPhoHgRCMTwXPQYHDq/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119039},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"eb547fddbca1f7553b95d1eeb8d456bd50d1acc3","scripts":{"dev":"vite --port 5173","test":"node --experimental-strip-types --test test/**/*.test.ts","build":"npm run build:lib","preview":"vite preview","build:lib":"tsc --noEmit && vite build --mode lib","typecheck":"tsc --noEmit","build:demo":"tsc --noEmit && vite build","prepublishOnly":"npm run typecheck && npm run build:lib"},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"repository":{"url":"git+https://github.com/dyoo/korean-tts.git","type":"git"},"_npmVersion":"11.16.0","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","directories":{},"_nodeVersion":"24.18.1","dependencies":{"kokoro-js":"^1.2.1","@huggingface/transformers":"^3.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.2.1","typescript":"^7.0.2","@types/node":"^26.2.0","vite-plugin-dts":"^5.0.3","@typescript/typescript6":"^6.0.2"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/korean-tts_1.2.4_1787364196370_0.8836080454819495","host":"s3://npm-registry-packages-npm-production"}},"1.2.5":{"name":"@dannyyoo/korean-tts","version":"1.2.5","license":"MIT","_id":"@dannyyoo/korean-tts@1.2.5","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"homepage":"https://github.com/dyoo/korean-tts#readme","bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"dist":{"shasum":"53ea1c3a14c6ee7d72122b2f937a8a5f05093716","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.5.tgz","fileCount":11,"integrity":"sha512-u46Suu1+N4wFcGEjIk5J2LZcuLxZBk8KMfcReRPgVdW6O1Kf00W6Q0HW9BkepngoZVRAZ7B1DCnP0pr8wnIVCg==","signatures":[{"sig":"MEYCIQDKNV8IDNM9qzAtSt+qbCcAnUZ124LFIE/UO4tF3/XNegIhALiTRRR1QQopsd00SjQF8WEzLQ+4VDsH6Z3auL03fLyj","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119072},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"aaa44aeef75a6efcd938af312fe7015f58350bb0","scripts":{"dev":"vite --port 5173","test":"node --experimental-strip-types --test test/**/*.test.ts","build":"npm run build:lib","preview":"vite preview","build:lib":"tsc --noEmit && vite build --mode lib","typecheck":"tsc --noEmit","build:demo":"tsc --noEmit && vite build","prepublishOnly":"npm run typecheck && npm run build:lib"},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"repository":{"url":"git+https://github.com/dyoo/korean-tts.git","type":"git"},"_npmVersion":"11.16.0","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","directories":{},"_nodeVersion":"24.18.1","dependencies":{"kokoro-js":"^1.2.1","@huggingface/transformers":"^3.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.2.1","typescript":"^7.0.2","@types/node":"^26.2.0","vite-plugin-dts":"^5.0.3","@typescript/typescript6":"^6.0.2"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/korean-tts_1.2.5_1787364357896_0.7549004255220881","host":"s3://npm-registry-packages-npm-production"}},"1.2.6":{"name":"@dannyyoo/korean-tts","version":"1.2.6","license":"MIT","_id":"@dannyyoo/korean-tts@1.2.6","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"homepage":"https://github.com/dyoo/korean-tts#readme","bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"dist":{"shasum":"dddfeb586c61e5daa0bd6e801f167cc07b0677f0","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.6.tgz","fileCount":11,"integrity":"sha512-zFghrIBd8HX0o7qtJWmYc3OcAVCZ/oynEZrp/m+NMBE7nAZ5zgBEFk7q2V1di58U6CU9sJCbDNOAKgC+WdNhFg==","signatures":[{"sig":"MEUCIQCU1XVKO1hNHLVRgHE+SXzuGzyO0G2y68T0qYwS3Qg++QIgDiH3FvlefWpx2Qdsm+dKMb7CDumh/CZThk6OZFdOLe8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119165},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"d8cb80c9c9a2193ece5e674d2c500946db41675a","scripts":{"dev":"vite --port 5173","test":"node --experimental-strip-types --test test/**/*.test.ts","build":"npm run build:lib","preview":"vite preview","build:lib":"tsc --noEmit && vite build --mode lib","typecheck":"tsc --noEmit","build:demo":"tsc --noEmit && vite build","prepublishOnly":"npm run typecheck && npm run build:lib"},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"repository":{"url":"git+https://github.com/dyoo/korean-tts.git","type":"git"},"_npmVersion":"11.16.0","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","directories":{},"_nodeVersion":"24.18.1","dependencies":{"kokoro-js":"^1.2.1","@huggingface/transformers":"^3.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.2.1","typescript":"^7.0.2","@types/node":"^26.2.0","vite-plugin-dts":"^5.0.3","@typescript/typescript6":"^6.0.2"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/korean-tts_1.2.6_1787365302879_0.4580223643155301","host":"s3://npm-registry-packages-npm-production"}},"1.2.7":{"name":"@dannyyoo/korean-tts","version":"1.2.7","license":"MIT","_id":"@dannyyoo/korean-tts@1.2.7","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"homepage":"https://github.com/dyoo/korean-tts#readme","bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"dist":{"shasum":"cb219b36cdf9ba767ef876de21a9769dca9a08f2","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.7.tgz","fileCount":11,"integrity":"sha512-534NARyN0H/A7ANZCGHQhcJyWd5sxZykTXlNu5fQIWDFBpfkFDoPJ3eZULEtGqZ1Np+S9QI5VEkHIjsqFHaQEw==","signatures":[{"sig":"MEUCIQD32y81awcVnuwz9XB+rCqZKXz/InG5pjKHFWYPXty4/AIgfFFEyXIyNdgIp9AcyoHfbgzhtNNlaQTxhn9OOGCqUPA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119210},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"c170adb7fb0570d45b3f28793f2a8922951896b3","scripts":{"dev":"vite --port 5173","test":"node --experimental-strip-types --test test/**/*.test.ts","build":"npm run build:lib","preview":"vite preview","build:lib":"tsc --noEmit && vite build --mode lib","typecheck":"tsc --noEmit","build:demo":"tsc --noEmit && vite build","prepublishOnly":"npm run typecheck && npm run build:lib"},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"repository":{"url":"git+https://github.com/dyoo/korean-tts.git","type":"git"},"_npmVersion":"11.16.0","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","directories":{},"_nodeVersion":"24.18.1","dependencies":{"kokoro-js":"^1.2.1","@huggingface/transformers":"^3.8.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.2.1","typescript":"^7.0.2","@types/node":"^26.2.0","vite-plugin-dts":"^5.0.3","@typescript/typescript6":"^6.0.2"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/korean-tts_1.2.7_1787366046736_0.8515932272760143","host":"s3://npm-registry-packages-npm-production"}},"1.2.8":{"name":"@dannyyoo/korean-tts","version":"1.2.8","description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","type":"module","license":"MIT","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/dyoo/korean-tts.git"},"bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"homepage":"https://github.com/dyoo/korean-tts#readme","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"vite --port 5173","build":"npm run build:lib","build:lib":"tsc --noEmit && vite build --mode lib","build:demo":"tsc --noEmit && vite build","typecheck":"tsc --noEmit","preview":"vite preview","test":"node --experimental-strip-types --test test/**/*.test.ts","prepublishOnly":"npm run typecheck && npm run build:lib"},"dependencies":{"@huggingface/transformers":"^3.8.1","kokoro-js":"^1.2.1"},"peerDependencies":{"kokoro-js":"^1.2.1"},"peerDependenciesMeta":{"kokoro-js":{"optional":true}},"devDependencies":{"@types/node":"^26.2.0","@typescript/typescript6":"^6.0.2","typescript":"^7.0.2","vite":"^8.2.1","vite-plugin-dts":"^5.0.3"},"gitHead":"dee46276b6af882b00727f7828e7024b5ef8284b","_id":"@dannyyoo/korean-tts@1.2.8","_nodeVersion":"24.18.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-YA6rUbmMHphQD4vOoD9139MojMyu5EoaAUkAWAt8hTouYE+GIqmCTSWJr3vbK6Rd8porVRfOE5y/A/8qBr/3Kg==","shasum":"a50422b704ac87df3e52787695b045b883686408","tarball":"https://registry.npmjs.org/@dannyyoo/korean-tts/-/korean-tts-1.2.8.tgz","fileCount":11,"unpackedSize":121707,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGi0Qsn0rn/MLgN04uL8CeTsBahpamOJqpxk+sWYjnhiAiBc2NIH8itmycB7kgv73etIrgfJ7ZEn1wLlD8097JBX3Q=="}]},"_npmUser":{"name":"dannyyoo","email":"danny.yoo@gmail.com"},"directories":{},"maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/korean-tts_1.2.8_1787456317662_0.9406080045865837"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-22T01:22:53.766Z","modified":"2026-08-23T03:38:37.966Z","1.2.2":"2026-08-22T01:22:54.026Z","1.2.3":"2026-08-22T01:32:13.079Z","1.2.4":"2026-08-22T02:03:16.527Z","1.2.5":"2026-08-22T02:05:58.224Z","1.2.6":"2026-08-22T02:21:43.025Z","1.2.7":"2026-08-22T02:34:06.915Z","1.2.8":"2026-08-23T03:38:37.797Z"},"bugs":{"url":"https://github.com/dyoo/korean-tts/issues"},"license":"MIT","homepage":"https://github.com/dyoo/korean-tts#readme","repository":{"type":"git","url":"git+https://github.com/dyoo/korean-tts.git"},"description":"Korean phonology to IPA monophthong converter and audio utilities for Kokoro-82M TTS WASM","maintainers":[{"name":"dannyyoo","email":"danny.yoo@gmail.com"}],"readme":"# Korean Kokoro TTS — Phonology Engine & WASM Playground\n\n[![Live Demo](https://img.shields.io/badge/Live%20Demo-GitHub%20Pages-blue?style=flat-square&logo=github)](https://dyoo.github.io/korean-tts/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE)\n\nA lightweight, zero-backend WebAssembly (WASM) and WebGPU speech synthesis engine and testing playground for running [**Kokoro-82M TTS**](https://huggingface.co/hexgrad/Kokoro-82M) on Korean sentences.\n\n🎮 **Live Interactive Demo:** [https://dyoo.github.io/korean-tts/](https://dyoo.github.io/korean-tts/)\n\nThis package can be used as an **npm library** in your own web applications/PWAs, or run locally as an **interactive playground**.\n\n---\n\n## Library Usage (npm)\n\n### 1. Installation\n\n```bash\nnpm install @dannyyoo/korean-tts kokoro-js\n```\n\n### 2. Safari & Web Worker Compatibility\n\n1. **`ReadableStream` Polyfill**:\n   In Safari/WebKit (macOS & iOS) and dedicated Web Worker environments, `ReadableStream` lacks native async iterator (`[Symbol.asyncIterator]`) support. Because underlying phonemizer dependencies decompress phonetic dictionaries at module evaluation, import and call `polyfillReadableStreamAsyncIterator()` at the top of your worker or main script:\n\n   ```typescript\n   import { polyfillReadableStreamAsyncIterator } from \"@dannyyoo/korean-tts\";\n   polyfillReadableStreamAsyncIterator();\n   ```\n\n2. **Web Worker Threading**:\n   Safari's `DedicatedWorkerGlobalScope` does not allow nested worker spawning (`new Worker()` inside a Worker). When running Kokoro in a dedicated Web Worker, enforce single-threaded WASM execution before initializing:\n   ```typescript\n   import { env } from \"@huggingface/transformers\";\n   if (env.backends?.onnx?.wasm) {\n     env.backends.onnx.wasm.numThreads = 1;\n     env.backends.onnx.wasm.proxy = false;\n   }\n   ```\n\n### 3. High-Level `KoreanSpeaker` Example\n\n`KoreanSpeaker` manages model downloading, caching, voice selection, Hangul-to-IPA phonology conversion, audio synthesis, structured cancellation, and offline cache maintenance in one unified interface:\n\n```typescript\nimport { KoreanSpeaker } from \"@dannyyoo/korean-tts\";\n\n// 1. Initialize speaker instance\nconst speaker = new KoreanSpeaker({\n  device: \"wasm\", // \"wasm\" or \"webgpu\"\n  dtype: \"q8\",    // \"q8\" (~86MB), \"fp32\", \"fp16\", or \"q4\"\n});\n\n// 2. Load model with progress tracking (auto-cached in browser CacheStorage)\nawait speaker.load({\n  progressCallback: (p) => {\n    console.log(`Downloading: ${p.file} (${p.progress}%)`);\n  },\n});\n\n// 3. Get supported voices (Japanese / Mandarin CJK voices tuned for syllable timing)\nconst voices = speaker.getVoices();\n// [{ id: \"jf_nezumi\", name: \"Nezumi\", traits: \"...\", ... }, ...]\n\n// 4. Synthesize speech from Korean text (or raw IPA) with cancelable task handle\nconst task = speaker.synthesize({\n  text: \"안녕하세요! 반갑습니다.\",\n  voice: \"jf_nezumi\", // Default: jf_nezumi\n  speed: 1.0,\n  onProgress: (ev) => {\n    console.log(`Stage: ${ev.stage} (${Math.round(ev.progress * 100)}%)`);\n  },\n});\n\n// Cancel anytime if needed:\n// task.cancel(\"User navigated away\");\n\nconst result = await task;\n\n// Access metrics & outputs\nconsole.log(`Generated in ${result.genTimeMs}ms (${result.rtf.toFixed(2)}x RTF)`);\nconsole.log(`IPA Payload: ${result.ipa}`);\n\n// 5. Playback or download WAV\nconst wavBlob = result.toWavBlob();\nconst audioUrl = result.toAudioUrl();\n\n// 6. Direct one-line speak & play\nawait speaker.speak({ text: \"오늘도 좋은 하루 되세요!\" });\n\n// 7. Inspect or clear offline storage (PWA ready)\nconst storage = await speaker.getStorageInfo();\nconsole.log(`Storage: ${storage.modelSizeFormatted} (Offline Cached: ${storage.isCached})`);\n\n// Delete cached model from disk and release RAM when user opts out\n// await speaker.clearStorage();\n```\n\n---\n\n## API Reference (`korean-speaker`)\n\n### `KoreanSpeaker` Methods\n\n| Method | Parameters | Returns | Description |\n| :--- | :--- | :--- | :--- |\n| `constructor(options?)` | `SpeakerInitOptions?` | `KoreanSpeaker` | Creates a new speaker instance with default or custom backend config. |\n| `load(options?)` | `SpeakerInitOptions?` | `Promise<void>` | Downloads and initializes the ONNX model into WASM or WebGPU. |\n| `isLoaded()` | — | `boolean` | Returns `true` if the model is initialized and ready for synthesis. |\n| `getBackend()` | — | `{ device, dtype, modelId }` | Returns active hardware device, precision, and model repo. |\n| `getVoices()` | — | `VoiceConfig[]` | Returns all available voices with language, gender, and trait metadata. |\n| `textToIpa(text)` | `koreanText: string` | `string` | Converts Korean text into normalized, phonetically assimilated IPA. |\n| `getVoiceVector(name)` | `voiceName: string` | `Promise<Float32Array>` | Fetches and caches voice style embedding vector from CDN. |\n| `preloadVoices(names)` | `voiceNames: string[]` | `Promise<void>` | Preloads multiple voice style vectors into memory. |\n| `synthesize(input)` | `SynthesisInput` | `SynthesisTask` | Starts synthesis and returns a cancelable `SynthesisTask` handle. |\n| `getActiveTasks()` | — | `SynthesisTask[]` | Returns a snapshot of all active/in-flight synthesis tasks. |\n| `cancelCurrent(reason?)` | `reason?: string` | `void` | Cancels the most recently initiated synthesis task. |\n| `cancelAll(reason?)` | `reason?: string` | `void` | Cancels all active and in-flight synthesis tasks. |\n| `speak(input)` | `SynthesisInput` | `Promise<{ result, audio }>` | Synthesizes and immediately begins audio playback via `HTMLAudioElement`. |\n| `getStorageInfo()` | — | `Promise<StorageInfo>` | Inspects browser `CacheStorage` for offline model size and origin quota. |\n| `clearStorage()` | — | `Promise<boolean>` | Deletes model weights from `CacheStorage` and frees WebAssembly RAM. |\n| `dispose()` | — | `void` | Releases in-memory model instances, active tasks, and cached style vectors. |\n\n### Exported Types & Interfaces\n\n* **`SynthesisTask`**: Structured PromiseLike task handle supporting `.cancel(reason?)`, `.onProgress(cb)`, `.stage`, `.isCancelled`, and `.isSettled`.\n* **`SynthesisCancelledError`**: Error thrown when a task is aborted (`err.name === 'AbortError'`, `err.isCancelled === true`).\n* **`SpeakerInitOptions`**: Configuration for model loading (`modelId`, `dtype`, `device`, `progressCallback`, `requestPersistence`).\n* **`SynthesisInput`**: Discriminated union `{ text: string; voice?: string; speed?: number; onProgress?: cb } | { ipa: string; voice?: string; speed?: number; onProgress?: cb }`.\n* **`SynthesisResult`**: Synthesis outputs (`audio: Float32Array`, `sampleRate`, `durationSec`, `genTimeMs`, `rtf`, `ipa`, `voice`, `speed`, `toWavBlob()`, `toAudioUrl()`, `createAudioElement()`).\n* **`SpeakerProgress`**: Discriminated union of progress lifecycle events (`SpeakerInitiateProgress`, `SpeakerDownloadProgress`, `SpeakerChunkProgress`, `SpeakerDoneProgress`, `SpeakerReadyProgress`).\n* **`SpeakerProgressStatus`**: `\"initiate\" | \"download\" | \"progress\" | \"done\" | \"ready\"`.\n* **`SpeakerProgressCallback`**: `(progress: SpeakerProgress) => void`.\n* **`StorageInfo`**: Offline cache inspection metrics (`isCached`, `modelSizeBytes`, `modelSizeFormatted`, `totalUsageBytes`, `totalUsageFormatted`, `persisted`).\n\n---\n\n## Low-Level Phonology & Audio Utilities\n\nYou can also import individual building blocks:\n\n```typescript\nimport {\n  koreanToIpa,\n  koreanToPronunciation,\n  decomposeHangul,\n  composeHangul,\n  numberToNativeKorean,\n  createWavBlob,\n  Visualizer,\n} from \"@dannyyoo/korean-tts\";\n\n// 1. Phonetic Hangul pronunciation according to Standard Korean rules (표준 발음법)\nconst pron = koreanToPronunciation(\"국밥\"); // -> \"국빱\"\nconst thankYou = koreanToPronunciation(\"감사합니다\"); // -> \"감사함니다\"\nconst liaison = koreanToPronunciation(\"한국어\"); // -> \"한구거\"\n\n// 2. Phonetic Hangul-to-IPA transcription with assimilation rules\nconst ipa = koreanToIpa(\"감사합니다\"); // -> \"kamsahamnita\"\n\n// 3. Native Korean number conversion (순우리말 수사)\nconst count = numberToNativeKorean(20, true); // -> \"스무\" (e.g. \"스무 살\")\n\n// 4. Convert raw Float32Array PCM samples to WAV Blob\nconst wavBlob = createWavBlob(float32Array, 24000);\n```\n\n---\n\n## Korean G2P & IPA Algorithm Architecture\n\nThe speech synthesis pipeline converts raw Korean text into phonetically transcribed, Kokoro-compatible IPA monophthongs through a 4-stage pipeline:\n\n```\n┌─────────────────────────┐\n│     Raw Korean Text     │  \"국밥 2개 주세요! 50% 할인되나요?\"\n└────────────┬────────────┘\n             │ 1. Normalization & Tokenization\n┌────────────▼────────────┐\n│   Normalized Hangul     │  \"국밥 두 개 주세요! 오십 퍼센트 할인되나요?\"\n└────────────┬────────────┘\n             │ 2. Unicode Jamo Decomposition (초성 / 중성 / 종성)\n┌────────────▼────────────┐\n│   Decomposed Syllables  │  [{ᄀ, ᅮ, ᆨ}, {ᄇ, ᅡ, ᆸ}, ...]\n└────────────┬────────────┘\n             │ 3. Multi-Pass Phonology Engine (표준 발음법)\n┌────────────▼────────────┐\n│ Phonetic Hangul Pron.   │  \"국빱 두 개 주세요! 오십 퍼센트 할인되나요?\"\n└────────────┬────────────┘\n             │ 4. Allophonic Kokoro IPA Transcription\n┌────────────▼────────────┐\n│       Output IPA        │  \"kuk̚p͈ap̚ tu ɡe ʨusejo! oɕip̚ pʰʌsɛntʰɯ haɾindwenajo?\"\n└─────────────────────────┘\n```\n\n---\n\n### Stage 1: Text & Number Normalization (`normalizeKoreanText`)\n\nRaw inputs often contain digits, currency, dates, percentages, and acronyms that must be converted to spoken Korean words before phonetic transcription:\n\n1. **Native Korean Counting Units (순우리말 수사)**:\n   - Matches numbers `1–99` before counting classifiers (`개`, `명`, `살`, `마리`, `잔`, `권`, `장`, `번`, etc.) and transforms them into pure Korean attributive forms:\n     - `1개` → `한 개`, `2명` → `두 명`, `3살` → `세 살`, `4마리` → `네 마리`, `20살` → `스무 살`, `21명` → `스물한 명`.\n2. **Clock Times & Hours**:\n   - Hours use Native Korean, while minutes and seconds use Sino-Korean: `3시 30분` → `세시 삼십분`, `12시 5분` → `열두시 오분`.\n3. **Decimals, Percentages & Phone Numbers**:\n   - Decimals: `3.14` → `삼 점 일사`, `0.5` → `영 점 오`\n   - Percentages: `99.9%` → `구십구 점 구 퍼센트`\n   - Phone numbers: `010-1234-5678` → `공일공 일이삼사 오육칠팔`\n   - Ordinals: `1번째` → `첫 번째`, `2번째` → `두 번째`, `3번째` → `세 번째`\n4. **Sino-Korean Currency & Dates**:\n   - `24,500원` → `이만 사천오백원`, `2026년 8월 15일` → `이천이십육년 팔월 십오일`.\n5. **English Acronyms & Letters**:\n   - `AI 모델` → `에이아이 모델`, `TTS` → `티티에스`, `OK` → `오케이`.\n6. **Standalone Jamo Normalization (단독 자모 발음)**:\n   - **Standalone Vowels**: Mapped to canonical zero-onset syllables (`ㅗ` → `오` [o], `ㅏ` → `아` [a], `ㅜ` → `우` [u], `ㅣ` → `이` [i], `ㅐ` → `애` [ɛ]).\n   - **Standalone Consonants**: Vocalized with phonetic base vowel `ㅡ` (`ㄱ` → `그` [kɯ], `ㄴ` → `느` [nɯ], `ㄷ` → `드` [tɯ], `ㅅ` → `스` [sɯ], `ㅋ` → `크` [kʰɯ], `ㄲ` → `끄` [k͈ɯ], `ㅇ` → `응` [ɯŋ]).\n\n---\n\n### Stage 2: Syllabic Jamo Decomposition (`decomposeHangul`)\n\nHangul syllables in the Unicode range `0xAC00`–`0xD7A3` (and standalone compatibility Jamos `0x3131`–`0x318E` / `0x1100`–`0x11FF`) are decomposed arithmetically into their 19 Initial Consonants (초성), 21 Vowels (중성), and 28 Final Codas (종성):\n\n$$\\text{offset} = \\text{charCode} - \\text{0xAC00}$$\n$$\\text{choIdx} = \\lfloor \\text{offset} / 588 \\rfloor, \\quad \\text{jungIdx} = \\lfloor (\\text{offset} / 28) \\bmod 21 \\rfloor, \\quad \\text{jongIdx} = \\text{offset} \\bmod 28$$\n\n---\n\n### Stage 3: Multi-Pass Phonological Transformation (`applyPhonologicalRules`)\n\nApplies the official [Standard Korean Pronunciation Rules (국립국어원 표준 발음법)](https://ko.wikisource.org/wiki/%ED%91%9C%EC%A4%80%EC%96%B4_%EA%B7%9C%EC%A0%95#%EC%A0%9C2%EB%B6%80_%ED%91%9C%EC%A4%80_%EB%B0%9C%EC%9D%8C%EB%B2%95) across syllable boundaries:\n\n1. **Palatalization (구개음화 — 제17항)**:\n   - `ㄷ, ㅌ, ㄾ` before `ㅣ` or `j`-glides become `ㅈ, ㅊ`:\n   - `굳이` → `[구지]`, `같이` → `[가치]`, `핥이다` → `[할치다]`, `닫히다` → `[다치다]`.\n2. **Aspiration & ㅎ-Elision (격음화 및 ㅎ 탈락 — 제12항)**:\n   - Obstruent + `ㅎ` or `ㅎ` + obstruent fuse into aspirated consonants (`ㅋ, ㅌ, ㅍ, ㅊ`): `축하` → `[추카]`, `좋다` → `[조타]`, `맞히다` → `[마치다]`.\n   - `ㅎ` between vowels/sonorants drops: `좋아` → `[조아]`, `많이` → `[마니]`, `싫어` → `[시러]`.\n3. **Liaison (연음법칙 — 제13항, 제14항)**:\n   - Single and compound codas move to empty onset (`ㅇ`) of the following syllable: `한국어` → `[한구거]`, `값이` → `[갑씨]`, `닭을` → `[달글]`, `삶이` → `[살미]`.\n4. **Liquid Lateralization & Nasalization (유음화 및 ㄹ의 비음화 — 제19항, 제20항)**:\n   - `ㄴ + ㄹ` and `ㄹ + ㄴ` become lateral geminate `ㄹㄹ`: `신라` → `[실라]`, `난로` → `[날로]`, `설날` → `[설랄]`.\n   - `ㅁ, ㅇ` + `ㄹ` → `ㅁ, ㅇ + ㄴ`: `종로` → `[종노]`, `대통령` → `[대통녕]`, `침략` → `[침냑]`.\n   - `ㄱ, ㅂ` + `ㄹ` → `ㅇ, ㅁ + ㄴ` (Mutual assimilation): `국립` → `[궁닙]`, `독립` → `[동닙]`, `협력` → `[혐녁]`.\n5. **Nasalization (비음화 — 제18항)**:\n   - Stops (`ㄱ, ㄷ, ㅂ`) before nasals (`ㄴ, ㅁ`) become nasals (`ㅇ, ㄴ, ㅁ`): `국물` → `[궁물]`, `감사합니다` → `[감사함니다]`, `있는` → `[인는]`.\n6. **Tensification / Glottalization (경음화 / 된소리되기 — 제23항~제26항)**:\n   - **Post-Obstruent (제23항)**: `국밥` → `[국빱]`, `학교` → `[학꾜]`, `있다` → `[읻따]`, `잡지` → `[잡찌]`.\n   - **Special `ㄺ + ㄱ` (제25항)**: `맑게` → `[말께]`, `읽고` → `[일꼬]`.\n   - **Predicate Stems ending in `ㄴ, ㅁ` (제24항)**: `신다` → `[신따]`, `앉다` → `[안따]`, `젊다` → `[점따]`, `삼다` → `[삼따]`.\n   - **Sino-Korean `ㄹ` Coda (제26항)**: Hanja roots ending in `ㄹ` tensify subsequent `ㄷ, ㅅ, ㅈ`: `갈등` → `[갈뜽]`, `발전` → `[발쩐]`, `물질` → `[물찔]`, `실수` → `[실쑤]`, `활동` → `[활똥]`, `열정` → `[열쩡]`.\n7. **Coda Neutralization (자음군 단순화 & 음절 끝소리 규칙 — 제8항~제11항)**:\n   - Final codas in isolation or before consonants reduce to the 7 stop archetypes (`ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅂ, ㅇ`): `닭` → `[닥]`, `값` → `[갑]`, `삶` → `[삼]`, `여덟` → `[여덜]`, `꽃` → `[꼳]`.\n\n---\n\n### Stage 4: Allophonic Kokoro-Targeted IPA Transcription (`convertKoreanToSpeechText`)\n\nConverts the assimilated syllable tokens into accurate International Phonetic Alphabet (IPA) representations optimized for Kokoro-82M CJK acoustic models:\n\n1. **Alveolo-palatalization (`[ɕ, ɕ͈]`)**:\n   - `ㅅ, ㅆ` preceding `/i/` or `/j/` glides are transcribed as alveolo-palatal `[ɕ, ɕ͈]`:\n     - `시간` → `ɕiɡan` (instead of `sikan`)\n     - `신라` → `ɕilla`\n     - `시작` → `ɕiʥak̚`\n     - `씨앗` → `ɕ͈iat̚`\n2. **Intervocalic & Post-Sonorant Voicing (`[ɡ, d, b, ʥ]`)**:\n   - Plain stops and affricates (`ㄱ, ㄷ, ㅂ, ㅈ`) become voiced between sonorants (vowels and `ㄴ, ㄹ, ㅁ, ㅇ`):\n     - `아버지` → `abʌʥi`\n     - `친구` → `tʃʰinɡu`\n     - `한국어` → `hanɡuɡʌ`\n     - `감사합니다` → `kamsahamnida`\n3. **Lateral Gemination (`[ll]`)**:\n   - Consecutive `ㄹ` sounds are represented as true alveolar lateral geminates (`[ll]`):\n     - `설날` → `sʌllal`\n     - `빨리` → `p͈alli`\n4. **Unreleased Stop Codas (`[k̚, t̚, p̚]`)**:\n   - Syllable-final stops are marked as unreleased: `국밥` → `kuk̚p͈ap̚`.\n5. **Vowel Hiatus & Zero-Onset Boundaries (`[ˌ]`)**:\n   - Open syllables ending in a vowel followed by a zero-onset syllable (`ㅇ`) receive a secondary stress syllable foot marker `ˌ` (Token ID 161) to create a crisp, micro-beat syllable transition while preventing diphthong collapse:\n     - `내일` → `nɛˌil`\n     - `오이` → `oˌi`\n     - `아이` → `aˌi`\n     - `좋은` → `ʨoˌɯn`\n6. **Expressive Question Intonation (`[↗?]`)**:\n   - Question sentences ending in `?` are augmented with Kokoro's native rising pitch contour operator (`↗` — Token ID 175), sweeping fundamental frequency ($F_0$) upward on the final syllable for authentic spoken Korean interrogative delivery:\n     - `이거 뭐예요?` → `iɡʌ mwʌˌjeˌjo↗?`\n     - `밥 먹었어?` → `pap̚ mʌɡʌs͈ʌ↗?`\n7. **Single-Word / Isolated Syllable Duration Closure (`[.]`)**:\n   - Unpunctuated isolated vocabulary items receive declarative sentence-final boundary closure (`.`) prior to tokenization. This prevents neural duration predictors from treating single syllables as unfinished floating phrases, ensuring crisp $\\sim 200\\text{ms}$ pronunciations rather than drawn-out, drone-like vowels:\n     - `넋` → `nʌk̚.`\n     - `가` → `ka.`\n     - `물` → `mul.`\n\n---\n\n## [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M) 115-Token Phoneme Architecture & IPA Compatibility\n\n[**Kokoro-82M**](https://huggingface.co/hexgrad/Kokoro-82M) is a neural Text-to-Speech model with an internal 115-token phoneme vocabulary (comprising ASCII letters, selected IPA extensions, punctuation, and Japanese/Chinese phonetic tokens). Unlike standard NLP tokenizers with thousands of subwords, Kokoro processes text strictly at the phoneme level.\n\nCharacters not present in Kokoro's 115-token vocabulary are **silently dropped by the tokenizer**. Understanding this mapping is essential for natural Korean synthesis.\n\n### Complete 115-Token Vocabulary Breakdown\n\nKokoro's vocabulary consists of 115 valid tokens indexed across ID 0 to 177:\n\n| Category | Tokens | Count | Description |\n| :--- | :--- | :--- | :--- |\n| **Punctuation & Prosody** | `$`, `;`, `:`, `,`, `.`, `!`, `?`, `—`, `…`, `\"`, `(`, `)`, `“`, `”`, ` ` (space) | 15 | Sentence boundaries, pauses, and dialogue quotes |\n| **ASCII Alphabet (Lower)** | `a`, `b`, `c`, `d`, `e`, `f`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z` | 25 | Standard Latin phonemes (`g` is replaced by IPA `ɡ`) |\n| **ASCII Alphabet (Upper)** | `A`, `I`, `O`, `Q`, `S`, `T`, `W`, `Y` | 8 | Special prosodic & language tokens (`Q` = Japanese sokuon ッ) |\n| **Affricates & Ligatures** | `ʥ` (19), `ʨ` (21), `ʦ` (20), `ʣ` (18), `ʧ` (133), `ʤ` (82), `ꭧ` (23) | 7 | Dedicated alveolo-palatal, dental, and post-alveolar affricates |\n| **Vowels (IPA Extensions)** | `ɑ`, `ɐ`, `ɒ`, `æ`, `ɔ`, `ə`, `ɚ`, `ɛ`, `ɜ`, `ɨ`, `ɪ`, `ɯ`, `ø`, `œ`, `ʊ`, `ʌ`, `ɤ`, `ᵻ` | 18 | Monophthongs, central vowels, and open/close variants |\n| **Consonants (IPA Extensions)** | `ɕ`, `ç`, `ɖ`, `ð`, `ɟ`, `ɡ`, `ɥ`, `ʝ`, `ɰ`, `ŋ`, `ɳ`, `ɲ`, `ɴ`, `ɸ`, `θ`, `ɹ`, `ɾ`, `ɻ`, `ʁ`, `ɽ`, `ʂ`, `ʃ`, `ʈ`, `ʋ`, `ɣ`, `χ`, `ʎ`, `ʒ`, `ʔ` | 29 | Fricatives, nasals, retroflex, liquids, glottal stop |\n| **Diacritics & Modifiers** | `̃` (nasalization), `ᵝ`, `ᵊ`, `ˈ` (primary stress), `ˌ` (secondary stress), `ː` (length), `ʰ` (aspiration), `ʲ` (palatalization) | 8 | Phoneme modifiers |\n| **Tonal Contours** | `↓` (downstep / 3rd tone), `→` (level / 1st tone), `↗` (rising / 2nd tone), `↘` (falling / 4th tone) | 4 | Asian tonal pitch inflections |\n\n---\n\n### Korean Hangul to Kokoro IPA Phoneme Mapping\n\n| Korean Grapheme | Standard Linguistic IPA | Kokoro Token(s) | Status in Vocab | Notes & Acoustic Treatment |\n| :--- | :--- | :--- | :--- | :--- |\n| **ㄱ (Initial)** | `[k]` | `k` | Native (ID 53) | Voiceless velar stop |\n| **ㄱ (Voiced Intervocalic)** | `[ɡ]` | `ɡ` | Native (ID 92) | Voiced velar stop between vowels/sonorants |\n| **ㄲ (Tense)** | `[k͈]` | `k` / `k͈` | Diacritic dropped | `\\u0348` stripped by tokenizer; synthesized as unvoiced stop |\n| **ㅋ (Aspirated)** | `[kʰ]` | `kʰ` | Native (`k` + `ʰ`) | Aspirated velar stop (IDs 53 + 162) |\n| **ㄷ (Initial)** | `[t]` | `t` | Native (ID 62) | Voiceless alveolar stop |\n| **ㄷ (Voiced Intervocalic)** | `[d]` | `d` | Native (ID 46) | Voiced alveolar stop |\n| **ㄸ (Tense)** | `[t͈]` | `t` / `t͈` | Diacritic dropped | `\\u0348` stripped by tokenizer |\n| **ㅌ (Aspirated)** | `[tʰ]` | `tʰ` | Native (`t` + `ʰ`) | Aspirated alveolar stop (IDs 62 + 162) |\n| **ㅂ (Initial)** | `[p]` | `p` | Native (ID 58) | Voiceless bilabial stop |\n| **ㅂ (Voiced Intervocalic)** | `[b]` | `b` | Native (ID 44) | Voiced bilabial stop |\n| **ㅃ (Tense)** | `[p͈]` | `p` / `p͈` | Diacritic dropped | `\\u0348` stripped by tokenizer |\n| **ㅍ (Aspirated)** | `[pʰ]` | `pʰ` | Native (`p` + `ʰ`) | Aspirated bilabial stop (IDs 58 + 162) |\n| **ㅈ (Initial / Plain)** | `[t͡ɕ]` | `ʨ` | Native (ID 21) | Mapped to Kokoro's native voiceless alveolo-palatal affricate |\n| **ㅈ (Voiced Intervocalic)** | `[d͡ʑ]` | `ʥ` | Native (ID 19) | Mapped to Kokoro's native voiced alveolo-palatal affricate |\n| **ㅉ (Tense)** | `[t͡ɕ͈]` | `ʨ͈` $\\rightarrow$ `ʨ` | Diacritic dropped | `\\u0348` stripped by tokenizer |\n| **ㅊ (Aspirated)** | `[t͡ɕʰ]` | `tʃʰ` | Native (`t` + `ʃ` + `ʰ`) | Aspirated postalveolar affricate (IDs 62 + 131 + 162) |\n| **ㅅ (Plain)** | `[s]` | `s` | Native (ID 61) | Alveolar fricative before /a, ʌ, o, u, ɯ/ |\n| **ㅅ (Palatalized /i, j/)** | `[ɕ]` | `ɕ` | Native (ID 77) | Alveolo-palatal fricative before /i, j/ (e.g. `시간` → `ɕiɡan`) |\n| **ㅆ (Tense)** | `[s͈]` | `s` / `s͈` | Diacritic dropped | `\\u0348` stripped by tokenizer |\n| **ㅆ (Palatalized /i, j/)** | `[ɕ͈]` | `ɕ` / `ɕ͈` | Diacritic dropped | `\\u0348` stripped by tokenizer |\n| **ㅎ (Glottal)** | `[h]` | `h` | Native (ID 50) | Voiceless glottal fricative |\n| **ㄴ (Alveolar Nasal)** | `[n]` | `n` | Native (ID 56) | Alveolar nasal |\n| **ㅁ (Bilabial Nasal)** | `[m]` | `m` | Native (ID 55) | Bilabial nasal |\n| **ㅇ (Velar Nasal Coda)** | `[ŋ]` | `ŋ` | Native (ID 112) | Velar nasal coda (e.g. `강` → `kaŋ`) |\n| **ㄹ (Flap Onset)** | `[ɾ]` | `ɾ` | Native (ID 125) | Alveolar tap/flap (e.g. `바람` → `paɾam`) |\n| **ㄹ (Lateral Coda/Geminate)** | `[l]` / `[ll]` | `l` / `ll` | Native (ID 54) | Alveolar lateral (e.g. `신라` → `ɕilla`) |\n| **Unreleased Codas (ㄱ, ㄷ, ㅂ)** | `[k̚, t̚, p̚]` | `k, t, p` | Diacritic dropped | `\\u031a` stripped by tokenizer; natural coda acoustic decay |\n| **ㅏ, ㅓ, ㅗ, ㅜ, ㅡ, ㅣ** | `[a, ʌ, o, u, ɯ, i]` | `a, ʌ, o, u, ɯ, i` | All Native | Exact 1:1 monophthong tokens in Kokoro |\n| **ㅐ, ㅔ** | `[ɛ], [e]` | `ɛ, e` | All Native | `ɛ` (ID 86) and `e` (ID 47) both supported |\n| **ㅚ, ㅟ, ㅢ** | `[we], [ɥi], [ɰi]` | `we, ɥi, ɰi` | All Native | `ɰ` (ID 111) and `ɥ` (ID 99) natively supported |\n| **Glides (ㅑ, ㅕ, ㅛ, ㅠ, ㅘ, ㅝ, ...)** | `[ja, jʌ, jo, ju, wa, wʌ]` | `ja, jʌ, jo, ju, wa, wʌ` | All Native | Combined glide + vowel sequences |\n\n---\n\n### Key Phonetic Gaps & Solutions\n\n#### 1. The Intervocalic Affricate Gap (`d͡ʑ` $\\rightarrow$ `d` Regression)\n* **The Problem**: In standard linguistic literature, the voiced intervocalic allophone of `ㅈ` is written as `[d͡ʑ]`. However, neither the tie bar `\\u0361` (`͡`) nor the curly-tail z `\\u0291` (`ʑ`) exists in Kokoro's 115-token vocabulary. When `d͡ʑ` was passed to the tokenizer, it silently stripped `͡` and `ʑ`, leaving only `d` (alveolar plosive /d/).\n* **Result**:\n  - `휴지` (hyu-ji) became `['h', 'j', 'u', 'd', 'i']` $\\rightarrow$ synthesized as **\"휴디\" (hyudi)**.\n  - `타조` (ta-jo) became `['t', 'ʰ', 'a', 'd', 'o']` $\\rightarrow$ synthesized as **\"타도\" (tado)**.\n  - `된장` (doen-jang) became `['t', 'w', 'e', 'n', 'd', 'a', 'ŋ']` $\\rightarrow$ synthesized as **\"된당\" (doendang)**.\n  - `아버지` (a-beo-ji) became `['a', 'b', 'ʌ', 'd', 'i']` $\\rightarrow$ synthesized as **\"아버디\" (abeodi)**.\n* **The Solution**: Kokoro contains the dedicated CJK voiced alveolo-palatal affricate token **`ʥ` (Token 19)** (the same token used by Misaki for Japanese `ジ` and voiced affricates). Mapping voiced `ㅈ` to `ʥ` produces natural affricate voicing: `hjuʥi`, `tʰaʥo`, `twenʥaŋ`, `abʌʥi`.\n\n#### 2. The Aspirated Affricate Tokenization (`ㅊ` $\\rightarrow$ `tʃʰ`)\n* **The Problem**: When `ㅊ` was previously mapped to alveolo-palatal `ʨʰ` (`\\u02A8` + `\\u02B0`), the token sequence `[ʨ, ʰ]` represented an out-of-distribution phoneme combination for Kokoro (as `ʨ` in Kokoro was trained on unaspirated Mandarin/Japanese data). In multi-syllable reduplications like `차차` (`ʨʰaʨʰa`), the neural acoustic model split the phonemes into a high-palatal glide and disconnected breath puff, mispronouncing it as **\"ye ha chul\"** and severely stretching the initial syllable (4.67s).\n* **The Solution**: Mapping `ㅊ` to **`tʃʰ` (Tokens 62 + 131 + 162)** maps to Kokoro's native aspirated postalveolar affricate representation (as generated by eSpeak for \"ch\"). This produces crisp, natural articulation across initial and medial syllables (`차` → `tʃʰa`, `차차` → `tʃʰatʃʰa`, `친구` → `tʃʰinɡu`, `초코` → `tʃʰokʰo`), reducing syllable duration to a natural ~1.8s.\n\n#### 3. Tension / Glottalization Diacritic Gap (`\\u0348` / `͈`)\n* **The Problem**: The IPA tension mark `\\u0348` (`͈`) is not in Kokoro's vocabulary.\n* **Acoustic Behavior**: When `k͈a` (까) or `s͈a` (싸) is passed, the tokenizer strips `͈` and feeds `k` / `s`. In Kokoro, Asian voice models (e.g. `zf_xiaobei`, `jf_nezumi`) naturally articulate unvoiced initial stops `k`, `t`, `p` with high vocal tract tension compared to intervocalic voiced stops `ɡ`, `d`, `b`, `ʥ`.\n\n#### 4. Unreleased Coda Diacritic Gap (`\\u031a` / `̚`)\n* **The Problem**: The IPA unreleased stop mark `\\u031a` (`̚` as in `k̚, t̚, p̚`) is not in Kokoro's vocabulary.\n* **Acoustic Behavior**: The tokenizer strips `̚` and tokens become `k`, `t`, `p`. Because these tokens reside in syllable coda position before a boundary or subsequent onset, Kokoro's acoustic model naturally decays them without release bursts.\n\n#### 5. Vowel Hiatus & Zero-Onset Syllable Transition (`내일`, `아이`, `오이`)\n* **The Problem**: When a vowel-final syllable is followed by an `ㅇ`-onset syllable (e.g. `내일` $\\rightarrow$ `내` + `일` $\\rightarrow$ `nɛil`), direct concatenation of `ɛ` + `i` without boundary markers causes multilingual acoustic models to fuse the adjacent vowels into a single English-like diphthong (e.g. pronouncing `내일` as the 1-syllable English word \"nail\" /neɪl/). Full punctuation marks like `.` introduce an unnaturally long sentence-level pause (~250ms).\n* **The Solution**: The engine automatically detects vowel hiatus across zero-onset boundaries (`prev.jongIdx === 0 && s.choIdx === 11`) and inserts a secondary stress syllable foot marker `ˌ` (Token ID 161 / `\\u02CC`). This creates a crisp, natural 2-syllable beat without dead silence:\n  - `내일` → `nɛˌil` (0.82s vs 2.08s with `.`)\n  - `오이` → `oˌi`\n  - `아이` → `aˌi`\n  - `좋은` → `ʨoˌɯn`\n\n---\n\n## Unit Testing & Verification\n\nThe engine is covered by **227 automated unit tests** across 22 suites:\n\n```bash\nnpm run test\n# or directly with Node:\nnode --experimental-strip-types --test test/**/*.test.ts\n```\n\n---\n\n## Running the Interactive Demo Playground\n\n```bash\n# 1. Install dependencies\nnpm install\n\n# 2. Run unit tests\nnpm run test\n\n# 3. Start dev server\nnpm run dev\n# Open http://localhost:5173\n\n# 4. Build library package (ESM + CJS + .d.ts)\nnpm run build:lib\n\n# 5. Build demo web app\nnpm run build:demo\n```\n\n---\n\n## References & Standards\n\n- **[Kokoro-82M (hexgrad / Hugging Face)](https://huggingface.co/hexgrad/Kokoro-82M)**: Open-weight 82M parameter multi-lingual neural TTS model.\n- **[Kokoro-82M-v1.0-ONNX (onnx-community)](https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX)**: WebAssembly / WebGPU ONNX weights for in-browser client-side inference.\n- **[국립국어원 표준어 규정 — 제2부 표준 발음법 (Wikisource Full Text)](https://ko.wikisource.org/wiki/%ED%91%9C%EC%A4%80%EC%96%B4_%EA%B7%9C%EC%A0%95#%EC%A0%9C2%EB%B6%80_%ED%91%9C%EC%A4%80_%EB%B0%9C%EC%9D%8C%EB%B2%95)**: Complete official statutory text of the Standard Korean Pronunciation Rules (Articles 1–30, covering liaison, palatalization, aspiration, nasalization, liquid assimilation, tensification, and coda reduction).\n- **[National Institute of Korean Language Portal (국립국어원 한국어 어문 규범)](https://kornorms.korean.go.kr/regltn/regltnView.do?regltn_code=0002)**: Official NIKL regulations and commentary.\n- **[Wikipedia: Korean Phonology](https://en.wikipedia.org/wiki/Korean_phonology)**: Comprehensive linguistic overview of the sound system of modern Korean.\n\n---\n\n## Development & Attribution\n\nThis project and its Korean phonology G2P engine were designed and pair-programmed by **Danny Yoo** in collaboration with **Antigravity** (Google DeepMind).\n\n---\n\n## License\n\nThis project is licensed under the **MIT License**. See [`LICENSE`](LICENSE) for details.\n","readmeFilename":"README.md"}