@onjmin/koe 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  UTAU の oto.ini で定義された音源を `.koe` アーカイブに変換し、WebAssembly + AudioWorklet でリアルタイム再生・高品質再合成を行う。
6
6
 
7
7
  - [DEMO](https://onjmin.github.io/koe/demo) koeフォーマット作成もこちらで
8
- - [npm](https://www.npmjs.com/package/@onjmin/dtm)
8
+ - [npm](https://www.npmjs.com/package/@onjmin/koe)
9
9
 
10
10
  ---
11
11
 
@@ -94,11 +94,13 @@ await fs.writeFile("voice.koe", buf);
94
94
 
95
95
  AudioWorklet を使った連接合成エンジン。`koe-worklet.js` を同じオリジンから配信する必要がある。
96
96
 
97
+ GitHub Pages にホストされているファイルをそのまま使える:
98
+
97
99
  ```ts
98
100
  import { KoeEngine } from "@onjmin/koe";
99
101
 
100
102
  const engine = new KoeEngine({
101
- workletUrl: "/koe-worklet.js", // dist/koe-worklet.js を配信する
103
+ workletUrl: "https://onjmin.github.io/koe/demo/koe-worklet.js",
102
104
  });
103
105
 
104
106
  // .koe ファイルをロード (Blob でも URL でも可)
@@ -163,11 +165,15 @@ bank.has("a"); // boolean
163
165
 
164
166
  OpenUtau の worldline WASM で F0 分析・再合成を行う。`worldline.js` と `worldline.wasm` を配信する必要がある。
165
167
 
168
+ GitHub Pages にホストされているファイルをそのまま使える:
169
+
166
170
  ```ts
167
171
  import { VoiceBank, Worldline, leadInFromEntry } from "@onjmin/koe";
168
172
 
169
173
  const bank = await VoiceBank.load("/voice.koe");
170
- const wl = await Worldline.load({ scriptUrl: "/world/worldline.js" });
174
+ const wl = await Worldline.load({
175
+ scriptUrl: "https://onjmin.github.io/koe/demo/world/worldline.js",
176
+ });
171
177
 
172
178
  const alias = "a";
173
179
  const entry = bank.manifest.phonemes[alias];
package/dist/index.d.ts CHANGED
@@ -155,6 +155,7 @@ type WorldlineFactory = (opts?: {
155
155
  }) => Promise<WorldlineWasm>;
156
156
  declare global {
157
157
  var WorldlineModule: WorldlineFactory | undefined;
158
+ var importScripts: ((...urls: string[]) => void) | undefined;
158
159
  }
159
160
  interface WorldlineLoadOptions {
160
161
  /**
@@ -217,7 +218,13 @@ declare class Worldline {
217
218
  private wasm;
218
219
  readonly sampleRate = 48000;
219
220
  private constructor();
220
- /** Load + instantiate the worldline WASM module (deduped per scriptUrl). */
221
+ /**
222
+ * Load + instantiate the worldline WASM module (deduped per scriptUrl).
223
+ *
224
+ * Works on the main thread (loads via `<script>`) and inside a classic Web
225
+ * Worker (loads via `importScripts`), so the heavy synthesis can run
226
+ * off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.
227
+ */
221
228
  static load(options: WorldlineLoadOptions): Promise<Worldline>;
222
229
  /**
223
230
  * Render one note to Float32 PCM at 48 kHz.
package/dist/index.js CHANGED
@@ -235,22 +235,30 @@ function injectScript(src) {
235
235
  function loadWasm(scriptUrl) {
236
236
  const cached = moduleCache.get(scriptUrl);
237
237
  if (cached) return cached;
238
- if (typeof document === "undefined") {
239
- return Promise.reject(
240
- new Error(
241
- "Worldline.load requires a DOM (browser) environment to load worldline.js"
242
- )
243
- );
244
- }
245
238
  const baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf("/") + 1);
246
- const promise = injectScript(scriptUrl).then(() => {
239
+ const instantiate = () => {
247
240
  const factory = globalThis.WorldlineModule;
248
241
  if (!factory)
249
242
  throw new Error(
250
243
  "worldline: WorldlineModule global was not defined by the script"
251
244
  );
252
245
  return factory({ locateFile: (f) => baseUrl + f });
253
- });
246
+ };
247
+ let promise;
248
+ if (typeof document !== "undefined") {
249
+ promise = injectScript(scriptUrl).then(instantiate);
250
+ } else if (typeof globalThis.importScripts === "function") {
251
+ promise = Promise.resolve().then(() => {
252
+ globalThis.importScripts(scriptUrl);
253
+ return instantiate();
254
+ });
255
+ } else {
256
+ return Promise.reject(
257
+ new Error(
258
+ "Worldline.load requires a DOM or a classic Web Worker (importScripts) to load worldline.js"
259
+ )
260
+ );
261
+ }
254
262
  moduleCache.set(scriptUrl, promise);
255
263
  return promise;
256
264
  }
@@ -260,7 +268,13 @@ var Worldline = class _Worldline {
260
268
  }
261
269
  wasm;
262
270
  sampleRate = WORLDLINE_SAMPLE_RATE;
263
- /** Load + instantiate the worldline WASM module (deduped per scriptUrl). */
271
+ /**
272
+ * Load + instantiate the worldline WASM module (deduped per scriptUrl).
273
+ *
274
+ * Works on the main thread (loads via `<script>`) and inside a classic Web
275
+ * Worker (loads via `importScripts`), so the heavy synthesis can run
276
+ * off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.
277
+ */
264
278
  static async load(options) {
265
279
  return new _Worldline(await loadWasm(options.scriptUrl));
266
280
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/koe.ts","../src/engine/voice-bank.ts","../src/engine/index.ts","../src/engine/worldline.ts","../src/converter/parse-oto.ts","../src/converter/wav.ts","../src/converter/pitch.ts","../src/converter/pack.ts","../src/converter/frq.ts"],"sourcesContent":["import type { Manifest } from \"./types.js\";\n\n/** Magic number 'KOE\\0' (stored big-endian as 0x4B 0x4F 0x45 0x00). */\nconst MAGIC = 0x4b4f4500;\n\n/**\n * Koe Archive Format (.koe)\n * [4B] magic 'KOE\\0' (big-endian)\n * [4B] JSON length (little-endian)\n * [N ] manifest JSON (UTF-8)\n * [M ] raw PCM (Int16 / 48kHz / mono); phoneme offsets are relative to here\n */\nexport function packKoe(manifest: Manifest, pcmParts: BlobPart[]): Blob {\n\tconst json = new TextEncoder().encode(JSON.stringify(manifest));\n\tconst header = new ArrayBuffer(8);\n\tconst view = new DataView(header);\n\tview.setUint32(0, MAGIC, false); // magic, big-endian\n\tview.setUint32(4, json.byteLength, true); // json length, little-endian\n\treturn new Blob([header, json, ...pcmParts]);\n}\n\n/** Read the 8-byte header → JSON length. Throws on bad magic. */\nexport function parseKoeHeader(headerBytes: ArrayBuffer): {\n\tjsonLength: number;\n} {\n\tconst view = new DataView(headerBytes);\n\tif (view.byteLength < 8 || view.getUint32(0, false) !== MAGIC) {\n\t\tthrow new Error(\"Not a .koe file (bad magic)\");\n\t}\n\treturn { jsonLength: view.getUint32(4, true) };\n}\n\n/** Byte offset where PCM data begins, given the JSON length. */\nexport const pcmBase = (jsonLength: number): number => 8 + jsonLength;\n","import type { Manifest } from \"../types.js\";\nimport { parseKoeHeader, pcmBase } from \"../koe.js\";\n\n/**\n * Supplies raw bytes from the PCM section of a .koe archive on demand.\n * - BlobVoiceSource: slices an in-memory Blob / File (no full-buffer copy)\n * - RangeVoiceSource: HTTP Range requests against a URL (mobile-friendly)\n * `base` is the byte offset where PCM data starts (8 + jsonLength).\n */\ninterface VoiceSource {\n\treadBytes(offset: number, length: number): Promise<ArrayBuffer>;\n}\n\nclass BlobVoiceSource implements VoiceSource {\n\tconstructor(\n\t\tprivate blob: Blob,\n\t\tprivate base: number,\n\t) {}\n\treadBytes(offset: number, length: number): Promise<ArrayBuffer> {\n\t\tconst start = this.base + offset;\n\t\treturn this.blob.slice(start, start + length).arrayBuffer();\n\t}\n}\n\nclass RangeVoiceSource implements VoiceSource {\n\tconstructor(\n\t\tprivate url: string,\n\t\tprivate base: number,\n\t) {}\n\tasync readBytes(offset: number, length: number): Promise<ArrayBuffer> {\n\t\tconst start = this.base + offset;\n\t\tconst res = await fetch(this.url, {\n\t\t\theaders: { Range: `bytes=${start}-${start + length - 1}` },\n\t\t});\n\t\tif (!res.ok && res.status !== 206) {\n\t\t\tthrow new Error(`.koe range request failed: ${res.status}`);\n\t\t}\n\t\treturn res.arrayBuffer();\n\t}\n}\n\nasync function rangeFetch(\n\turl: string,\n\tstart: number,\n\tlength: number,\n): Promise<ArrayBuffer> {\n\tconst res = await fetch(url, {\n\t\theaders: { Range: `bytes=${start}-${start + length - 1}` },\n\t});\n\tif (!res.ok && res.status !== 206)\n\t\tthrow new Error(`.koe fetch failed: ${res.status}`);\n\treturn res.arrayBuffer();\n}\n\n/**\n * Read-only access to a .koe voice bank: its manifest plus per-phoneme PCM,\n * fetched on demand (Blob slice or HTTP Range). The full bank is never held in\n * memory.\n *\n * Pure data — no AudioContext, no AudioWorklet, no DOM. Use this when you only\n * need the source samples (e.g. to feed the {@link Worldline} renderer or any\n * other vocoder). {@link KoeEngine} builds its concatenative playback on top of\n * this same class.\n *\n * const bank = await VoiceBank.load(koeBlobOrUrl);\n * const pcm = await bank.getPcm('a'); // Float64 [-1, 1]\n */\nexport class VoiceBank {\n\tprivate constructor(\n\t\t/** The voice bank manifest (sample rate, reference pitch, phoneme table). */\n\t\treadonly manifest: Manifest,\n\t\tprivate source: VoiceSource,\n\t) {}\n\n\t/**\n\t * Parse a .koe archive header + manifest and bind a lazy PCM source.\n\t * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)\n\t */\n\tstatic async load(koe: Blob | string): Promise<VoiceBank> {\n\t\tif (typeof koe === \"string\") {\n\t\t\tconst header = await rangeFetch(koe, 0, 8);\n\t\t\tconst { jsonLength } = parseKoeHeader(header);\n\t\t\tconst json = await rangeFetch(koe, 8, jsonLength);\n\t\t\tconst manifest = JSON.parse(new TextDecoder().decode(json)) as Manifest;\n\t\t\treturn new VoiceBank(\n\t\t\t\tmanifest,\n\t\t\t\tnew RangeVoiceSource(koe, pcmBase(jsonLength)),\n\t\t\t);\n\t\t}\n\t\tconst header = await koe.slice(0, 8).arrayBuffer();\n\t\tconst { jsonLength } = parseKoeHeader(header);\n\t\tconst json = await koe.slice(8, 8 + jsonLength).arrayBuffer();\n\t\tconst manifest = JSON.parse(new TextDecoder().decode(json)) as Manifest;\n\t\treturn new VoiceBank(\n\t\t\tmanifest,\n\t\t\tnew BlobVoiceSource(koe, pcmBase(jsonLength)),\n\t\t);\n\t}\n\n\t/** True if the bank contains a phoneme under this alias. */\n\thas(phoneme: string): boolean {\n\t\treturn this.manifest.phonemes[phoneme] !== undefined;\n\t}\n\n\t/**\n\t * Raw Int16 PCM bytes (48 kHz / mono) for a phoneme, or null if unknown.\n\t * The returned ArrayBuffer is freshly allocated and safe to transfer to a\n\t * worker / AudioWorklet.\n\t */\n\tasync readPcmBytes(phoneme: string): Promise<ArrayBuffer | null> {\n\t\tconst entry = this.manifest.phonemes[phoneme];\n\t\tif (!entry) return null;\n\t\treturn this.source.readBytes(entry.offset, entry.length * 2); // Int16 = 2 bytes/sample\n\t}\n\n\t/**\n\t * A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.\n\t * Intended for external analysis / resynthesis such as the WORLD vocoder.\n\t */\n\tasync getPcm(phoneme: string): Promise<Float64Array | null> {\n\t\tconst buf = await this.readPcmBytes(phoneme);\n\t\tif (!buf) return null;\n\t\tconst int16 = new Int16Array(buf);\n\t\tconst f64 = new Float64Array(int16.length);\n\t\tfor (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;\n\t\treturn f64;\n\t}\n}\n","import type { Manifest, NoteEvent } from \"../types.js\";\nimport { VoiceBank } from \"./voice-bank.js\";\n\nexport type { NoteEvent };\n\nexport interface KoeEngineOptions {\n\t/** URL to koe-worklet.js. Defaults to './koe-worklet.js'. */\n\tworkletUrl?: string;\n}\n\n/**\n * Main-thread API for the koe concatenative synthesis engine.\n *\n * Loads a single .koe archive. The full voice bank is never held in memory:\n * phonemes are fetched on demand (via {@link VoiceBank}) and cached in the\n * audio thread.\n *\n * const engine = new KoeEngine();\n * await engine.load(koeBlobOrUrl);\n * await engine.play([{ phoneme: 'a', pitch: 440, duration: 48000 }]);\n *\n * For analysis / resynthesis (e.g. {@link Worldline}) you usually only need the\n * raw samples — use {@link VoiceBank} directly instead, which needs no\n * AudioContext or worklet.\n */\nexport class KoeEngine {\n\tprivate ctx: AudioContext;\n\tprivate workletUrl: string;\n\tprivate node: AudioWorkletNode | null = null;\n\tprivate bank: VoiceBank | null = null;\n\tprivate delivered = new Set<string>();\n\tprivate pending = new Map<string, Promise<void>>();\n\n\tconstructor(options: KoeEngineOptions = {}) {\n\t\tthis.ctx = new AudioContext({ sampleRate: 48000 });\n\t\tthis.workletUrl = options.workletUrl ?? \"./koe-worklet.js\";\n\t}\n\n\tget audioContext(): AudioContext {\n\t\treturn this.ctx;\n\t}\n\n\tget manifest(): Manifest | null {\n\t\treturn this.bank?.manifest ?? null;\n\t}\n\n\t/** The underlying voice bank (manifest + on-demand PCM), or null before load(). */\n\tget voiceBank(): VoiceBank | null {\n\t\treturn this.bank;\n\t}\n\n\t/**\n\t * Register the worklet and bind a .koe voice bank.\n\t * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)\n\t */\n\tasync load(koe: Blob | string): Promise<void> {\n\t\tawait this.ctx.audioWorklet.addModule(this.workletUrl);\n\n\t\tthis.bank = await VoiceBank.load(koe);\n\t\tthis.delivered.clear();\n\t\tthis.pending.clear();\n\n\t\tthis.node = new AudioWorkletNode(this.ctx, \"koe-processor\", {\n\t\t\tnumberOfInputs: 0,\n\t\t\tnumberOfOutputs: 1,\n\t\t\toutputChannelCount: [1],\n\t\t});\n\t\tthis.node.port.postMessage({ type: \"init\", manifest: this.bank.manifest });\n\t\tthis.node.connect(this.ctx.destination);\n\n\t\tconsole.log(\n\t\t\t\"[koe] ready —\",\n\t\t\tObject.keys(this.bank.manifest.phonemes).length,\n\t\t\t\"phonemes (on-demand)\",\n\t\t);\n\t}\n\n\t/** Fetch one phoneme's PCM and deliver it to the worklet (deduped, cached). */\n\tprivate ensurePhoneme(name: string): Promise<void> {\n\t\tif (this.delivered.has(name)) return Promise.resolve();\n\t\tconst existing = this.pending.get(name);\n\t\tif (existing) return existing;\n\t\tif (!this.bank || !this.node) return Promise.resolve();\n\n\t\tconst load = this.bank.readPcmBytes(name).then((buf) => {\n\t\t\tif (!buf) return;\n\t\t\tthis.node!.port.postMessage({ type: \"phoneme\", name, buffer: buf }, [\n\t\t\t\tbuf,\n\t\t\t]);\n\t\t\tthis.delivered.add(name);\n\t\t\tthis.pending.delete(name);\n\t\t});\n\n\t\tthis.pending.set(name, load);\n\t\treturn load;\n\t}\n\n\t/** Stop current playback, preload the phonemes for `notes`, then queue them. */\n\tasync play(notes: NoteEvent[]): Promise<void> {\n\t\tif (!this.node) throw new Error(\"KoeEngine: call load() before play()\");\n\t\tthis.node.port.postMessage({ type: \"stop\" });\n\t\tconst names = [...new Set(notes.map((n) => n.phoneme))].filter(Boolean);\n\t\tawait Promise.all(names.map((n) => this.ensurePhoneme(n)));\n\t\tthis.node.port.postMessage({ type: \"play\", notes });\n\t}\n\n\t/** Stop playback and clear the queue. */\n\tstop(): void {\n\t\tthis.node?.port.postMessage({ type: \"stop\" });\n\t}\n\n\t/** Resume the AudioContext if suspended (e.g. after autoplay block). */\n\tasync resume(): Promise<void> {\n\t\tif (this.ctx.state === \"suspended\") await this.ctx.resume();\n\t}\n\n\t/**\n\t * Read a phoneme's raw PCM and return it as a Float64Array normalised to\n\t * [-1, 1]. Convenience that forwards to the underlying {@link VoiceBank}.\n\t * Intended for external analysis such as the WORLD vocoder.\n\t */\n\tasync getPcm(phoneme: string): Promise<Float64Array | null> {\n\t\treturn this.bank?.getPcm(phoneme) ?? null;\n\t}\n}\n","import type { PhonemeEntry } from \"../types.js\";\n\n/** Output sample rate of the worldline synthesizer. */\nexport const WORLDLINE_SAMPLE_RATE = 48000;\n\n/**\n * WORLD needs roughly 85 ms of audio for stable F0 analysis. Phonemes with\n * fewer samples than this are rejected (renderNote returns null).\n */\nexport const MIN_WORLDLINE_SAMPLES = 4096;\n\n/** SynthRequest struct layout — 120 bytes, WASM32. */\nconst SYNTH_REQ_SIZE = 120;\n/** Frame period hardcoded in worldline's phrase_synth.cpp. */\nconst WL_FRAME_MS = 10;\n\n/**\n * The subset of the Emscripten module surface that we call. worldline.js is an\n * MODULARIZE=1 / EXPORT_NAME=WorldlineModule build of OpenUtau's worldline.\n */\ninterface WorldlineWasm {\n\t_PhraseSynthNew(): number;\n\t_PhraseSynthDelete(ps: number): void;\n\t_PhraseSynthAddRequest(\n\t\tps: number,\n\t\treq: number,\n\t\tposMs: number,\n\t\tskipMs: number,\n\t\tlengthMs: number,\n\t\tfadeInMs: number,\n\t\tfadeOutMs: number,\n\t\tflag: number,\n\t): void;\n\t_PhraseSynthSetCurves(\n\t\tps: number,\n\t\tf0: number,\n\t\tgender: number,\n\t\ttension: number,\n\t\tbreathiness: number,\n\t\tvoicing: number,\n\t\tlength: number,\n\t\tframeMs: number,\n\t): void;\n\t_PhraseSynthSynth(ps: number, yPtrPtr: number, flag: number): number;\n\t_malloc(size: number): number;\n\t_free(ptr: number): void;\n\tsetValue(ptr: number, value: number, type: string): void;\n\tgetValue(ptr: number, type: string): number;\n\tHEAPF32: Float32Array;\n\tHEAPF64: Float64Array;\n}\n\ntype WorldlineFactory = (opts?: {\n\tlocateFile?: (path: string) => string;\n}) => Promise<WorldlineWasm>;\n\ndeclare global {\n\t// Injected by worldline.js when loaded as a classic <script>.\n\t// eslint-disable-next-line no-var\n\tvar WorldlineModule: WorldlineFactory | undefined;\n}\n\nexport interface WorldlineLoadOptions {\n\t/**\n\t * URL of `worldline.js` (the Emscripten loader). The matching\n\t * `worldline.wasm` must sit next to it — it is resolved relative to this URL.\n\t *\n\t * When consuming koe from npm + a CDN this is typically e.g.\n\t * `https://cdn.jsdelivr.net/npm/@onjmin/koe/dist/world/worldline.js`, or your\n\t * own hosted copy of `dist/world/`.\n\t */\n\tscriptUrl: string;\n}\n\nexport interface RenderNoteParams {\n\t/**\n\t * Source phoneme PCM normalised to [-1, 1] (e.g. from\n\t * `VoiceBank.getPcm()` / `KoeEngine.getPcm()`).\n\t */\n\tpcm: Float64Array;\n\t/** Target output pitch in Hz. */\n\tpitch: number;\n\t/** Sustain / vowel duration in ms (the lead-in below is rendered on top). */\n\tdurationMs: number;\n\t/** Preutterance / lead-in in ms — convert from {@link PhonemeEntry.pre}. */\n\tpreMs: number;\n\t/** Consonant length in ms — convert from {@link PhonemeEntry.consonant}. */\n\tconsonantMs: number;\n\t/** Reference tempo in BPM for worldline's internal timing. Default 120. */\n\ttempo?: number;\n}\n\n/** Convert a sample count at 48 kHz to milliseconds. */\nexport const samplesToMs = (samples: number): number =>\n\t(samples / WORLDLINE_SAMPLE_RATE) * 1000;\n\n/**\n * Derive {@link RenderNoteParams} lead-in / consonant fields from a manifest\n * entry, so callers don't repeat the sample→ms conversion.\n *\n * const params = { pcm, pitch, durationMs, ...leadInFromEntry(entry) };\n */\nexport function leadInFromEntry(entry: PhonemeEntry): {\n\tpreMs: number;\n\tconsonantMs: number;\n} {\n\treturn {\n\t\tpreMs: samplesToMs(entry.pre || 0),\n\t\tconsonantMs: samplesToMs(entry.consonant || 0),\n\t};\n}\n\n// Cache module factories by script URL so repeated loads share one WASM instance.\nconst moduleCache = new Map<string, Promise<WorldlineWasm>>();\n\nfunction injectScript(src: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst existing = document.querySelector<HTMLScriptElement>(\n\t\t\t`script[data-koe-worldline=\"${src}\"]`,\n\t\t);\n\t\tif (existing) {\n\t\t\tresolve();\n\t\t\treturn;\n\t\t}\n\t\tconst s = document.createElement(\"script\");\n\t\ts.src = src;\n\t\ts.dataset.koeWorldline = src;\n\t\ts.onload = () => resolve();\n\t\ts.onerror = () => reject(new Error(`worldline: failed to load ${src}`));\n\t\tdocument.head.appendChild(s);\n\t});\n}\n\nfunction loadWasm(scriptUrl: string): Promise<WorldlineWasm> {\n\tconst cached = moduleCache.get(scriptUrl);\n\tif (cached) return cached;\n\n\tif (typeof document === \"undefined\") {\n\t\treturn Promise.reject(\n\t\t\tnew Error(\n\t\t\t\t\"Worldline.load requires a DOM (browser) environment to load worldline.js\",\n\t\t\t),\n\t\t);\n\t}\n\n\tconst baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf(\"/\") + 1);\n\tconst promise = injectScript(scriptUrl).then(() => {\n\t\tconst factory = globalThis.WorldlineModule;\n\t\tif (!factory)\n\t\t\tthrow new Error(\n\t\t\t\t\"worldline: WorldlineModule global was not defined by the script\",\n\t\t\t);\n\t\treturn factory({ locateFile: (f: string) => baseUrl + f });\n\t});\n\tmoduleCache.set(scriptUrl, promise);\n\treturn promise;\n}\n\n/**\n * High-quality WORLD-vocoder note renderer (OpenUtau's worldline via WASM).\n *\n * Pure synthesis: PCM in → PCM out. No AudioContext, no scheduling — the caller\n * owns playback (schedule the returned buffer on its own timeline). Pair it with\n * {@link VoiceBank} for the source samples:\n *\n * const bank = await VoiceBank.load(koeUrl);\n * const wl = await Worldline.load({ scriptUrl: '.../world/worldline.js' });\n *\n * const entry = bank.manifest.phonemes[alias];\n * const pcm = await bank.getPcm(alias);\n * const audio = wl.renderNote({\n * pcm, pitch: 440, durationMs: 500, ...leadInFromEntry(entry),\n * });\n * // audio: Float32 @ 48 kHz, layout [lead-in/consonant ≈ preMs][vowel ≈ durationMs]\n */\nexport class Worldline {\n\treadonly sampleRate = WORLDLINE_SAMPLE_RATE;\n\n\tprivate constructor(private wasm: WorldlineWasm) {}\n\n\t/** Load + instantiate the worldline WASM module (deduped per scriptUrl). */\n\tstatic async load(options: WorldlineLoadOptions): Promise<Worldline> {\n\t\treturn new Worldline(await loadWasm(options.scriptUrl));\n\t}\n\n\t/**\n\t * Render one note to Float32 PCM at 48 kHz.\n\t *\n\t * The output buffer is laid out as [lead-in/consonant ≈ preMs][vowel ≈\n\t * durationMs], rendered from sample offset 0 (no leading silence). The vowel\n\t * onset (the \"beat\") sits at ≈ preMs into the buffer, so a sequencer should\n\t * place the buffer at `beatTime − preMs` and may trim/crossfade the lead-in.\n\t *\n\t * No internal crossfade is applied — apply fades externally.\n\t *\n\t * @returns Float32 PCM, or null when `pcm` is shorter than\n\t * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).\n\t */\n\trenderNote(params: RenderNoteParams): Float32Array | null {\n\t\tconst { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;\n\t\tif (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;\n\n\t\tconst WL = this.wasm;\n\t\tconst FS = WORLDLINE_SAMPLE_RATE;\n\t\tconst midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));\n\t\tconst posMs = 0; // no leading silence\n\t\tconst reqLen = preMs + durationMs; // render lead-in/consonant + vowel\n\n\t\t// cut_off must trim at least ~2 frames off the tail. PhraseSynth::AddRequest\n\t\t// computes the trim frame count as ceil(in_length_ms)/frame_ms, but\n\t\t// PyinEstimator yields floor(N/nhop)−1 frames — one fewer. With cut_off=0 the\n\t\t// requested frame count exceeds f0_.size() and Model::Trim's f0_.erase() runs\n\t\t// past end(), causing \"memory access out of bounds\". Trimming 2 frames keeps\n\t\t// it within bounds.\n\t\tconst cutMs = WL_FRAME_MS * 2;\n\n\t\tconst ps = WL._PhraseSynthNew();\n\t\tif (!ps) return null;\n\t\tconst reqPtr = WL._malloc(SYNTH_REQ_SIZE);\n\t\tif (!reqPtr) {\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tconst samplePtr = WL._malloc(pcm.length * 8);\n\t\tif (!samplePtr) {\n\t\t\tWL._free(reqPtr);\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tWL.HEAPF64.set(pcm, samplePtr >> 3);\n\n\t\tconst sv = (off: number, val: number, type: string) =>\n\t\t\tWL.setValue(reqPtr + off, val, type);\n\t\tsv(0, FS, \"i32\");\n\t\tsv(4, pcm.length, \"i32\");\n\t\tsv(8, samplePtr, \"*\");\n\t\tsv(12, 0, \"i32\");\n\t\tsv(16, 0, \"*\");\n\t\tsv(20, midiNote, \"i32\");\n\t\tsv(24, 100.0, \"double\");\n\t\tsv(32, 0.0, \"double\");\n\t\tsv(40, reqLen, \"double\");\n\t\tsv(48, consonantMs, \"double\");\n\t\tsv(56, cutMs, \"double\");\n\t\tsv(64, 100.0, \"double\");\n\t\tsv(72, 0.0, \"double\");\n\t\tsv(80, tempo, \"double\");\n\t\tsv(88, 0, \"i32\");\n\t\tsv(92, 0, \"*\");\n\t\tsv(96, 0, \"i32\");\n\t\tsv(100, 0, \"i32\");\n\t\tsv(104, 100, \"i32\");\n\t\tsv(108, 0, \"i32\");\n\t\tsv(112, 0, \"i32\");\n\t\tsv(116, 100, \"i32\");\n\n\t\t// skip/fadeIn/fadeOut all 0 — crossfade is handled externally.\n\t\tWL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0.0, reqLen, 0.0, 0.0, 0);\n\t\tWL._free(samplePtr);\n\t\tWL._free(reqPtr);\n\n\t\tconst totalMs = posMs + reqLen + WL_FRAME_MS * 2;\n\t\tconst nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;\n\t\tconst f0Arr = new Float64Array(nFrames).fill(pitch);\n\t\tconst gArr = new Float64Array(nFrames).fill(0.5);\n\t\tconst tArr = new Float64Array(nFrames).fill(0.5);\n\t\tconst bArr = new Float64Array(nFrames).fill(0.5);\n\t\tconst vArr = new Float64Array(nFrames).fill(1.0);\n\n\t\tconst f0Ptr = WL._malloc(nFrames * 8);\n\t\tconst gPtr = WL._malloc(nFrames * 8);\n\t\tconst tPtr = WL._malloc(nFrames * 8);\n\t\tconst bPtr = WL._malloc(nFrames * 8);\n\t\tconst vPtr = WL._malloc(nFrames * 8);\n\t\tif (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {\n\t\t\tif (f0Ptr) WL._free(f0Ptr);\n\t\t\tif (gPtr) WL._free(gPtr);\n\t\t\tif (tPtr) WL._free(tPtr);\n\t\t\tif (bPtr) WL._free(bPtr);\n\t\t\tif (vPtr) WL._free(vPtr);\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tWL.HEAPF64.set(f0Arr, f0Ptr >> 3);\n\t\tWL.HEAPF64.set(gArr, gPtr >> 3);\n\t\tWL.HEAPF64.set(tArr, tPtr >> 3);\n\t\tWL.HEAPF64.set(bArr, bPtr >> 3);\n\t\tWL.HEAPF64.set(vArr, vPtr >> 3);\n\t\tWL._PhraseSynthSetCurves(\n\t\t\tps,\n\t\t\tf0Ptr,\n\t\t\tgPtr,\n\t\t\ttPtr,\n\t\t\tbPtr,\n\t\t\tvPtr,\n\t\t\tnFrames,\n\t\t\tWL_FRAME_MS,\n\t\t);\n\t\tWL._free(f0Ptr);\n\t\tWL._free(gPtr);\n\t\tWL._free(tPtr);\n\t\tWL._free(bPtr);\n\t\tWL._free(vPtr);\n\n\t\tconst yPtrPtr = WL._malloc(4);\n\t\tif (!yPtrPtr) {\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tconst outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);\n\t\tconst yPtr = WL.getValue(yPtrPtr, \"*\");\n\t\tconst audio =\n\t\t\toutLen > 0\n\t\t\t\t? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice()\n\t\t\t\t: null;\n\t\tWL._free(yPtrPtr);\n\t\tWL._PhraseSynthDelete(ps);\n\t\treturn audio;\n\t}\n}\n","export interface OtoEntry {\n\t/** Source WAV filename */\n\twav: string;\n\t/** Phoneme alias */\n\talias: string;\n\t/** Left blank — offset from WAV start (ms) */\n\toffset: number;\n\t/** Consonant portion end from offset (ms) */\n\tconsonant: number;\n\t/** Right blank — negative = from WAV end, positive = from offset (ms) */\n\tcutoff: number;\n\t/** Preutterance from offset (ms) */\n\tpre: number;\n\t/** Overlap / crossfade region (ms) */\n\toverlap: number;\n}\n\n/**\n * Parse oto.ini content (already decoded to UTF-8 string).\n * Silently skips malformed lines.\n */\nexport function parseOto(content: string): OtoEntry[] {\n\tconst entries: OtoEntry[] = [];\n\n\tfor (const raw of content.split(/\\r?\\n/)) {\n\t\tconst line = raw.trim();\n\t\tif (!line || line.startsWith(\"#\")) continue;\n\n\t\tconst eq = line.indexOf(\"=\");\n\t\tif (eq === -1) continue;\n\n\t\tconst wav = line.slice(0, eq).trim();\n\t\tconst parts = line.slice(eq + 1).split(\",\");\n\t\tif (parts.length < 6) continue;\n\n\t\tconst [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] =\n\t\t\tparts;\n\t\t// Empty alias → use filename without extension (UTAU spec default)\n\t\tconst aliasStr = alias.trim() || wav.replace(/\\.[^.]+$/, \"\");\n\t\tconst entry: OtoEntry = {\n\t\t\twav,\n\t\t\talias: aliasStr,\n\t\t\toffset: parseFloat(offsetStr) || 0,\n\t\t\tconsonant: parseFloat(consonantStr) || 0,\n\t\t\tcutoff: parseFloat(cutoffStr) || 0,\n\t\t\tpre: parseFloat(preStr) || 0,\n\t\t\toverlap: parseFloat(overlapStr) || 0,\n\t\t};\n\n\t\tif (!entry.alias) continue;\n\t\tentries.push(entry);\n\t}\n\n\treturn entries;\n}\n","export interface WavData {\n\tsampleRate: number;\n\tchannels: number;\n\t/** Normalized samples in [-1, 1], interleaved if multi-channel */\n\tsamples: Float32Array;\n}\n\n/** Parse a WAV file from an ArrayBuffer. Supports PCM 8/16/24-bit and IEEE float 32-bit. */\nexport function parseWav(buf: ArrayBuffer): WavData {\n\tconst view = new DataView(buf);\n\n\tconst riff = readFourCC(view, 0);\n\tif (riff !== \"RIFF\") throw new Error(`Not a RIFF file (got \"${riff}\")`);\n\n\tlet sampleRate = 0;\n\tlet channels = 0;\n\tlet bitsPerSample = 0;\n\tlet audioFormat = 1;\n\tlet dataOffset = 0;\n\tlet dataLength = 0;\n\n\tlet pos = 12;\n\twhile (pos < view.byteLength - 8) {\n\t\tconst id = readFourCC(view, pos);\n\t\tconst size = view.getUint32(pos + 4, true);\n\t\tpos += 8;\n\n\t\tif (id === \"fmt \") {\n\t\t\taudioFormat = view.getUint16(pos, true);\n\t\t\tchannels = view.getUint16(pos + 2, true);\n\t\t\tsampleRate = view.getUint32(pos + 4, true);\n\t\t\tbitsPerSample = view.getUint16(pos + 14, true);\n\t\t} else if (id === \"data\") {\n\t\t\tdataOffset = pos;\n\t\t\tdataLength = size;\n\t\t\tbreak;\n\t\t}\n\n\t\tpos += size + (size & 1);\n\t}\n\n\tif (!dataOffset) throw new Error(\"WAV has no data chunk\");\n\tif (!channels || !sampleRate) throw new Error(\"WAV fmt chunk missing\");\n\n\tconst bytesPerSample = bitsPerSample >> 3;\n\tconst totalSamples = Math.floor(dataLength / bytesPerSample);\n\tconst samples = new Float32Array(totalSamples);\n\n\tfor (let i = 0; i < totalSamples; i++) {\n\t\tconst p = dataOffset + i * bytesPerSample;\n\t\tif (audioFormat === 3) {\n\t\t\tsamples[i] = view.getFloat32(p, true);\n\t\t} else if (bitsPerSample === 8) {\n\t\t\tsamples[i] = (view.getUint8(p) - 128) / 128;\n\t\t} else if (bitsPerSample === 16) {\n\t\t\tsamples[i] = view.getInt16(p, true) / 32768;\n\t\t} else if (bitsPerSample === 24) {\n\t\t\tconst lo = view.getUint8(p) | (view.getUint8(p + 1) << 8);\n\t\t\tlet hi = view.getUint8(p + 2);\n\t\t\tif (hi & 0x80) hi = hi | 0xffffff00;\n\t\t\tsamples[i] = ((hi << 16) | lo) / 8388608;\n\t\t}\n\t}\n\n\treturn { sampleRate, channels, samples };\n}\n\n/** Mix down to mono by averaging all channels. */\nexport function toMono(wav: WavData): WavData {\n\tif (wav.channels === 1) return wav;\n\tconst len = wav.samples.length / wav.channels;\n\tconst out = new Float32Array(len);\n\tfor (let i = 0; i < len; i++) {\n\t\tlet sum = 0;\n\t\tfor (let c = 0; c < wav.channels; c++)\n\t\t\tsum += wav.samples[i * wav.channels + c];\n\t\tout[i] = sum / wav.channels;\n\t}\n\treturn { sampleRate: wav.sampleRate, channels: 1, samples: out };\n}\n\n/** Linear interpolation resample to targetRate. Expects mono input. */\nexport function resample(wav: WavData, targetRate: number): WavData {\n\tif (wav.sampleRate === targetRate) return wav;\n\tconst ratio = wav.sampleRate / targetRate;\n\tconst outLen = Math.floor(wav.samples.length / ratio);\n\tconst out = new Float32Array(outLen);\n\tconst src = wav.samples;\n\tfor (let i = 0; i < outLen; i++) {\n\t\tconst x = i * ratio;\n\t\tconst xi = Math.floor(x);\n\t\tconst frac = x - xi;\n\t\tout[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;\n\t}\n\treturn { sampleRate: targetRate, channels: 1, samples: out };\n}\n\n/** Convert Float32 [-1,1] samples to Int16 PCM. */\nexport function toInt16(samples: Float32Array): Int16Array {\n\tconst out = new Int16Array(samples.length);\n\tfor (let i = 0; i < samples.length; i++) {\n\t\tout[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);\n\t}\n\treturn out;\n}\n\n/** Normalize then convert a WAV to 48kHz/16bit/mono Int16 PCM. */\nexport function normalizePcm(buf: ArrayBuffer): Int16Array {\n\tconst wav = parseWav(buf);\n\tconst mono = toMono(wav);\n\tconst resampled = resample(mono, 48000);\n\treturn toInt16(resampled.samples);\n}\n\nfunction readFourCC(view: DataView, pos: number): string {\n\treturn String.fromCharCode(\n\t\tview.getUint8(pos),\n\t\tview.getUint8(pos + 1),\n\t\tview.getUint8(pos + 2),\n\t\tview.getUint8(pos + 3),\n\t);\n}\n","const SAMPLE_RATE = 48000;\n\nconst NAME_SEMITONE: Record<string, number> = {\n\tc: 0,\n\td: 2,\n\te: 4,\n\tf: 5,\n\tg: 7,\n\ta: 9,\n\tb: 11,\n};\n\n/** Parse a note name like \"E4\", \"G#4\", \"Db5\" → frequency in Hz (null if invalid). */\nexport function noteNameToHz(name: string): number | null {\n\tconst m = /^([A-Ga-g])([#b]?)(-?\\d+)$/.exec(name);\n\tif (!m) return null;\n\tlet semi = NAME_SEMITONE[m[1].toLowerCase()];\n\tif (m[2] === \"#\") semi++;\n\telse if (m[2] === \"b\") semi--;\n\tconst midi = (parseInt(m[3], 10) + 1) * 12 + semi;\n\treturn 440 * 2 ** ((midi - 69) / 12);\n}\n\n/** Recorded pitch encoded in a multi-pitch alias suffix: \"a い_E4\" → 329.63 Hz. */\nexport function pitchFromAliasSuffix(alias: string): number | null {\n\tconst m = /_([A-Ga-g][#b]?-?\\d+)$/.exec(alias);\n\treturn m ? noteNameToHz(m[1]) : null;\n}\n\n/**\n * Estimate the fundamental frequency (Hz) of a voiced region by normalized\n * autocorrelation. Returns 0 when no clear pitch is found (unvoiced consonant,\n * silence, or a region too short to analyse).\n *\n * The signal is decimated to a lower analysis rate for speed; f0 below ~700 Hz\n * is well within the resulting Nyquist limit. A parabolic interpolation around\n * the best lag gives sub-sample (sub-semitone) accuracy.\n */\nexport function detectF0(pcm: Int16Array, start: number, end: number): number {\n\tconst DECIM = 4;\n\tconst sr = SAMPLE_RATE / DECIM; // 12 kHz analysis rate\n\tconst minLag = Math.floor(sr / 700); // highest f0 we look for\n\tconst maxLag = Math.floor(sr / 70); // lowest f0 we look for\n\n\tconst outLen = Math.floor((end - start) / DECIM);\n\tif (outLen < maxLag + 2) return 0;\n\tconst win = Math.min(outLen, 1500); // up to ~0.125 s of vowel\n\n\t// Decimate (box-average) + remove DC, and build a prefix sum of squares so\n\t// each lag's window energy is O(1).\n\tconst buf = new Float32Array(win);\n\tlet mean = 0;\n\tfor (let i = 0; i < win; i++) {\n\t\tlet s = 0;\n\t\tconst base = start + i * DECIM;\n\t\tfor (let j = 0; j < DECIM; j++) s += pcm[base + j];\n\t\tbuf[i] = s;\n\t\tmean += s;\n\t}\n\tmean /= win;\n\n\tconst sq = new Float64Array(win + 1);\n\tfor (let i = 0; i < win; i++) {\n\t\tbuf[i] -= mean;\n\t\tsq[i + 1] = sq[i] + buf[i] * buf[i];\n\t}\n\tif (sq[win] < 1) return 0; // effectively silent\n\n\t// Normalized autocorrelation: 2·r(lag) / (E[0..n] + E[lag..lag+n]) ∈ [-1, 1].\n\tconst norm = (lag: number): number => {\n\t\tconst n = win - lag;\n\t\tlet r = 0;\n\t\tfor (let i = 0; i < n; i++) r += buf[i] * buf[i + lag];\n\t\tconst e = sq[n] + (sq[lag + n] - sq[lag]);\n\t\treturn e > 0 ? (2 * r) / e : 0;\n\t};\n\n\tlet bestLag = -1;\n\tlet best = 0;\n\tfor (let lag = minLag; lag <= maxLag; lag++) {\n\t\tconst v = norm(lag);\n\t\tif (v > best) {\n\t\t\tbest = v;\n\t\t\tbestLag = lag;\n\t\t}\n\t}\n\tif (bestLag < 1 || best < 0.4) return 0; // unvoiced / no clear period\n\n\t// Parabolic interpolation around the peak\n\tconst y0 = norm(bestLag - 1);\n\tconst y1 = best;\n\tconst y2 = norm(bestLag + 1);\n\tconst denom = y0 - 2 * y1 + y2;\n\tconst shift = denom !== 0 ? (0.5 * (y0 - y2)) / denom : 0;\n\n\treturn sr / (bestLag + shift);\n}\n","import type { Manifest, PhonemeEntry } from \"../types.js\";\nimport type { OtoEntry } from \"./parse-oto.js\";\nimport { detectF0 } from \"./pitch.js\";\n\nconst TARGET_RATE = 48000;\n\nfunction msToSamples(ms: number): number {\n\treturn Math.round((ms / 1000) * TARGET_RATE);\n}\n\nconst clamp = (v: number, lo: number, hi: number) =>\n\tMath.max(lo, Math.min(hi, v));\n\nexport interface PackInput {\n\toto: OtoEntry;\n\t/** Full normalized PCM of the source WAV (48kHz / 16bit / mono) */\n\tpcm: Int16Array;\n\t/** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */\n\trecordedPitch?: number;\n}\n\nexport interface PackOutput {\n\tmanifest: Manifest;\n\t/** Raw PCM blob — Int16 / 48kHz / mono */\n\tbin: ArrayBuffer;\n}\n\nexport interface TrimmedPhoneme {\n\t/** PCM trimmed to the oto region [offset, cutoff] */\n\tpcm: Int16Array;\n\t/** Manifest params relative to the trimmed start (sample 0 = oto offset) */\n\tentry: Omit<PhonemeEntry, \"offset\">;\n}\n\n/**\n * Cut the full WAV PCM down to its usable oto region and recompute parameters\n * relative to the trimmed start.\n *\n * UTAU oto.ini values are all in ms and measured from `offset` (the left blank),\n * except `cutoff` (right blank):\n * - cutoff >= 0 : measured from the END of the file\n * - cutoff < 0 : region length from offset = |cutoff|\n *\n * After trimming, sample 0 == oto offset, so pre/overlap/consonant carry over\n * unchanged (just converted to samples), and the slice length is the region end.\n */\nexport function trimToOto(\n\tpcm: Int16Array,\n\toto: OtoEntry,\n\trecordedPitch = 0,\n): TrimmedPhoneme {\n\tconst full = pcm.length;\n\n\tconst start = clamp(msToSamples(oto.offset), 0, full);\n\tconst end =\n\t\toto.cutoff < 0\n\t\t\t? clamp(start + msToSamples(-oto.cutoff), start, full)\n\t\t\t: clamp(full - msToSamples(oto.cutoff), start, full);\n\n\tconst slice = pcm.subarray(start, end);\n\tconst length = slice.length;\n\n\tconst pre = clamp(msToSamples(oto.pre), 0, length);\n\tconst overlap = clamp(msToSamples(oto.overlap), 0, length);\n\tconst consonant = clamp(msToSamples(oto.consonant), 0, length);\n\n\t// Prefer the known recorded pitch (from the .frq file or alias suffix); only\n\t// fall back to autocorrelation on the sustained vowel when none is available.\n\tconst pitch =\n\t\trecordedPitch > 0\n\t\t\t? recordedPitch\n\t\t\t: detectF0(\n\t\t\t\t\tslice,\n\t\t\t\t\tMath.min(Math.max(pre, consonant), Math.max(0, length - 1)),\n\t\t\t\t\tlength,\n\t\t\t\t);\n\n\treturn {\n\t\tpcm: slice,\n\t\tentry: { length, pre, overlap, consonant, pitch },\n\t};\n}\n\n/**\n * Pack normalized PCM phonemes into voice.bin + manifest.json.\n * Each phoneme is trimmed to its oto region first.\n * Duplicate aliases are silently overwritten by the later entry.\n */\nexport function pack(inputs: PackInput[], referencePitch = 220): PackOutput {\n\tconst phonemes: Record<string, PhonemeEntry> = {};\n\tconst chunks: Int16Array[] = [];\n\tlet byteOffset = 0;\n\n\tfor (const { oto, pcm, recordedPitch } of inputs) {\n\t\tconst { pcm: slice, entry } = trimToOto(pcm, oto, recordedPitch);\n\t\tif (slice.length === 0) continue;\n\n\t\tphonemes[oto.alias] = { offset: byteOffset, ...entry };\n\t\tbyteOffset += slice.byteLength;\n\t\tchunks.push(slice);\n\t}\n\n\tconst bin = new ArrayBuffer(byteOffset);\n\tconst view = new Uint8Array(bin);\n\tlet pos = 0;\n\tfor (const chunk of chunks) {\n\t\tview.set(\n\t\t\tnew Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength),\n\t\t\tpos,\n\t\t);\n\t\tpos += chunk.byteLength;\n\t}\n\n\tconst manifest: Manifest = {\n\t\tsampleRate: 48000,\n\t\treferencePitch,\n\t\tphonemes,\n\t};\n\n\treturn { manifest, bin };\n}\n","/**\n * UTAU `.frq` frequency-analysis files (FREQ0003). The same format OpenUtau\n * reads: an 8-byte header, hop size, then the average fundamental frequency of\n * the recording — exactly the reference pitch we need for correct resampling.\n *\n * Layout:\n * char[8] \"FREQ0003\"\n * int32 hopSize\n * float64 averageF0 ← the recorded pitch in Hz\n * byte[16] (blank)\n * int32 length\n * { float64 f0, float64 amp } × length\n */\nexport function parseFrqAverageF0(buffer: ArrayBuffer): number | null {\n\tif (buffer.byteLength < 20) return null;\n\tconst view = new DataView(buffer);\n\n\tlet header = \"\";\n\tfor (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));\n\tif (header !== \"FREQ0003\") return null;\n\n\tconst avg = view.getFloat64(12, true); // little-endian\n\treturn Number.isFinite(avg) && avg > 0 ? avg : null;\n}\n\n/** Map a WAV filename to its sibling frq filename: \"あ.wav\" → \"あ_wav.frq\". */\nexport function frqFileName(wavName: string): string {\n\tconst dot = wavName.lastIndexOf(\".\");\n\tconst base = dot >= 0 ? wavName.slice(0, dot) : wavName;\n\tconst ext = dot >= 0 ? wavName.slice(dot + 1) : \"wav\";\n\treturn `${base}_${ext}.frq`;\n}\n"],"mappings":"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","names":["header","jsonLength","json","manifest"]}
1
+ {"version":3,"sources":["../src/koe.ts","../src/engine/voice-bank.ts","../src/engine/index.ts","../src/engine/worldline.ts","../src/converter/parse-oto.ts","../src/converter/wav.ts","../src/converter/pitch.ts","../src/converter/pack.ts","../src/converter/frq.ts"],"sourcesContent":["import type { Manifest } from \"./types.js\";\n\n/** Magic number 'KOE\\0' (stored big-endian as 0x4B 0x4F 0x45 0x00). */\nconst MAGIC = 0x4b4f4500;\n\n/**\n * Koe Archive Format (.koe)\n * [4B] magic 'KOE\\0' (big-endian)\n * [4B] JSON length (little-endian)\n * [N ] manifest JSON (UTF-8)\n * [M ] raw PCM (Int16 / 48kHz / mono); phoneme offsets are relative to here\n */\nexport function packKoe(manifest: Manifest, pcmParts: BlobPart[]): Blob {\n\tconst json = new TextEncoder().encode(JSON.stringify(manifest));\n\tconst header = new ArrayBuffer(8);\n\tconst view = new DataView(header);\n\tview.setUint32(0, MAGIC, false); // magic, big-endian\n\tview.setUint32(4, json.byteLength, true); // json length, little-endian\n\treturn new Blob([header, json, ...pcmParts]);\n}\n\n/** Read the 8-byte header → JSON length. Throws on bad magic. */\nexport function parseKoeHeader(headerBytes: ArrayBuffer): {\n\tjsonLength: number;\n} {\n\tconst view = new DataView(headerBytes);\n\tif (view.byteLength < 8 || view.getUint32(0, false) !== MAGIC) {\n\t\tthrow new Error(\"Not a .koe file (bad magic)\");\n\t}\n\treturn { jsonLength: view.getUint32(4, true) };\n}\n\n/** Byte offset where PCM data begins, given the JSON length. */\nexport const pcmBase = (jsonLength: number): number => 8 + jsonLength;\n","import type { Manifest } from \"../types.js\";\nimport { parseKoeHeader, pcmBase } from \"../koe.js\";\n\n/**\n * Supplies raw bytes from the PCM section of a .koe archive on demand.\n * - BlobVoiceSource: slices an in-memory Blob / File (no full-buffer copy)\n * - RangeVoiceSource: HTTP Range requests against a URL (mobile-friendly)\n * `base` is the byte offset where PCM data starts (8 + jsonLength).\n */\ninterface VoiceSource {\n\treadBytes(offset: number, length: number): Promise<ArrayBuffer>;\n}\n\nclass BlobVoiceSource implements VoiceSource {\n\tconstructor(\n\t\tprivate blob: Blob,\n\t\tprivate base: number,\n\t) {}\n\treadBytes(offset: number, length: number): Promise<ArrayBuffer> {\n\t\tconst start = this.base + offset;\n\t\treturn this.blob.slice(start, start + length).arrayBuffer();\n\t}\n}\n\nclass RangeVoiceSource implements VoiceSource {\n\tconstructor(\n\t\tprivate url: string,\n\t\tprivate base: number,\n\t) {}\n\tasync readBytes(offset: number, length: number): Promise<ArrayBuffer> {\n\t\tconst start = this.base + offset;\n\t\tconst res = await fetch(this.url, {\n\t\t\theaders: { Range: `bytes=${start}-${start + length - 1}` },\n\t\t});\n\t\tif (!res.ok && res.status !== 206) {\n\t\t\tthrow new Error(`.koe range request failed: ${res.status}`);\n\t\t}\n\t\treturn res.arrayBuffer();\n\t}\n}\n\nasync function rangeFetch(\n\turl: string,\n\tstart: number,\n\tlength: number,\n): Promise<ArrayBuffer> {\n\tconst res = await fetch(url, {\n\t\theaders: { Range: `bytes=${start}-${start + length - 1}` },\n\t});\n\tif (!res.ok && res.status !== 206)\n\t\tthrow new Error(`.koe fetch failed: ${res.status}`);\n\treturn res.arrayBuffer();\n}\n\n/**\n * Read-only access to a .koe voice bank: its manifest plus per-phoneme PCM,\n * fetched on demand (Blob slice or HTTP Range). The full bank is never held in\n * memory.\n *\n * Pure data — no AudioContext, no AudioWorklet, no DOM. Use this when you only\n * need the source samples (e.g. to feed the {@link Worldline} renderer or any\n * other vocoder). {@link KoeEngine} builds its concatenative playback on top of\n * this same class.\n *\n * const bank = await VoiceBank.load(koeBlobOrUrl);\n * const pcm = await bank.getPcm('a'); // Float64 [-1, 1]\n */\nexport class VoiceBank {\n\tprivate constructor(\n\t\t/** The voice bank manifest (sample rate, reference pitch, phoneme table). */\n\t\treadonly manifest: Manifest,\n\t\tprivate source: VoiceSource,\n\t) {}\n\n\t/**\n\t * Parse a .koe archive header + manifest and bind a lazy PCM source.\n\t * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)\n\t */\n\tstatic async load(koe: Blob | string): Promise<VoiceBank> {\n\t\tif (typeof koe === \"string\") {\n\t\t\tconst header = await rangeFetch(koe, 0, 8);\n\t\t\tconst { jsonLength } = parseKoeHeader(header);\n\t\t\tconst json = await rangeFetch(koe, 8, jsonLength);\n\t\t\tconst manifest = JSON.parse(new TextDecoder().decode(json)) as Manifest;\n\t\t\treturn new VoiceBank(\n\t\t\t\tmanifest,\n\t\t\t\tnew RangeVoiceSource(koe, pcmBase(jsonLength)),\n\t\t\t);\n\t\t}\n\t\tconst header = await koe.slice(0, 8).arrayBuffer();\n\t\tconst { jsonLength } = parseKoeHeader(header);\n\t\tconst json = await koe.slice(8, 8 + jsonLength).arrayBuffer();\n\t\tconst manifest = JSON.parse(new TextDecoder().decode(json)) as Manifest;\n\t\treturn new VoiceBank(\n\t\t\tmanifest,\n\t\t\tnew BlobVoiceSource(koe, pcmBase(jsonLength)),\n\t\t);\n\t}\n\n\t/** True if the bank contains a phoneme under this alias. */\n\thas(phoneme: string): boolean {\n\t\treturn this.manifest.phonemes[phoneme] !== undefined;\n\t}\n\n\t/**\n\t * Raw Int16 PCM bytes (48 kHz / mono) for a phoneme, or null if unknown.\n\t * The returned ArrayBuffer is freshly allocated and safe to transfer to a\n\t * worker / AudioWorklet.\n\t */\n\tasync readPcmBytes(phoneme: string): Promise<ArrayBuffer | null> {\n\t\tconst entry = this.manifest.phonemes[phoneme];\n\t\tif (!entry) return null;\n\t\treturn this.source.readBytes(entry.offset, entry.length * 2); // Int16 = 2 bytes/sample\n\t}\n\n\t/**\n\t * A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.\n\t * Intended for external analysis / resynthesis such as the WORLD vocoder.\n\t */\n\tasync getPcm(phoneme: string): Promise<Float64Array | null> {\n\t\tconst buf = await this.readPcmBytes(phoneme);\n\t\tif (!buf) return null;\n\t\tconst int16 = new Int16Array(buf);\n\t\tconst f64 = new Float64Array(int16.length);\n\t\tfor (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;\n\t\treturn f64;\n\t}\n}\n","import type { Manifest, NoteEvent } from \"../types.js\";\nimport { VoiceBank } from \"./voice-bank.js\";\n\nexport type { NoteEvent };\n\nexport interface KoeEngineOptions {\n\t/** URL to koe-worklet.js. Defaults to './koe-worklet.js'. */\n\tworkletUrl?: string;\n}\n\n/**\n * Main-thread API for the koe concatenative synthesis engine.\n *\n * Loads a single .koe archive. The full voice bank is never held in memory:\n * phonemes are fetched on demand (via {@link VoiceBank}) and cached in the\n * audio thread.\n *\n * const engine = new KoeEngine();\n * await engine.load(koeBlobOrUrl);\n * await engine.play([{ phoneme: 'a', pitch: 440, duration: 48000 }]);\n *\n * For analysis / resynthesis (e.g. {@link Worldline}) you usually only need the\n * raw samples — use {@link VoiceBank} directly instead, which needs no\n * AudioContext or worklet.\n */\nexport class KoeEngine {\n\tprivate ctx: AudioContext;\n\tprivate workletUrl: string;\n\tprivate node: AudioWorkletNode | null = null;\n\tprivate bank: VoiceBank | null = null;\n\tprivate delivered = new Set<string>();\n\tprivate pending = new Map<string, Promise<void>>();\n\n\tconstructor(options: KoeEngineOptions = {}) {\n\t\tthis.ctx = new AudioContext({ sampleRate: 48000 });\n\t\tthis.workletUrl = options.workletUrl ?? \"./koe-worklet.js\";\n\t}\n\n\tget audioContext(): AudioContext {\n\t\treturn this.ctx;\n\t}\n\n\tget manifest(): Manifest | null {\n\t\treturn this.bank?.manifest ?? null;\n\t}\n\n\t/** The underlying voice bank (manifest + on-demand PCM), or null before load(). */\n\tget voiceBank(): VoiceBank | null {\n\t\treturn this.bank;\n\t}\n\n\t/**\n\t * Register the worklet and bind a .koe voice bank.\n\t * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)\n\t */\n\tasync load(koe: Blob | string): Promise<void> {\n\t\tawait this.ctx.audioWorklet.addModule(this.workletUrl);\n\n\t\tthis.bank = await VoiceBank.load(koe);\n\t\tthis.delivered.clear();\n\t\tthis.pending.clear();\n\n\t\tthis.node = new AudioWorkletNode(this.ctx, \"koe-processor\", {\n\t\t\tnumberOfInputs: 0,\n\t\t\tnumberOfOutputs: 1,\n\t\t\toutputChannelCount: [1],\n\t\t});\n\t\tthis.node.port.postMessage({ type: \"init\", manifest: this.bank.manifest });\n\t\tthis.node.connect(this.ctx.destination);\n\n\t\tconsole.log(\n\t\t\t\"[koe] ready —\",\n\t\t\tObject.keys(this.bank.manifest.phonemes).length,\n\t\t\t\"phonemes (on-demand)\",\n\t\t);\n\t}\n\n\t/** Fetch one phoneme's PCM and deliver it to the worklet (deduped, cached). */\n\tprivate ensurePhoneme(name: string): Promise<void> {\n\t\tif (this.delivered.has(name)) return Promise.resolve();\n\t\tconst existing = this.pending.get(name);\n\t\tif (existing) return existing;\n\t\tif (!this.bank || !this.node) return Promise.resolve();\n\n\t\tconst load = this.bank.readPcmBytes(name).then((buf) => {\n\t\t\tif (!buf) return;\n\t\t\tthis.node!.port.postMessage({ type: \"phoneme\", name, buffer: buf }, [\n\t\t\t\tbuf,\n\t\t\t]);\n\t\t\tthis.delivered.add(name);\n\t\t\tthis.pending.delete(name);\n\t\t});\n\n\t\tthis.pending.set(name, load);\n\t\treturn load;\n\t}\n\n\t/** Stop current playback, preload the phonemes for `notes`, then queue them. */\n\tasync play(notes: NoteEvent[]): Promise<void> {\n\t\tif (!this.node) throw new Error(\"KoeEngine: call load() before play()\");\n\t\tthis.node.port.postMessage({ type: \"stop\" });\n\t\tconst names = [...new Set(notes.map((n) => n.phoneme))].filter(Boolean);\n\t\tawait Promise.all(names.map((n) => this.ensurePhoneme(n)));\n\t\tthis.node.port.postMessage({ type: \"play\", notes });\n\t}\n\n\t/** Stop playback and clear the queue. */\n\tstop(): void {\n\t\tthis.node?.port.postMessage({ type: \"stop\" });\n\t}\n\n\t/** Resume the AudioContext if suspended (e.g. after autoplay block). */\n\tasync resume(): Promise<void> {\n\t\tif (this.ctx.state === \"suspended\") await this.ctx.resume();\n\t}\n\n\t/**\n\t * Read a phoneme's raw PCM and return it as a Float64Array normalised to\n\t * [-1, 1]. Convenience that forwards to the underlying {@link VoiceBank}.\n\t * Intended for external analysis such as the WORLD vocoder.\n\t */\n\tasync getPcm(phoneme: string): Promise<Float64Array | null> {\n\t\treturn this.bank?.getPcm(phoneme) ?? null;\n\t}\n}\n","import type { PhonemeEntry } from \"../types.js\";\n\n/** Output sample rate of the worldline synthesizer. */\nexport const WORLDLINE_SAMPLE_RATE = 48000;\n\n/**\n * WORLD needs roughly 85 ms of audio for stable F0 analysis. Phonemes with\n * fewer samples than this are rejected (renderNote returns null).\n */\nexport const MIN_WORLDLINE_SAMPLES = 4096;\n\n/** SynthRequest struct layout — 120 bytes, WASM32. */\nconst SYNTH_REQ_SIZE = 120;\n/** Frame period hardcoded in worldline's phrase_synth.cpp. */\nconst WL_FRAME_MS = 10;\n\n/**\n * The subset of the Emscripten module surface that we call. worldline.js is an\n * MODULARIZE=1 / EXPORT_NAME=WorldlineModule build of OpenUtau's worldline.\n */\ninterface WorldlineWasm {\n\t_PhraseSynthNew(): number;\n\t_PhraseSynthDelete(ps: number): void;\n\t_PhraseSynthAddRequest(\n\t\tps: number,\n\t\treq: number,\n\t\tposMs: number,\n\t\tskipMs: number,\n\t\tlengthMs: number,\n\t\tfadeInMs: number,\n\t\tfadeOutMs: number,\n\t\tflag: number,\n\t): void;\n\t_PhraseSynthSetCurves(\n\t\tps: number,\n\t\tf0: number,\n\t\tgender: number,\n\t\ttension: number,\n\t\tbreathiness: number,\n\t\tvoicing: number,\n\t\tlength: number,\n\t\tframeMs: number,\n\t): void;\n\t_PhraseSynthSynth(ps: number, yPtrPtr: number, flag: number): number;\n\t_malloc(size: number): number;\n\t_free(ptr: number): void;\n\tsetValue(ptr: number, value: number, type: string): void;\n\tgetValue(ptr: number, type: string): number;\n\tHEAPF32: Float32Array;\n\tHEAPF64: Float64Array;\n}\n\ntype WorldlineFactory = (opts?: {\n\tlocateFile?: (path: string) => string;\n}) => Promise<WorldlineWasm>;\n\ndeclare global {\n\t// Injected by worldline.js when loaded as a classic <script> or via importScripts.\n\t// eslint-disable-next-line no-var\n\tvar WorldlineModule: WorldlineFactory | undefined;\n\t// Present in classic Web Workers (used to load worldline.js off the main thread).\n\t// eslint-disable-next-line no-var\n\tvar importScripts: ((...urls: string[]) => void) | undefined;\n}\n\nexport interface WorldlineLoadOptions {\n\t/**\n\t * URL of `worldline.js` (the Emscripten loader). The matching\n\t * `worldline.wasm` must sit next to it — it is resolved relative to this URL.\n\t *\n\t * When consuming koe from npm + a CDN this is typically e.g.\n\t * `https://cdn.jsdelivr.net/npm/@onjmin/koe/dist/world/worldline.js`, or your\n\t * own hosted copy of `dist/world/`.\n\t */\n\tscriptUrl: string;\n}\n\nexport interface RenderNoteParams {\n\t/**\n\t * Source phoneme PCM normalised to [-1, 1] (e.g. from\n\t * `VoiceBank.getPcm()` / `KoeEngine.getPcm()`).\n\t */\n\tpcm: Float64Array;\n\t/** Target output pitch in Hz. */\n\tpitch: number;\n\t/** Sustain / vowel duration in ms (the lead-in below is rendered on top). */\n\tdurationMs: number;\n\t/** Preutterance / lead-in in ms — convert from {@link PhonemeEntry.pre}. */\n\tpreMs: number;\n\t/** Consonant length in ms — convert from {@link PhonemeEntry.consonant}. */\n\tconsonantMs: number;\n\t/** Reference tempo in BPM for worldline's internal timing. Default 120. */\n\ttempo?: number;\n}\n\n/** Convert a sample count at 48 kHz to milliseconds. */\nexport const samplesToMs = (samples: number): number =>\n\t(samples / WORLDLINE_SAMPLE_RATE) * 1000;\n\n/**\n * Derive {@link RenderNoteParams} lead-in / consonant fields from a manifest\n * entry, so callers don't repeat the sample→ms conversion.\n *\n * const params = { pcm, pitch, durationMs, ...leadInFromEntry(entry) };\n */\nexport function leadInFromEntry(entry: PhonemeEntry): {\n\tpreMs: number;\n\tconsonantMs: number;\n} {\n\treturn {\n\t\tpreMs: samplesToMs(entry.pre || 0),\n\t\tconsonantMs: samplesToMs(entry.consonant || 0),\n\t};\n}\n\n// Cache module factories by script URL so repeated loads share one WASM instance.\nconst moduleCache = new Map<string, Promise<WorldlineWasm>>();\n\nfunction injectScript(src: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst existing = document.querySelector<HTMLScriptElement>(\n\t\t\t`script[data-koe-worldline=\"${src}\"]`,\n\t\t);\n\t\tif (existing) {\n\t\t\tresolve();\n\t\t\treturn;\n\t\t}\n\t\tconst s = document.createElement(\"script\");\n\t\ts.src = src;\n\t\ts.dataset.koeWorldline = src;\n\t\ts.onload = () => resolve();\n\t\ts.onerror = () => reject(new Error(`worldline: failed to load ${src}`));\n\t\tdocument.head.appendChild(s);\n\t});\n}\n\nfunction loadWasm(scriptUrl: string): Promise<WorldlineWasm> {\n\tconst cached = moduleCache.get(scriptUrl);\n\tif (cached) return cached;\n\n\tconst baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf(\"/\") + 1);\n\tconst instantiate = (): Promise<WorldlineWasm> => {\n\t\tconst factory = globalThis.WorldlineModule;\n\t\tif (!factory)\n\t\t\tthrow new Error(\n\t\t\t\t\"worldline: WorldlineModule global was not defined by the script\",\n\t\t\t);\n\t\t// locateFile resolves worldline.wasm next to the script; fetch works in both\n\t\t// the DOM and Web Workers, so the WASM instantiates the same way off-thread.\n\t\treturn factory({ locateFile: (f: string) => baseUrl + f });\n\t};\n\n\tlet promise: Promise<WorldlineWasm>;\n\tif (typeof document !== \"undefined\") {\n\t\t// Main thread / DOM: load worldline.js via a <script> tag.\n\t\tpromise = injectScript(scriptUrl).then(instantiate);\n\t} else if (typeof globalThis.importScripts === \"function\") {\n\t\t// Classic Web Worker: importScripts runs worldline.js synchronously in the\n\t\t// worker's global scope, defining globalThis.WorldlineModule. This lets the\n\t\t// heavy WORLD synthesis run off the main thread.\n\t\tpromise = Promise.resolve().then(() => {\n\t\t\t(globalThis.importScripts as (...urls: string[]) => void)(scriptUrl);\n\t\t\treturn instantiate();\n\t\t});\n\t} else {\n\t\treturn Promise.reject(\n\t\t\tnew Error(\n\t\t\t\t\"Worldline.load requires a DOM or a classic Web Worker (importScripts) to load worldline.js\",\n\t\t\t),\n\t\t);\n\t}\n\tmoduleCache.set(scriptUrl, promise);\n\treturn promise;\n}\n\n/**\n * High-quality WORLD-vocoder note renderer (OpenUtau's worldline via WASM).\n *\n * Pure synthesis: PCM in → PCM out. No AudioContext, no scheduling — the caller\n * owns playback (schedule the returned buffer on its own timeline). Pair it with\n * {@link VoiceBank} for the source samples:\n *\n * const bank = await VoiceBank.load(koeUrl);\n * const wl = await Worldline.load({ scriptUrl: '.../world/worldline.js' });\n *\n * const entry = bank.manifest.phonemes[alias];\n * const pcm = await bank.getPcm(alias);\n * const audio = wl.renderNote({\n * pcm, pitch: 440, durationMs: 500, ...leadInFromEntry(entry),\n * });\n * // audio: Float32 @ 48 kHz, layout [lead-in/consonant ≈ preMs][vowel ≈ durationMs]\n */\nexport class Worldline {\n\treadonly sampleRate = WORLDLINE_SAMPLE_RATE;\n\n\tprivate constructor(private wasm: WorldlineWasm) {}\n\n\t/**\n\t * Load + instantiate the worldline WASM module (deduped per scriptUrl).\n\t *\n\t * Works on the main thread (loads via `<script>`) and inside a classic Web\n\t * Worker (loads via `importScripts`), so the heavy synthesis can run\n\t * off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.\n\t */\n\tstatic async load(options: WorldlineLoadOptions): Promise<Worldline> {\n\t\treturn new Worldline(await loadWasm(options.scriptUrl));\n\t}\n\n\t/**\n\t * Render one note to Float32 PCM at 48 kHz.\n\t *\n\t * The output buffer is laid out as [lead-in/consonant ≈ preMs][vowel ≈\n\t * durationMs], rendered from sample offset 0 (no leading silence). The vowel\n\t * onset (the \"beat\") sits at ≈ preMs into the buffer, so a sequencer should\n\t * place the buffer at `beatTime − preMs` and may trim/crossfade the lead-in.\n\t *\n\t * No internal crossfade is applied — apply fades externally.\n\t *\n\t * @returns Float32 PCM, or null when `pcm` is shorter than\n\t * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).\n\t */\n\trenderNote(params: RenderNoteParams): Float32Array | null {\n\t\tconst { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;\n\t\tif (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;\n\n\t\tconst WL = this.wasm;\n\t\tconst FS = WORLDLINE_SAMPLE_RATE;\n\t\tconst midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));\n\t\tconst posMs = 0; // no leading silence\n\t\tconst reqLen = preMs + durationMs; // render lead-in/consonant + vowel\n\n\t\t// cut_off must trim at least ~2 frames off the tail. PhraseSynth::AddRequest\n\t\t// computes the trim frame count as ceil(in_length_ms)/frame_ms, but\n\t\t// PyinEstimator yields floor(N/nhop)−1 frames — one fewer. With cut_off=0 the\n\t\t// requested frame count exceeds f0_.size() and Model::Trim's f0_.erase() runs\n\t\t// past end(), causing \"memory access out of bounds\". Trimming 2 frames keeps\n\t\t// it within bounds.\n\t\tconst cutMs = WL_FRAME_MS * 2;\n\n\t\tconst ps = WL._PhraseSynthNew();\n\t\tif (!ps) return null;\n\t\tconst reqPtr = WL._malloc(SYNTH_REQ_SIZE);\n\t\tif (!reqPtr) {\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tconst samplePtr = WL._malloc(pcm.length * 8);\n\t\tif (!samplePtr) {\n\t\t\tWL._free(reqPtr);\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tWL.HEAPF64.set(pcm, samplePtr >> 3);\n\n\t\tconst sv = (off: number, val: number, type: string) =>\n\t\t\tWL.setValue(reqPtr + off, val, type);\n\t\tsv(0, FS, \"i32\");\n\t\tsv(4, pcm.length, \"i32\");\n\t\tsv(8, samplePtr, \"*\");\n\t\tsv(12, 0, \"i32\");\n\t\tsv(16, 0, \"*\");\n\t\tsv(20, midiNote, \"i32\");\n\t\tsv(24, 100.0, \"double\");\n\t\tsv(32, 0.0, \"double\");\n\t\tsv(40, reqLen, \"double\");\n\t\tsv(48, consonantMs, \"double\");\n\t\tsv(56, cutMs, \"double\");\n\t\tsv(64, 100.0, \"double\");\n\t\tsv(72, 0.0, \"double\");\n\t\tsv(80, tempo, \"double\");\n\t\tsv(88, 0, \"i32\");\n\t\tsv(92, 0, \"*\");\n\t\tsv(96, 0, \"i32\");\n\t\tsv(100, 0, \"i32\");\n\t\tsv(104, 100, \"i32\");\n\t\tsv(108, 0, \"i32\");\n\t\tsv(112, 0, \"i32\");\n\t\tsv(116, 100, \"i32\");\n\n\t\t// skip/fadeIn/fadeOut all 0 — crossfade is handled externally.\n\t\tWL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0.0, reqLen, 0.0, 0.0, 0);\n\t\tWL._free(samplePtr);\n\t\tWL._free(reqPtr);\n\n\t\tconst totalMs = posMs + reqLen + WL_FRAME_MS * 2;\n\t\tconst nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;\n\t\tconst f0Arr = new Float64Array(nFrames).fill(pitch);\n\t\tconst gArr = new Float64Array(nFrames).fill(0.5);\n\t\tconst tArr = new Float64Array(nFrames).fill(0.5);\n\t\tconst bArr = new Float64Array(nFrames).fill(0.5);\n\t\tconst vArr = new Float64Array(nFrames).fill(1.0);\n\n\t\tconst f0Ptr = WL._malloc(nFrames * 8);\n\t\tconst gPtr = WL._malloc(nFrames * 8);\n\t\tconst tPtr = WL._malloc(nFrames * 8);\n\t\tconst bPtr = WL._malloc(nFrames * 8);\n\t\tconst vPtr = WL._malloc(nFrames * 8);\n\t\tif (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {\n\t\t\tif (f0Ptr) WL._free(f0Ptr);\n\t\t\tif (gPtr) WL._free(gPtr);\n\t\t\tif (tPtr) WL._free(tPtr);\n\t\t\tif (bPtr) WL._free(bPtr);\n\t\t\tif (vPtr) WL._free(vPtr);\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tWL.HEAPF64.set(f0Arr, f0Ptr >> 3);\n\t\tWL.HEAPF64.set(gArr, gPtr >> 3);\n\t\tWL.HEAPF64.set(tArr, tPtr >> 3);\n\t\tWL.HEAPF64.set(bArr, bPtr >> 3);\n\t\tWL.HEAPF64.set(vArr, vPtr >> 3);\n\t\tWL._PhraseSynthSetCurves(\n\t\t\tps,\n\t\t\tf0Ptr,\n\t\t\tgPtr,\n\t\t\ttPtr,\n\t\t\tbPtr,\n\t\t\tvPtr,\n\t\t\tnFrames,\n\t\t\tWL_FRAME_MS,\n\t\t);\n\t\tWL._free(f0Ptr);\n\t\tWL._free(gPtr);\n\t\tWL._free(tPtr);\n\t\tWL._free(bPtr);\n\t\tWL._free(vPtr);\n\n\t\tconst yPtrPtr = WL._malloc(4);\n\t\tif (!yPtrPtr) {\n\t\t\tWL._PhraseSynthDelete(ps);\n\t\t\treturn null;\n\t\t}\n\t\tconst outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);\n\t\tconst yPtr = WL.getValue(yPtrPtr, \"*\");\n\t\tconst audio =\n\t\t\toutLen > 0\n\t\t\t\t? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice()\n\t\t\t\t: null;\n\t\tWL._free(yPtrPtr);\n\t\tWL._PhraseSynthDelete(ps);\n\t\treturn audio;\n\t}\n}\n","export interface OtoEntry {\n\t/** Source WAV filename */\n\twav: string;\n\t/** Phoneme alias */\n\talias: string;\n\t/** Left blank — offset from WAV start (ms) */\n\toffset: number;\n\t/** Consonant portion end from offset (ms) */\n\tconsonant: number;\n\t/** Right blank — negative = from WAV end, positive = from offset (ms) */\n\tcutoff: number;\n\t/** Preutterance from offset (ms) */\n\tpre: number;\n\t/** Overlap / crossfade region (ms) */\n\toverlap: number;\n}\n\n/**\n * Parse oto.ini content (already decoded to UTF-8 string).\n * Silently skips malformed lines.\n */\nexport function parseOto(content: string): OtoEntry[] {\n\tconst entries: OtoEntry[] = [];\n\n\tfor (const raw of content.split(/\\r?\\n/)) {\n\t\tconst line = raw.trim();\n\t\tif (!line || line.startsWith(\"#\")) continue;\n\n\t\tconst eq = line.indexOf(\"=\");\n\t\tif (eq === -1) continue;\n\n\t\tconst wav = line.slice(0, eq).trim();\n\t\tconst parts = line.slice(eq + 1).split(\",\");\n\t\tif (parts.length < 6) continue;\n\n\t\tconst [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] =\n\t\t\tparts;\n\t\t// Empty alias → use filename without extension (UTAU spec default)\n\t\tconst aliasStr = alias.trim() || wav.replace(/\\.[^.]+$/, \"\");\n\t\tconst entry: OtoEntry = {\n\t\t\twav,\n\t\t\talias: aliasStr,\n\t\t\toffset: parseFloat(offsetStr) || 0,\n\t\t\tconsonant: parseFloat(consonantStr) || 0,\n\t\t\tcutoff: parseFloat(cutoffStr) || 0,\n\t\t\tpre: parseFloat(preStr) || 0,\n\t\t\toverlap: parseFloat(overlapStr) || 0,\n\t\t};\n\n\t\tif (!entry.alias) continue;\n\t\tentries.push(entry);\n\t}\n\n\treturn entries;\n}\n","export interface WavData {\n\tsampleRate: number;\n\tchannels: number;\n\t/** Normalized samples in [-1, 1], interleaved if multi-channel */\n\tsamples: Float32Array;\n}\n\n/** Parse a WAV file from an ArrayBuffer. Supports PCM 8/16/24-bit and IEEE float 32-bit. */\nexport function parseWav(buf: ArrayBuffer): WavData {\n\tconst view = new DataView(buf);\n\n\tconst riff = readFourCC(view, 0);\n\tif (riff !== \"RIFF\") throw new Error(`Not a RIFF file (got \"${riff}\")`);\n\n\tlet sampleRate = 0;\n\tlet channels = 0;\n\tlet bitsPerSample = 0;\n\tlet audioFormat = 1;\n\tlet dataOffset = 0;\n\tlet dataLength = 0;\n\n\tlet pos = 12;\n\twhile (pos < view.byteLength - 8) {\n\t\tconst id = readFourCC(view, pos);\n\t\tconst size = view.getUint32(pos + 4, true);\n\t\tpos += 8;\n\n\t\tif (id === \"fmt \") {\n\t\t\taudioFormat = view.getUint16(pos, true);\n\t\t\tchannels = view.getUint16(pos + 2, true);\n\t\t\tsampleRate = view.getUint32(pos + 4, true);\n\t\t\tbitsPerSample = view.getUint16(pos + 14, true);\n\t\t} else if (id === \"data\") {\n\t\t\tdataOffset = pos;\n\t\t\tdataLength = size;\n\t\t\tbreak;\n\t\t}\n\n\t\tpos += size + (size & 1);\n\t}\n\n\tif (!dataOffset) throw new Error(\"WAV has no data chunk\");\n\tif (!channels || !sampleRate) throw new Error(\"WAV fmt chunk missing\");\n\n\tconst bytesPerSample = bitsPerSample >> 3;\n\tconst totalSamples = Math.floor(dataLength / bytesPerSample);\n\tconst samples = new Float32Array(totalSamples);\n\n\tfor (let i = 0; i < totalSamples; i++) {\n\t\tconst p = dataOffset + i * bytesPerSample;\n\t\tif (audioFormat === 3) {\n\t\t\tsamples[i] = view.getFloat32(p, true);\n\t\t} else if (bitsPerSample === 8) {\n\t\t\tsamples[i] = (view.getUint8(p) - 128) / 128;\n\t\t} else if (bitsPerSample === 16) {\n\t\t\tsamples[i] = view.getInt16(p, true) / 32768;\n\t\t} else if (bitsPerSample === 24) {\n\t\t\tconst lo = view.getUint8(p) | (view.getUint8(p + 1) << 8);\n\t\t\tlet hi = view.getUint8(p + 2);\n\t\t\tif (hi & 0x80) hi = hi | 0xffffff00;\n\t\t\tsamples[i] = ((hi << 16) | lo) / 8388608;\n\t\t}\n\t}\n\n\treturn { sampleRate, channels, samples };\n}\n\n/** Mix down to mono by averaging all channels. */\nexport function toMono(wav: WavData): WavData {\n\tif (wav.channels === 1) return wav;\n\tconst len = wav.samples.length / wav.channels;\n\tconst out = new Float32Array(len);\n\tfor (let i = 0; i < len; i++) {\n\t\tlet sum = 0;\n\t\tfor (let c = 0; c < wav.channels; c++)\n\t\t\tsum += wav.samples[i * wav.channels + c];\n\t\tout[i] = sum / wav.channels;\n\t}\n\treturn { sampleRate: wav.sampleRate, channels: 1, samples: out };\n}\n\n/** Linear interpolation resample to targetRate. Expects mono input. */\nexport function resample(wav: WavData, targetRate: number): WavData {\n\tif (wav.sampleRate === targetRate) return wav;\n\tconst ratio = wav.sampleRate / targetRate;\n\tconst outLen = Math.floor(wav.samples.length / ratio);\n\tconst out = new Float32Array(outLen);\n\tconst src = wav.samples;\n\tfor (let i = 0; i < outLen; i++) {\n\t\tconst x = i * ratio;\n\t\tconst xi = Math.floor(x);\n\t\tconst frac = x - xi;\n\t\tout[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;\n\t}\n\treturn { sampleRate: targetRate, channels: 1, samples: out };\n}\n\n/** Convert Float32 [-1,1] samples to Int16 PCM. */\nexport function toInt16(samples: Float32Array): Int16Array {\n\tconst out = new Int16Array(samples.length);\n\tfor (let i = 0; i < samples.length; i++) {\n\t\tout[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);\n\t}\n\treturn out;\n}\n\n/** Normalize then convert a WAV to 48kHz/16bit/mono Int16 PCM. */\nexport function normalizePcm(buf: ArrayBuffer): Int16Array {\n\tconst wav = parseWav(buf);\n\tconst mono = toMono(wav);\n\tconst resampled = resample(mono, 48000);\n\treturn toInt16(resampled.samples);\n}\n\nfunction readFourCC(view: DataView, pos: number): string {\n\treturn String.fromCharCode(\n\t\tview.getUint8(pos),\n\t\tview.getUint8(pos + 1),\n\t\tview.getUint8(pos + 2),\n\t\tview.getUint8(pos + 3),\n\t);\n}\n","const SAMPLE_RATE = 48000;\n\nconst NAME_SEMITONE: Record<string, number> = {\n\tc: 0,\n\td: 2,\n\te: 4,\n\tf: 5,\n\tg: 7,\n\ta: 9,\n\tb: 11,\n};\n\n/** Parse a note name like \"E4\", \"G#4\", \"Db5\" → frequency in Hz (null if invalid). */\nexport function noteNameToHz(name: string): number | null {\n\tconst m = /^([A-Ga-g])([#b]?)(-?\\d+)$/.exec(name);\n\tif (!m) return null;\n\tlet semi = NAME_SEMITONE[m[1].toLowerCase()];\n\tif (m[2] === \"#\") semi++;\n\telse if (m[2] === \"b\") semi--;\n\tconst midi = (parseInt(m[3], 10) + 1) * 12 + semi;\n\treturn 440 * 2 ** ((midi - 69) / 12);\n}\n\n/** Recorded pitch encoded in a multi-pitch alias suffix: \"a い_E4\" → 329.63 Hz. */\nexport function pitchFromAliasSuffix(alias: string): number | null {\n\tconst m = /_([A-Ga-g][#b]?-?\\d+)$/.exec(alias);\n\treturn m ? noteNameToHz(m[1]) : null;\n}\n\n/**\n * Estimate the fundamental frequency (Hz) of a voiced region by normalized\n * autocorrelation. Returns 0 when no clear pitch is found (unvoiced consonant,\n * silence, or a region too short to analyse).\n *\n * The signal is decimated to a lower analysis rate for speed; f0 below ~700 Hz\n * is well within the resulting Nyquist limit. A parabolic interpolation around\n * the best lag gives sub-sample (sub-semitone) accuracy.\n */\nexport function detectF0(pcm: Int16Array, start: number, end: number): number {\n\tconst DECIM = 4;\n\tconst sr = SAMPLE_RATE / DECIM; // 12 kHz analysis rate\n\tconst minLag = Math.floor(sr / 700); // highest f0 we look for\n\tconst maxLag = Math.floor(sr / 70); // lowest f0 we look for\n\n\tconst outLen = Math.floor((end - start) / DECIM);\n\tif (outLen < maxLag + 2) return 0;\n\tconst win = Math.min(outLen, 1500); // up to ~0.125 s of vowel\n\n\t// Decimate (box-average) + remove DC, and build a prefix sum of squares so\n\t// each lag's window energy is O(1).\n\tconst buf = new Float32Array(win);\n\tlet mean = 0;\n\tfor (let i = 0; i < win; i++) {\n\t\tlet s = 0;\n\t\tconst base = start + i * DECIM;\n\t\tfor (let j = 0; j < DECIM; j++) s += pcm[base + j];\n\t\tbuf[i] = s;\n\t\tmean += s;\n\t}\n\tmean /= win;\n\n\tconst sq = new Float64Array(win + 1);\n\tfor (let i = 0; i < win; i++) {\n\t\tbuf[i] -= mean;\n\t\tsq[i + 1] = sq[i] + buf[i] * buf[i];\n\t}\n\tif (sq[win] < 1) return 0; // effectively silent\n\n\t// Normalized autocorrelation: 2·r(lag) / (E[0..n] + E[lag..lag+n]) ∈ [-1, 1].\n\tconst norm = (lag: number): number => {\n\t\tconst n = win - lag;\n\t\tlet r = 0;\n\t\tfor (let i = 0; i < n; i++) r += buf[i] * buf[i + lag];\n\t\tconst e = sq[n] + (sq[lag + n] - sq[lag]);\n\t\treturn e > 0 ? (2 * r) / e : 0;\n\t};\n\n\tlet bestLag = -1;\n\tlet best = 0;\n\tfor (let lag = minLag; lag <= maxLag; lag++) {\n\t\tconst v = norm(lag);\n\t\tif (v > best) {\n\t\t\tbest = v;\n\t\t\tbestLag = lag;\n\t\t}\n\t}\n\tif (bestLag < 1 || best < 0.4) return 0; // unvoiced / no clear period\n\n\t// Parabolic interpolation around the peak\n\tconst y0 = norm(bestLag - 1);\n\tconst y1 = best;\n\tconst y2 = norm(bestLag + 1);\n\tconst denom = y0 - 2 * y1 + y2;\n\tconst shift = denom !== 0 ? (0.5 * (y0 - y2)) / denom : 0;\n\n\treturn sr / (bestLag + shift);\n}\n","import type { Manifest, PhonemeEntry } from \"../types.js\";\nimport type { OtoEntry } from \"./parse-oto.js\";\nimport { detectF0 } from \"./pitch.js\";\n\nconst TARGET_RATE = 48000;\n\nfunction msToSamples(ms: number): number {\n\treturn Math.round((ms / 1000) * TARGET_RATE);\n}\n\nconst clamp = (v: number, lo: number, hi: number) =>\n\tMath.max(lo, Math.min(hi, v));\n\nexport interface PackInput {\n\toto: OtoEntry;\n\t/** Full normalized PCM of the source WAV (48kHz / 16bit / mono) */\n\tpcm: Int16Array;\n\t/** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */\n\trecordedPitch?: number;\n}\n\nexport interface PackOutput {\n\tmanifest: Manifest;\n\t/** Raw PCM blob — Int16 / 48kHz / mono */\n\tbin: ArrayBuffer;\n}\n\nexport interface TrimmedPhoneme {\n\t/** PCM trimmed to the oto region [offset, cutoff] */\n\tpcm: Int16Array;\n\t/** Manifest params relative to the trimmed start (sample 0 = oto offset) */\n\tentry: Omit<PhonemeEntry, \"offset\">;\n}\n\n/**\n * Cut the full WAV PCM down to its usable oto region and recompute parameters\n * relative to the trimmed start.\n *\n * UTAU oto.ini values are all in ms and measured from `offset` (the left blank),\n * except `cutoff` (right blank):\n * - cutoff >= 0 : measured from the END of the file\n * - cutoff < 0 : region length from offset = |cutoff|\n *\n * After trimming, sample 0 == oto offset, so pre/overlap/consonant carry over\n * unchanged (just converted to samples), and the slice length is the region end.\n */\nexport function trimToOto(\n\tpcm: Int16Array,\n\toto: OtoEntry,\n\trecordedPitch = 0,\n): TrimmedPhoneme {\n\tconst full = pcm.length;\n\n\tconst start = clamp(msToSamples(oto.offset), 0, full);\n\tconst end =\n\t\toto.cutoff < 0\n\t\t\t? clamp(start + msToSamples(-oto.cutoff), start, full)\n\t\t\t: clamp(full - msToSamples(oto.cutoff), start, full);\n\n\tconst slice = pcm.subarray(start, end);\n\tconst length = slice.length;\n\n\tconst pre = clamp(msToSamples(oto.pre), 0, length);\n\tconst overlap = clamp(msToSamples(oto.overlap), 0, length);\n\tconst consonant = clamp(msToSamples(oto.consonant), 0, length);\n\n\t// Prefer the known recorded pitch (from the .frq file or alias suffix); only\n\t// fall back to autocorrelation on the sustained vowel when none is available.\n\tconst pitch =\n\t\trecordedPitch > 0\n\t\t\t? recordedPitch\n\t\t\t: detectF0(\n\t\t\t\t\tslice,\n\t\t\t\t\tMath.min(Math.max(pre, consonant), Math.max(0, length - 1)),\n\t\t\t\t\tlength,\n\t\t\t\t);\n\n\treturn {\n\t\tpcm: slice,\n\t\tentry: { length, pre, overlap, consonant, pitch },\n\t};\n}\n\n/**\n * Pack normalized PCM phonemes into voice.bin + manifest.json.\n * Each phoneme is trimmed to its oto region first.\n * Duplicate aliases are silently overwritten by the later entry.\n */\nexport function pack(inputs: PackInput[], referencePitch = 220): PackOutput {\n\tconst phonemes: Record<string, PhonemeEntry> = {};\n\tconst chunks: Int16Array[] = [];\n\tlet byteOffset = 0;\n\n\tfor (const { oto, pcm, recordedPitch } of inputs) {\n\t\tconst { pcm: slice, entry } = trimToOto(pcm, oto, recordedPitch);\n\t\tif (slice.length === 0) continue;\n\n\t\tphonemes[oto.alias] = { offset: byteOffset, ...entry };\n\t\tbyteOffset += slice.byteLength;\n\t\tchunks.push(slice);\n\t}\n\n\tconst bin = new ArrayBuffer(byteOffset);\n\tconst view = new Uint8Array(bin);\n\tlet pos = 0;\n\tfor (const chunk of chunks) {\n\t\tview.set(\n\t\t\tnew Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength),\n\t\t\tpos,\n\t\t);\n\t\tpos += chunk.byteLength;\n\t}\n\n\tconst manifest: Manifest = {\n\t\tsampleRate: 48000,\n\t\treferencePitch,\n\t\tphonemes,\n\t};\n\n\treturn { manifest, bin };\n}\n","/**\n * UTAU `.frq` frequency-analysis files (FREQ0003). The same format OpenUtau\n * reads: an 8-byte header, hop size, then the average fundamental frequency of\n * the recording — exactly the reference pitch we need for correct resampling.\n *\n * Layout:\n * char[8] \"FREQ0003\"\n * int32 hopSize\n * float64 averageF0 ← the recorded pitch in Hz\n * byte[16] (blank)\n * int32 length\n * { float64 f0, float64 amp } × length\n */\nexport function parseFrqAverageF0(buffer: ArrayBuffer): number | null {\n\tif (buffer.byteLength < 20) return null;\n\tconst view = new DataView(buffer);\n\n\tlet header = \"\";\n\tfor (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));\n\tif (header !== \"FREQ0003\") return null;\n\n\tconst avg = view.getFloat64(12, true); // little-endian\n\treturn Number.isFinite(avg) && avg > 0 ? avg : null;\n}\n\n/** Map a WAV filename to its sibling frq filename: \"あ.wav\" → \"あ_wav.frq\". */\nexport function frqFileName(wavName: string): string {\n\tconst dot = wavName.lastIndexOf(\".\");\n\tconst base = dot >= 0 ? wavName.slice(0, dot) : wavName;\n\tconst ext = dot >= 0 ? wavName.slice(dot + 1) : \"wav\";\n\treturn `${base}_${ext}.frq`;\n}\n"],"mappings":"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","names":["header","jsonLength","json","manifest"]}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "onjmin",
3
3
  "license": "MIT",
4
4
  "name": "@onjmin/koe",
5
- "version": "1.0.2",
5
+ "version": "1.0.3",
6
6
  "description": "UTAU concatenative synthesis engine optimized for browser / mobile via WebAssembly + AudioWorklet",
7
7
  "type": "module",
8
8
  "main": "./dist/index.js",