@onjmin/koe 1.0.0
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/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/index.d.ts +362 -0
- package/dist/index.js +632 -0
- package/dist/index.js.map +1 -0
- package/dist/koe-convert.js +344 -0
- package/dist/koe-worklet.js +1 -0
- package/dist/world/worldline.js +2 -0
- package/dist/world/worldline.wasm +0 -0
- package/package.json +48 -0
|
@@ -0,0 +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 const json = new TextEncoder().encode(JSON.stringify(manifest));\n const header = new ArrayBuffer(8);\n const view = new DataView(header);\n view.setUint32(0, MAGIC, false); // magic, big-endian\n view.setUint32(4, json.byteLength, true); // json length, little-endian\n return new Blob([header, json, ...pcmParts]);\n}\n\n/** Read the 8-byte header → JSON length. Throws on bad magic. */\nexport function parseKoeHeader(headerBytes: ArrayBuffer): { jsonLength: number } {\n const view = new DataView(headerBytes);\n if (view.byteLength < 8 || view.getUint32(0, false) !== MAGIC) {\n throw new Error('Not a .koe file (bad magic)');\n }\n return { 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 readBytes(offset: number, length: number): Promise<ArrayBuffer>;\n}\n\nclass BlobVoiceSource implements VoiceSource {\n constructor(\n private blob: Blob,\n private base: number,\n ) {}\n readBytes(offset: number, length: number): Promise<ArrayBuffer> {\n const start = this.base + offset;\n return this.blob.slice(start, start + length).arrayBuffer();\n }\n}\n\nclass RangeVoiceSource implements VoiceSource {\n constructor(\n private url: string,\n private base: number,\n ) {}\n async readBytes(offset: number, length: number): Promise<ArrayBuffer> {\n const start = this.base + offset;\n const res = await fetch(this.url, {\n headers: { Range: `bytes=${start}-${start + length - 1}` },\n });\n if (!res.ok && res.status !== 206) {\n throw new Error(`.koe range request failed: ${res.status}`);\n }\n return res.arrayBuffer();\n }\n}\n\nasync function rangeFetch(url: string, start: number, length: number): Promise<ArrayBuffer> {\n const res = await fetch(url, { headers: { Range: `bytes=${start}-${start + length - 1}` } });\n if (!res.ok && res.status !== 206) throw new Error(`.koe fetch failed: ${res.status}`);\n return 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 private constructor(\n /** The voice bank manifest (sample rate, reference pitch, phoneme table). */\n readonly manifest: Manifest,\n private source: VoiceSource,\n ) {}\n\n /**\n * Parse a .koe archive header + manifest and bind a lazy PCM source.\n * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)\n */\n static async load(koe: Blob | string): Promise<VoiceBank> {\n if (typeof koe === 'string') {\n const header = await rangeFetch(koe, 0, 8);\n const { jsonLength } = parseKoeHeader(header);\n const json = await rangeFetch(koe, 8, jsonLength);\n const manifest = JSON.parse(new TextDecoder().decode(json)) as Manifest;\n return new VoiceBank(manifest, new RangeVoiceSource(koe, pcmBase(jsonLength)));\n }\n const header = await koe.slice(0, 8).arrayBuffer();\n const { jsonLength } = parseKoeHeader(header);\n const json = await koe.slice(8, 8 + jsonLength).arrayBuffer();\n const manifest = JSON.parse(new TextDecoder().decode(json)) as Manifest;\n return new VoiceBank(manifest, new BlobVoiceSource(koe, pcmBase(jsonLength)));\n }\n\n /** True if the bank contains a phoneme under this alias. */\n has(phoneme: string): boolean {\n return this.manifest.phonemes[phoneme] !== undefined;\n }\n\n /**\n * Raw Int16 PCM bytes (48 kHz / mono) for a phoneme, or null if unknown.\n * The returned ArrayBuffer is freshly allocated and safe to transfer to a\n * worker / AudioWorklet.\n */\n async readPcmBytes(phoneme: string): Promise<ArrayBuffer | null> {\n const entry = this.manifest.phonemes[phoneme];\n if (!entry) return null;\n return this.source.readBytes(entry.offset, entry.length * 2); // Int16 = 2 bytes/sample\n }\n\n /**\n * A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.\n * Intended for external analysis / resynthesis such as the WORLD vocoder.\n */\n async getPcm(phoneme: string): Promise<Float64Array | null> {\n const buf = await this.readPcmBytes(phoneme);\n if (!buf) return null;\n const int16 = new Int16Array(buf);\n const f64 = new Float64Array(int16.length);\n for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;\n return f64;\n }\n}\n","import type { Manifest, NoteEvent } from '../types.js';\nimport { VoiceBank } from './voice-bank.js';\n\nexport type { NoteEvent };\n\nexport interface KoeEngineOptions {\n /** URL to koe-worklet.js. Defaults to './koe-worklet.js'. */\n workletUrl?: 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 private ctx: AudioContext;\n private workletUrl: string;\n private node: AudioWorkletNode | null = null;\n private bank: VoiceBank | null = null;\n private delivered = new Set<string>();\n private pending = new Map<string, Promise<void>>();\n\n constructor(options: KoeEngineOptions = {}) {\n this.ctx = new AudioContext({ sampleRate: 48000 });\n this.workletUrl = options.workletUrl ?? './koe-worklet.js';\n }\n\n get audioContext(): AudioContext {\n return this.ctx;\n }\n\n get manifest(): Manifest | null {\n return this.bank?.manifest ?? null;\n }\n\n /** The underlying voice bank (manifest + on-demand PCM), or null before load(). */\n get voiceBank(): VoiceBank | null {\n return this.bank;\n }\n\n /**\n * Register the worklet and bind a .koe voice bank.\n * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)\n */\n async load(koe: Blob | string): Promise<void> {\n await this.ctx.audioWorklet.addModule(this.workletUrl);\n\n this.bank = await VoiceBank.load(koe);\n this.delivered.clear();\n this.pending.clear();\n\n this.node = new AudioWorkletNode(this.ctx, 'koe-processor', {\n numberOfInputs: 0,\n numberOfOutputs: 1,\n outputChannelCount: [1],\n });\n this.node.port.postMessage({ type: 'init', manifest: this.bank.manifest });\n this.node.connect(this.ctx.destination);\n\n console.log(\n '[koe] ready —',\n Object.keys(this.bank.manifest.phonemes).length,\n 'phonemes (on-demand)',\n );\n }\n\n /** Fetch one phoneme's PCM and deliver it to the worklet (deduped, cached). */\n private ensurePhoneme(name: string): Promise<void> {\n if (this.delivered.has(name)) return Promise.resolve();\n const existing = this.pending.get(name);\n if (existing) return existing;\n if (!this.bank || !this.node) return Promise.resolve();\n\n const load = this.bank.readPcmBytes(name).then(buf => {\n if (!buf) return;\n this.node!.port.postMessage({ type: 'phoneme', name, buffer: buf }, [buf]);\n this.delivered.add(name);\n this.pending.delete(name);\n });\n\n this.pending.set(name, load);\n return load;\n }\n\n /** Stop current playback, preload the phonemes for `notes`, then queue them. */\n async play(notes: NoteEvent[]): Promise<void> {\n if (!this.node) throw new Error('KoeEngine: call load() before play()');\n this.node.port.postMessage({ type: 'stop' });\n const names = [...new Set(notes.map(n => n.phoneme))].filter(Boolean);\n await Promise.all(names.map(n => this.ensurePhoneme(n)));\n this.node.port.postMessage({ type: 'play', notes });\n }\n\n /** Stop playback and clear the queue. */\n stop(): void {\n this.node?.port.postMessage({ type: 'stop' });\n }\n\n /** Resume the AudioContext if suspended (e.g. after autoplay block). */\n async resume(): Promise<void> {\n if (this.ctx.state === 'suspended') await this.ctx.resume();\n }\n\n /**\n * Read a phoneme's raw PCM and return it as a Float64Array normalised to\n * [-1, 1]. Convenience that forwards to the underlying {@link VoiceBank}.\n * Intended for external analysis such as the WORLD vocoder.\n */\n async getPcm(phoneme: string): Promise<Float64Array | null> {\n return this.bank?.getPcm(phoneme) ?? null;\n }\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 _PhraseSynthNew(): number;\n _PhraseSynthDelete(ps: number): void;\n _PhraseSynthAddRequest(\n ps: number,\n req: number,\n posMs: number,\n skipMs: number,\n lengthMs: number,\n fadeInMs: number,\n fadeOutMs: number,\n flag: number,\n ): void;\n _PhraseSynthSetCurves(\n ps: number,\n f0: number,\n gender: number,\n tension: number,\n breathiness: number,\n voicing: number,\n length: number,\n frameMs: number,\n ): void;\n _PhraseSynthSynth(ps: number, yPtrPtr: number, flag: number): number;\n _malloc(size: number): number;\n _free(ptr: number): void;\n setValue(ptr: number, value: number, type: string): void;\n getValue(ptr: number, type: string): number;\n HEAPF32: Float32Array;\n HEAPF64: Float64Array;\n}\n\ntype WorldlineFactory = (opts?: {\n locateFile?: (path: string) => string;\n}) => Promise<WorldlineWasm>;\n\ndeclare global {\n // Injected by worldline.js when loaded as a classic <script>.\n // eslint-disable-next-line no-var\n var WorldlineModule: WorldlineFactory | undefined;\n}\n\nexport interface WorldlineLoadOptions {\n /**\n * URL of `worldline.js` (the Emscripten loader). The matching\n * `worldline.wasm` must sit next to it — it is resolved relative to this URL.\n *\n * When consuming koe from npm + a CDN this is typically e.g.\n * `https://cdn.jsdelivr.net/npm/@onjmin/koe/dist/world/worldline.js`, or your\n * own hosted copy of `dist/world/`.\n */\n scriptUrl: string;\n}\n\nexport interface RenderNoteParams {\n /**\n * Source phoneme PCM normalised to [-1, 1] (e.g. from\n * `VoiceBank.getPcm()` / `KoeEngine.getPcm()`).\n */\n pcm: Float64Array;\n /** Target output pitch in Hz. */\n pitch: number;\n /** Sustain / vowel duration in ms (the lead-in below is rendered on top). */\n durationMs: number;\n /** Preutterance / lead-in in ms — convert from {@link PhonemeEntry.pre}. */\n preMs: number;\n /** Consonant length in ms — convert from {@link PhonemeEntry.consonant}. */\n consonantMs: number;\n /** Reference tempo in BPM for worldline's internal timing. Default 120. */\n tempo?: number;\n}\n\n/** Convert a sample count at 48 kHz to milliseconds. */\nexport const samplesToMs = (samples: number): number => (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): { preMs: number; consonantMs: number } {\n return { preMs: samplesToMs(entry.pre || 0), consonantMs: samplesToMs(entry.consonant || 0) };\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 return new Promise((resolve, reject) => {\n const existing = document.querySelector<HTMLScriptElement>(`script[data-koe-worldline=\"${src}\"]`);\n if (existing) {\n resolve();\n return;\n }\n const s = document.createElement('script');\n s.src = src;\n s.dataset.koeWorldline = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`worldline: failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\nfunction loadWasm(scriptUrl: string): Promise<WorldlineWasm> {\n const cached = moduleCache.get(scriptUrl);\n if (cached) return cached;\n\n if (typeof document === 'undefined') {\n return Promise.reject(\n new Error('Worldline.load requires a DOM (browser) environment to load worldline.js'),\n );\n }\n\n const baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf('/') + 1);\n const promise = injectScript(scriptUrl).then(() => {\n const factory = globalThis.WorldlineModule;\n if (!factory) throw new Error('worldline: WorldlineModule global was not defined by the script');\n return factory({ locateFile: (f: string) => baseUrl + f });\n });\n moduleCache.set(scriptUrl, promise);\n return 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 readonly sampleRate = WORLDLINE_SAMPLE_RATE;\n\n private constructor(private wasm: WorldlineWasm) {}\n\n /** Load + instantiate the worldline WASM module (deduped per scriptUrl). */\n static async load(options: WorldlineLoadOptions): Promise<Worldline> {\n return new Worldline(await loadWasm(options.scriptUrl));\n }\n\n /**\n * Render one note to Float32 PCM at 48 kHz.\n *\n * The output buffer is laid out as [lead-in/consonant ≈ preMs][vowel ≈\n * durationMs], rendered from sample offset 0 (no leading silence). The vowel\n * onset (the \"beat\") sits at ≈ preMs into the buffer, so a sequencer should\n * place the buffer at `beatTime − preMs` and may trim/crossfade the lead-in.\n *\n * No internal crossfade is applied — apply fades externally.\n *\n * @returns Float32 PCM, or null when `pcm` is shorter than\n * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).\n */\n renderNote(params: RenderNoteParams): Float32Array | null {\n const { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;\n if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;\n\n const WL = this.wasm;\n const FS = WORLDLINE_SAMPLE_RATE;\n const midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));\n const posMs = 0; // no leading silence\n const reqLen = preMs + durationMs; // render lead-in/consonant + vowel\n\n // cut_off must trim at least ~2 frames off the tail. PhraseSynth::AddRequest\n // computes the trim frame count as ceil(in_length_ms)/frame_ms, but\n // PyinEstimator yields floor(N/nhop)−1 frames — one fewer. With cut_off=0 the\n // requested frame count exceeds f0_.size() and Model::Trim's f0_.erase() runs\n // past end(), causing \"memory access out of bounds\". Trimming 2 frames keeps\n // it within bounds.\n const cutMs = WL_FRAME_MS * 2;\n\n const ps = WL._PhraseSynthNew();\n if (!ps) return null;\n const reqPtr = WL._malloc(SYNTH_REQ_SIZE);\n if (!reqPtr) {\n WL._PhraseSynthDelete(ps);\n return null;\n }\n const samplePtr = WL._malloc(pcm.length * 8);\n if (!samplePtr) {\n WL._free(reqPtr);\n WL._PhraseSynthDelete(ps);\n return null;\n }\n WL.HEAPF64.set(pcm, samplePtr >> 3);\n\n const sv = (off: number, val: number, type: string) => WL.setValue(reqPtr + off, val, type);\n sv(0, FS, 'i32');\n sv(4, pcm.length, 'i32');\n sv(8, samplePtr, '*');\n sv(12, 0, 'i32');\n sv(16, 0, '*');\n sv(20, midiNote, 'i32');\n sv(24, 100.0, 'double');\n sv(32, 0.0, 'double');\n sv(40, reqLen, 'double');\n sv(48, consonantMs, 'double');\n sv(56, cutMs, 'double');\n sv(64, 100.0, 'double');\n sv(72, 0.0, 'double');\n sv(80, tempo, 'double');\n sv(88, 0, 'i32');\n sv(92, 0, '*');\n sv(96, 0, 'i32');\n sv(100, 0, 'i32');\n sv(104, 100, 'i32');\n sv(108, 0, 'i32');\n sv(112, 0, 'i32');\n sv(116, 100, 'i32');\n\n // skip/fadeIn/fadeOut all 0 — crossfade is handled externally.\n WL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0.0, reqLen, 0.0, 0.0, 0);\n WL._free(samplePtr);\n WL._free(reqPtr);\n\n const totalMs = posMs + reqLen + WL_FRAME_MS * 2;\n const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;\n const f0Arr = new Float64Array(nFrames).fill(pitch);\n const gArr = new Float64Array(nFrames).fill(0.5);\n const tArr = new Float64Array(nFrames).fill(0.5);\n const bArr = new Float64Array(nFrames).fill(0.5);\n const vArr = new Float64Array(nFrames).fill(1.0);\n\n const f0Ptr = WL._malloc(nFrames * 8);\n const gPtr = WL._malloc(nFrames * 8);\n const tPtr = WL._malloc(nFrames * 8);\n const bPtr = WL._malloc(nFrames * 8);\n const vPtr = WL._malloc(nFrames * 8);\n if (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {\n if (f0Ptr) WL._free(f0Ptr);\n if (gPtr) WL._free(gPtr);\n if (tPtr) WL._free(tPtr);\n if (bPtr) WL._free(bPtr);\n if (vPtr) WL._free(vPtr);\n WL._PhraseSynthDelete(ps);\n return null;\n }\n WL.HEAPF64.set(f0Arr, f0Ptr >> 3);\n WL.HEAPF64.set(gArr, gPtr >> 3);\n WL.HEAPF64.set(tArr, tPtr >> 3);\n WL.HEAPF64.set(bArr, bPtr >> 3);\n WL.HEAPF64.set(vArr, vPtr >> 3);\n WL._PhraseSynthSetCurves(ps, f0Ptr, gPtr, tPtr, bPtr, vPtr, nFrames, WL_FRAME_MS);\n WL._free(f0Ptr);\n WL._free(gPtr);\n WL._free(tPtr);\n WL._free(bPtr);\n WL._free(vPtr);\n\n const yPtrPtr = WL._malloc(4);\n if (!yPtrPtr) {\n WL._PhraseSynthDelete(ps);\n return null;\n }\n const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);\n const yPtr = WL.getValue(yPtrPtr, '*');\n const audio =\n outLen > 0 ? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice() : null;\n WL._free(yPtrPtr);\n WL._PhraseSynthDelete(ps);\n return audio;\n }\n}\n","export interface OtoEntry {\n /** Source WAV filename */\n wav: string;\n /** Phoneme alias */\n alias: string;\n /** Left blank — offset from WAV start (ms) */\n offset: number;\n /** Consonant portion end from offset (ms) */\n consonant: number;\n /** Right blank — negative = from WAV end, positive = from offset (ms) */\n cutoff: number;\n /** Preutterance from offset (ms) */\n pre: number;\n /** Overlap / crossfade region (ms) */\n overlap: 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 const entries: OtoEntry[] = [];\n\n for (const raw of content.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line || line.startsWith('#')) continue;\n\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n\n const wav = line.slice(0, eq).trim();\n const parts = line.slice(eq + 1).split(',');\n if (parts.length < 6) continue;\n\n const [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] = parts;\n // Empty alias → use filename without extension (UTAU spec default)\n const aliasStr = alias.trim() || wav.replace(/\\.[^.]+$/, '');\n const entry: OtoEntry = {\n wav,\n alias: aliasStr,\n offset: parseFloat(offsetStr) || 0,\n consonant: parseFloat(consonantStr) || 0,\n cutoff: parseFloat(cutoffStr) || 0,\n pre: parseFloat(preStr) || 0,\n overlap: parseFloat(overlapStr) || 0,\n };\n\n if (!entry.alias) continue;\n entries.push(entry);\n }\n\n return entries;\n}\n","export interface WavData {\n sampleRate: number;\n channels: number;\n /** Normalized samples in [-1, 1], interleaved if multi-channel */\n samples: 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 const view = new DataView(buf);\n\n const riff = readFourCC(view, 0);\n if (riff !== 'RIFF') throw new Error(`Not a RIFF file (got \"${riff}\")`);\n\n let sampleRate = 0;\n let channels = 0;\n let bitsPerSample = 0;\n let audioFormat = 1;\n let dataOffset = 0;\n let dataLength = 0;\n\n let pos = 12;\n while (pos < view.byteLength - 8) {\n const id = readFourCC(view, pos);\n const size = view.getUint32(pos + 4, true);\n pos += 8;\n\n if (id === 'fmt ') {\n audioFormat = view.getUint16(pos, true);\n channels = view.getUint16(pos + 2, true);\n sampleRate = view.getUint32(pos + 4, true);\n bitsPerSample = view.getUint16(pos + 14, true);\n } else if (id === 'data') {\n dataOffset = pos;\n dataLength = size;\n break;\n }\n\n pos += size + (size & 1);\n }\n\n if (!dataOffset) throw new Error('WAV has no data chunk');\n if (!channels || !sampleRate) throw new Error('WAV fmt chunk missing');\n\n const bytesPerSample = bitsPerSample >> 3;\n const totalSamples = Math.floor(dataLength / bytesPerSample);\n const samples = new Float32Array(totalSamples);\n\n for (let i = 0; i < totalSamples; i++) {\n const p = dataOffset + i * bytesPerSample;\n if (audioFormat === 3) {\n samples[i] = view.getFloat32(p, true);\n } else if (bitsPerSample === 8) {\n samples[i] = (view.getUint8(p) - 128) / 128;\n } else if (bitsPerSample === 16) {\n samples[i] = view.getInt16(p, true) / 32768;\n } else if (bitsPerSample === 24) {\n const lo = view.getUint8(p) | (view.getUint8(p + 1) << 8);\n let hi = view.getUint8(p + 2);\n if (hi & 0x80) hi = hi | 0xffffff00;\n samples[i] = ((hi << 16) | lo) / 8388608;\n }\n }\n\n return { sampleRate, channels, samples };\n}\n\n/** Mix down to mono by averaging all channels. */\nexport function toMono(wav: WavData): WavData {\n if (wav.channels === 1) return wav;\n const len = wav.samples.length / wav.channels;\n const out = new Float32Array(len);\n for (let i = 0; i < len; i++) {\n let sum = 0;\n for (let c = 0; c < wav.channels; c++) sum += wav.samples[i * wav.channels + c];\n out[i] = sum / wav.channels;\n }\n return { 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 if (wav.sampleRate === targetRate) return wav;\n const ratio = wav.sampleRate / targetRate;\n const outLen = Math.floor(wav.samples.length / ratio);\n const out = new Float32Array(outLen);\n const src = wav.samples;\n for (let i = 0; i < outLen; i++) {\n const x = i * ratio;\n const xi = Math.floor(x);\n const frac = x - xi;\n out[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;\n }\n return { sampleRate: targetRate, channels: 1, samples: out };\n}\n\n/** Convert Float32 [-1,1] samples to Int16 PCM. */\nexport function toInt16(samples: Float32Array): Int16Array {\n const out = new Int16Array(samples.length);\n for (let i = 0; i < samples.length; i++) {\n out[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);\n }\n return out;\n}\n\n/** Normalize then convert a WAV to 48kHz/16bit/mono Int16 PCM. */\nexport function normalizePcm(buf: ArrayBuffer): Int16Array {\n const wav = parseWav(buf);\n const mono = toMono(wav);\n const resampled = resample(mono, 48000);\n return toInt16(resampled.samples);\n}\n\nfunction readFourCC(view: DataView, pos: number): string {\n return String.fromCharCode(\n view.getUint8(pos),\n view.getUint8(pos + 1),\n view.getUint8(pos + 2),\n view.getUint8(pos + 3),\n );\n}\n","const SAMPLE_RATE = 48000;\n\nconst NAME_SEMITONE: Record<string, number> = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };\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 const m = /^([A-Ga-g])([#b]?)(-?\\d+)$/.exec(name);\n if (!m) return null;\n let semi = NAME_SEMITONE[m[1].toLowerCase()];\n if (m[2] === '#') semi++;\n else if (m[2] === 'b') semi--;\n const midi = (parseInt(m[3], 10) + 1) * 12 + semi;\n return 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 const m = /_([A-Ga-g][#b]?-?\\d+)$/.exec(alias);\n return 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 const DECIM = 4;\n const sr = SAMPLE_RATE / DECIM; // 12 kHz analysis rate\n const minLag = Math.floor(sr / 700); // highest f0 we look for\n const maxLag = Math.floor(sr / 70); // lowest f0 we look for\n\n const outLen = Math.floor((end - start) / DECIM);\n if (outLen < maxLag + 2) return 0;\n const win = Math.min(outLen, 1500); // up to ~0.125 s of vowel\n\n // Decimate (box-average) + remove DC, and build a prefix sum of squares so\n // each lag's window energy is O(1).\n const buf = new Float32Array(win);\n let mean = 0;\n for (let i = 0; i < win; i++) {\n let s = 0;\n const base = start + i * DECIM;\n for (let j = 0; j < DECIM; j++) s += pcm[base + j];\n buf[i] = s;\n mean += s;\n }\n mean /= win;\n\n const sq = new Float64Array(win + 1);\n for (let i = 0; i < win; i++) {\n buf[i] -= mean;\n sq[i + 1] = sq[i] + buf[i] * buf[i];\n }\n if (sq[win] < 1) return 0; // effectively silent\n\n // Normalized autocorrelation: 2·r(lag) / (E[0..n] + E[lag..lag+n]) ∈ [-1, 1].\n const norm = (lag: number): number => {\n const n = win - lag;\n let r = 0;\n for (let i = 0; i < n; i++) r += buf[i] * buf[i + lag];\n const e = sq[n] + (sq[lag + n] - sq[lag]);\n return e > 0 ? (2 * r) / e : 0;\n };\n\n let bestLag = -1;\n let best = 0;\n for (let lag = minLag; lag <= maxLag; lag++) {\n const v = norm(lag);\n if (v > best) {\n best = v;\n bestLag = lag;\n }\n }\n if (bestLag < 1 || best < 0.4) return 0; // unvoiced / no clear period\n\n // Parabolic interpolation around the peak\n const y0 = norm(bestLag - 1);\n const y1 = best;\n const y2 = norm(bestLag + 1);\n const denom = y0 - 2 * y1 + y2;\n const shift = denom !== 0 ? (0.5 * (y0 - y2)) / denom : 0;\n\n return 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 return Math.round((ms / 1000) * TARGET_RATE);\n}\n\nconst clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n\nexport interface PackInput {\n oto: OtoEntry;\n /** Full normalized PCM of the source WAV (48kHz / 16bit / mono) */\n pcm: Int16Array;\n /** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */\n recordedPitch?: number;\n}\n\nexport interface PackOutput {\n manifest: Manifest;\n /** Raw PCM blob — Int16 / 48kHz / mono */\n bin: ArrayBuffer;\n}\n\nexport interface TrimmedPhoneme {\n /** PCM trimmed to the oto region [offset, cutoff] */\n pcm: Int16Array;\n /** Manifest params relative to the trimmed start (sample 0 = oto offset) */\n entry: 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(pcm: Int16Array, oto: OtoEntry, recordedPitch = 0): TrimmedPhoneme {\n const full = pcm.length;\n\n const start = clamp(msToSamples(oto.offset), 0, full);\n const end = oto.cutoff < 0\n ? clamp(start + msToSamples(-oto.cutoff), start, full)\n : clamp(full - msToSamples(oto.cutoff), start, full);\n\n const slice = pcm.subarray(start, end);\n const length = slice.length;\n\n const pre = clamp(msToSamples(oto.pre), 0, length);\n const overlap = clamp(msToSamples(oto.overlap), 0, length);\n const consonant = clamp(msToSamples(oto.consonant), 0, length);\n\n // Prefer the known recorded pitch (from the .frq file or alias suffix); only\n // fall back to autocorrelation on the sustained vowel when none is available.\n const pitch = recordedPitch > 0\n ? recordedPitch\n : detectF0(slice, Math.min(Math.max(pre, consonant), Math.max(0, length - 1)), length);\n\n return {\n pcm: slice,\n entry: { length, pre, overlap, consonant, pitch },\n };\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 const phonemes: Record<string, PhonemeEntry> = {};\n const chunks: Int16Array[] = [];\n let byteOffset = 0;\n\n for (const { oto, pcm, recordedPitch } of inputs) {\n const { pcm: slice, entry } = trimToOto(pcm, oto, recordedPitch);\n if (slice.length === 0) continue;\n\n phonemes[oto.alias] = { offset: byteOffset, ...entry };\n byteOffset += slice.byteLength;\n chunks.push(slice);\n }\n\n const bin = new ArrayBuffer(byteOffset);\n const view = new Uint8Array(bin);\n let pos = 0;\n for (const chunk of chunks) {\n view.set(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength), pos);\n pos += chunk.byteLength;\n }\n\n const manifest: Manifest = {\n sampleRate: 48000,\n referencePitch,\n phonemes,\n };\n\n return { 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 if (buffer.byteLength < 20) return null;\n const view = new DataView(buffer);\n\n let header = '';\n for (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));\n if (header !== 'FREQ0003') return null;\n\n const avg = view.getFloat64(12, true); // little-endian\n return 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 const dot = wavName.lastIndexOf('.');\n const base = dot >= 0 ? wavName.slice(0, dot) : wavName;\n const ext = dot >= 0 ? wavName.slice(dot + 1) : 'wav';\n return `${base}_${ext}.frq`;\n}\n"],"mappings":"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","names":["header","jsonLength","json","manifest"]}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/converter/cli.ts
|
|
5
|
+
import { readdir, readFile, writeFile, mkdir } from "fs/promises";
|
|
6
|
+
import { join, dirname, basename, resolve } from "path";
|
|
7
|
+
|
|
8
|
+
// src/converter/parse-oto.ts
|
|
9
|
+
function parseOto(content) {
|
|
10
|
+
const entries = [];
|
|
11
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
12
|
+
const line = raw.trim();
|
|
13
|
+
if (!line || line.startsWith("#")) continue;
|
|
14
|
+
const eq = line.indexOf("=");
|
|
15
|
+
if (eq === -1) continue;
|
|
16
|
+
const wav = line.slice(0, eq).trim();
|
|
17
|
+
const parts = line.slice(eq + 1).split(",");
|
|
18
|
+
if (parts.length < 6) continue;
|
|
19
|
+
const [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] = parts;
|
|
20
|
+
const aliasStr = alias.trim() || wav.replace(/\.[^.]+$/, "");
|
|
21
|
+
const entry = {
|
|
22
|
+
wav,
|
|
23
|
+
alias: aliasStr,
|
|
24
|
+
offset: parseFloat(offsetStr) || 0,
|
|
25
|
+
consonant: parseFloat(consonantStr) || 0,
|
|
26
|
+
cutoff: parseFloat(cutoffStr) || 0,
|
|
27
|
+
pre: parseFloat(preStr) || 0,
|
|
28
|
+
overlap: parseFloat(overlapStr) || 0
|
|
29
|
+
};
|
|
30
|
+
if (!entry.alias) continue;
|
|
31
|
+
entries.push(entry);
|
|
32
|
+
}
|
|
33
|
+
return entries;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/converter/wav.ts
|
|
37
|
+
function parseWav(buf) {
|
|
38
|
+
const view = new DataView(buf);
|
|
39
|
+
const riff = readFourCC(view, 0);
|
|
40
|
+
if (riff !== "RIFF") throw new Error(`Not a RIFF file (got "${riff}")`);
|
|
41
|
+
let sampleRate = 0;
|
|
42
|
+
let channels = 0;
|
|
43
|
+
let bitsPerSample = 0;
|
|
44
|
+
let audioFormat = 1;
|
|
45
|
+
let dataOffset = 0;
|
|
46
|
+
let dataLength = 0;
|
|
47
|
+
let pos = 12;
|
|
48
|
+
while (pos < view.byteLength - 8) {
|
|
49
|
+
const id = readFourCC(view, pos);
|
|
50
|
+
const size = view.getUint32(pos + 4, true);
|
|
51
|
+
pos += 8;
|
|
52
|
+
if (id === "fmt ") {
|
|
53
|
+
audioFormat = view.getUint16(pos, true);
|
|
54
|
+
channels = view.getUint16(pos + 2, true);
|
|
55
|
+
sampleRate = view.getUint32(pos + 4, true);
|
|
56
|
+
bitsPerSample = view.getUint16(pos + 14, true);
|
|
57
|
+
} else if (id === "data") {
|
|
58
|
+
dataOffset = pos;
|
|
59
|
+
dataLength = size;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
pos += size + (size & 1);
|
|
63
|
+
}
|
|
64
|
+
if (!dataOffset) throw new Error("WAV has no data chunk");
|
|
65
|
+
if (!channels || !sampleRate) throw new Error("WAV fmt chunk missing");
|
|
66
|
+
const bytesPerSample = bitsPerSample >> 3;
|
|
67
|
+
const totalSamples = Math.floor(dataLength / bytesPerSample);
|
|
68
|
+
const samples = new Float32Array(totalSamples);
|
|
69
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
70
|
+
const p = dataOffset + i * bytesPerSample;
|
|
71
|
+
if (audioFormat === 3) {
|
|
72
|
+
samples[i] = view.getFloat32(p, true);
|
|
73
|
+
} else if (bitsPerSample === 8) {
|
|
74
|
+
samples[i] = (view.getUint8(p) - 128) / 128;
|
|
75
|
+
} else if (bitsPerSample === 16) {
|
|
76
|
+
samples[i] = view.getInt16(p, true) / 32768;
|
|
77
|
+
} else if (bitsPerSample === 24) {
|
|
78
|
+
const lo = view.getUint8(p) | view.getUint8(p + 1) << 8;
|
|
79
|
+
let hi = view.getUint8(p + 2);
|
|
80
|
+
if (hi & 128) hi = hi | 4294967040;
|
|
81
|
+
samples[i] = (hi << 16 | lo) / 8388608;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { sampleRate, channels, samples };
|
|
85
|
+
}
|
|
86
|
+
function toMono(wav) {
|
|
87
|
+
if (wav.channels === 1) return wav;
|
|
88
|
+
const len = wav.samples.length / wav.channels;
|
|
89
|
+
const out = new Float32Array(len);
|
|
90
|
+
for (let i = 0; i < len; i++) {
|
|
91
|
+
let sum = 0;
|
|
92
|
+
for (let c = 0; c < wav.channels; c++) sum += wav.samples[i * wav.channels + c];
|
|
93
|
+
out[i] = sum / wav.channels;
|
|
94
|
+
}
|
|
95
|
+
return { sampleRate: wav.sampleRate, channels: 1, samples: out };
|
|
96
|
+
}
|
|
97
|
+
function resample(wav, targetRate) {
|
|
98
|
+
if (wav.sampleRate === targetRate) return wav;
|
|
99
|
+
const ratio = wav.sampleRate / targetRate;
|
|
100
|
+
const outLen = Math.floor(wav.samples.length / ratio);
|
|
101
|
+
const out = new Float32Array(outLen);
|
|
102
|
+
const src = wav.samples;
|
|
103
|
+
for (let i = 0; i < outLen; i++) {
|
|
104
|
+
const x = i * ratio;
|
|
105
|
+
const xi = Math.floor(x);
|
|
106
|
+
const frac = x - xi;
|
|
107
|
+
out[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;
|
|
108
|
+
}
|
|
109
|
+
return { sampleRate: targetRate, channels: 1, samples: out };
|
|
110
|
+
}
|
|
111
|
+
function toInt16(samples) {
|
|
112
|
+
const out = new Int16Array(samples.length);
|
|
113
|
+
for (let i = 0; i < samples.length; i++) {
|
|
114
|
+
out[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function normalizePcm(buf) {
|
|
119
|
+
const wav = parseWav(buf);
|
|
120
|
+
const mono = toMono(wav);
|
|
121
|
+
const resampled = resample(mono, 48e3);
|
|
122
|
+
return toInt16(resampled.samples);
|
|
123
|
+
}
|
|
124
|
+
function readFourCC(view, pos) {
|
|
125
|
+
return String.fromCharCode(
|
|
126
|
+
view.getUint8(pos),
|
|
127
|
+
view.getUint8(pos + 1),
|
|
128
|
+
view.getUint8(pos + 2),
|
|
129
|
+
view.getUint8(pos + 3)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/converter/pitch.ts
|
|
134
|
+
var SAMPLE_RATE = 48e3;
|
|
135
|
+
var NAME_SEMITONE = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
|
|
136
|
+
function noteNameToHz(name) {
|
|
137
|
+
const m = /^([A-Ga-g])([#b]?)(-?\d+)$/.exec(name);
|
|
138
|
+
if (!m) return null;
|
|
139
|
+
let semi = NAME_SEMITONE[m[1].toLowerCase()];
|
|
140
|
+
if (m[2] === "#") semi++;
|
|
141
|
+
else if (m[2] === "b") semi--;
|
|
142
|
+
const midi = (parseInt(m[3], 10) + 1) * 12 + semi;
|
|
143
|
+
return 440 * 2 ** ((midi - 69) / 12);
|
|
144
|
+
}
|
|
145
|
+
function pitchFromAliasSuffix(alias) {
|
|
146
|
+
const m = /_([A-Ga-g][#b]?-?\d+)$/.exec(alias);
|
|
147
|
+
return m ? noteNameToHz(m[1]) : null;
|
|
148
|
+
}
|
|
149
|
+
function detectF0(pcm, start, end) {
|
|
150
|
+
const DECIM = 4;
|
|
151
|
+
const sr = SAMPLE_RATE / DECIM;
|
|
152
|
+
const minLag = Math.floor(sr / 700);
|
|
153
|
+
const maxLag = Math.floor(sr / 70);
|
|
154
|
+
const outLen = Math.floor((end - start) / DECIM);
|
|
155
|
+
if (outLen < maxLag + 2) return 0;
|
|
156
|
+
const win = Math.min(outLen, 1500);
|
|
157
|
+
const buf = new Float32Array(win);
|
|
158
|
+
let mean = 0;
|
|
159
|
+
for (let i = 0; i < win; i++) {
|
|
160
|
+
let s = 0;
|
|
161
|
+
const base = start + i * DECIM;
|
|
162
|
+
for (let j = 0; j < DECIM; j++) s += pcm[base + j];
|
|
163
|
+
buf[i] = s;
|
|
164
|
+
mean += s;
|
|
165
|
+
}
|
|
166
|
+
mean /= win;
|
|
167
|
+
const sq = new Float64Array(win + 1);
|
|
168
|
+
for (let i = 0; i < win; i++) {
|
|
169
|
+
buf[i] -= mean;
|
|
170
|
+
sq[i + 1] = sq[i] + buf[i] * buf[i];
|
|
171
|
+
}
|
|
172
|
+
if (sq[win] < 1) return 0;
|
|
173
|
+
const norm = (lag) => {
|
|
174
|
+
const n = win - lag;
|
|
175
|
+
let r = 0;
|
|
176
|
+
for (let i = 0; i < n; i++) r += buf[i] * buf[i + lag];
|
|
177
|
+
const e = sq[n] + (sq[lag + n] - sq[lag]);
|
|
178
|
+
return e > 0 ? 2 * r / e : 0;
|
|
179
|
+
};
|
|
180
|
+
let bestLag = -1;
|
|
181
|
+
let best = 0;
|
|
182
|
+
for (let lag = minLag; lag <= maxLag; lag++) {
|
|
183
|
+
const v = norm(lag);
|
|
184
|
+
if (v > best) {
|
|
185
|
+
best = v;
|
|
186
|
+
bestLag = lag;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (bestLag < 1 || best < 0.4) return 0;
|
|
190
|
+
const y0 = norm(bestLag - 1);
|
|
191
|
+
const y1 = best;
|
|
192
|
+
const y2 = norm(bestLag + 1);
|
|
193
|
+
const denom = y0 - 2 * y1 + y2;
|
|
194
|
+
const shift = denom !== 0 ? 0.5 * (y0 - y2) / denom : 0;
|
|
195
|
+
return sr / (bestLag + shift);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/converter/pack.ts
|
|
199
|
+
var TARGET_RATE = 48e3;
|
|
200
|
+
function msToSamples(ms) {
|
|
201
|
+
return Math.round(ms / 1e3 * TARGET_RATE);
|
|
202
|
+
}
|
|
203
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
204
|
+
function trimToOto(pcm, oto, recordedPitch = 0) {
|
|
205
|
+
const full = pcm.length;
|
|
206
|
+
const start = clamp(msToSamples(oto.offset), 0, full);
|
|
207
|
+
const end = oto.cutoff < 0 ? clamp(start + msToSamples(-oto.cutoff), start, full) : clamp(full - msToSamples(oto.cutoff), start, full);
|
|
208
|
+
const slice = pcm.subarray(start, end);
|
|
209
|
+
const length = slice.length;
|
|
210
|
+
const pre = clamp(msToSamples(oto.pre), 0, length);
|
|
211
|
+
const overlap = clamp(msToSamples(oto.overlap), 0, length);
|
|
212
|
+
const consonant = clamp(msToSamples(oto.consonant), 0, length);
|
|
213
|
+
const pitch = recordedPitch > 0 ? recordedPitch : detectF0(slice, Math.min(Math.max(pre, consonant), Math.max(0, length - 1)), length);
|
|
214
|
+
return {
|
|
215
|
+
pcm: slice,
|
|
216
|
+
entry: { length, pre, overlap, consonant, pitch }
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function pack(inputs, referencePitch = 220) {
|
|
220
|
+
const phonemes = {};
|
|
221
|
+
const chunks = [];
|
|
222
|
+
let byteOffset = 0;
|
|
223
|
+
for (const { oto, pcm, recordedPitch } of inputs) {
|
|
224
|
+
const { pcm: slice, entry } = trimToOto(pcm, oto, recordedPitch);
|
|
225
|
+
if (slice.length === 0) continue;
|
|
226
|
+
phonemes[oto.alias] = { offset: byteOffset, ...entry };
|
|
227
|
+
byteOffset += slice.byteLength;
|
|
228
|
+
chunks.push(slice);
|
|
229
|
+
}
|
|
230
|
+
const bin = new ArrayBuffer(byteOffset);
|
|
231
|
+
const view = new Uint8Array(bin);
|
|
232
|
+
let pos = 0;
|
|
233
|
+
for (const chunk of chunks) {
|
|
234
|
+
view.set(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength), pos);
|
|
235
|
+
pos += chunk.byteLength;
|
|
236
|
+
}
|
|
237
|
+
const manifest = {
|
|
238
|
+
sampleRate: 48e3,
|
|
239
|
+
referencePitch,
|
|
240
|
+
phonemes
|
|
241
|
+
};
|
|
242
|
+
return { manifest, bin };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/converter/frq.ts
|
|
246
|
+
function parseFrqAverageF0(buffer) {
|
|
247
|
+
if (buffer.byteLength < 20) return null;
|
|
248
|
+
const view = new DataView(buffer);
|
|
249
|
+
let header = "";
|
|
250
|
+
for (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));
|
|
251
|
+
if (header !== "FREQ0003") return null;
|
|
252
|
+
const avg = view.getFloat64(12, true);
|
|
253
|
+
return Number.isFinite(avg) && avg > 0 ? avg : null;
|
|
254
|
+
}
|
|
255
|
+
function frqFileName(wavName) {
|
|
256
|
+
const dot = wavName.lastIndexOf(".");
|
|
257
|
+
const base = dot >= 0 ? wavName.slice(0, dot) : wavName;
|
|
258
|
+
const ext = dot >= 0 ? wavName.slice(dot + 1) : "wav";
|
|
259
|
+
return `${base}_${ext}.frq`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/koe.ts
|
|
263
|
+
var MAGIC = 1263486208;
|
|
264
|
+
function packKoe(manifest, pcmParts) {
|
|
265
|
+
const json = new TextEncoder().encode(JSON.stringify(manifest));
|
|
266
|
+
const header = new ArrayBuffer(8);
|
|
267
|
+
const view = new DataView(header);
|
|
268
|
+
view.setUint32(0, MAGIC, false);
|
|
269
|
+
view.setUint32(4, json.byteLength, true);
|
|
270
|
+
return new Blob([header, json, ...pcmParts]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/converter/cli.ts
|
|
274
|
+
var [voiceDir, outDir = "dist"] = process.argv.slice(2);
|
|
275
|
+
if (!voiceDir) {
|
|
276
|
+
process.stderr.write("Usage: koe-convert <voice-dir> [output-dir]\n");
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
async function findOtoFiles(dir) {
|
|
280
|
+
const found = [];
|
|
281
|
+
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
282
|
+
for (const e of entries) {
|
|
283
|
+
if (e.isFile() && e.name.toLowerCase() === "oto.ini") {
|
|
284
|
+
found.push(join(e.parentPath ?? dirname(join(dir, e.name)), e.name));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return found;
|
|
288
|
+
}
|
|
289
|
+
async function main() {
|
|
290
|
+
const otoFiles = await findOtoFiles(voiceDir);
|
|
291
|
+
if (otoFiles.length === 0) {
|
|
292
|
+
process.stderr.write(`No oto.ini found under "${voiceDir}"
|
|
293
|
+
`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
const inputs = [];
|
|
297
|
+
let skipped = 0;
|
|
298
|
+
for (const otoPath of otoFiles) {
|
|
299
|
+
const otoDir = dirname(otoPath);
|
|
300
|
+
const rawBytes = await readFile(otoPath);
|
|
301
|
+
const content = new TextDecoder("shift_jis").decode(rawBytes);
|
|
302
|
+
const entries = parseOto(content);
|
|
303
|
+
for (const oto of entries) {
|
|
304
|
+
const wavPath = join(otoDir, oto.wav);
|
|
305
|
+
try {
|
|
306
|
+
const wavBytes = await readFile(wavPath);
|
|
307
|
+
const pcm = normalizePcm(wavBytes.buffer);
|
|
308
|
+
let recordedPitch = pitchFromAliasSuffix(oto.alias) ?? 0;
|
|
309
|
+
try {
|
|
310
|
+
const frqBytes = await readFile(join(otoDir, frqFileName(oto.wav)));
|
|
311
|
+
const avg = parseFrqAverageF0(frqBytes.buffer);
|
|
312
|
+
if (avg) recordedPitch = avg;
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
inputs.push({ oto, pcm, recordedPitch });
|
|
316
|
+
} catch (err) {
|
|
317
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
318
|
+
process.stderr.write(`[skip] ${oto.alias} (${basename(wavPath)}): ${msg}
|
|
319
|
+
`);
|
|
320
|
+
skipped++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (inputs.length === 0) {
|
|
325
|
+
process.stderr.write("No phonemes could be converted.\n");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
const { manifest, bin } = pack(inputs);
|
|
329
|
+
const koeName = basename(resolve(voiceDir)) + ".koe";
|
|
330
|
+
const koe = packKoe(manifest, [bin]);
|
|
331
|
+
const koeBytes = Buffer.from(await koe.arrayBuffer());
|
|
332
|
+
await mkdir(outDir, { recursive: true });
|
|
333
|
+
await writeFile(join(outDir, koeName), koeBytes);
|
|
334
|
+
const kb = (koeBytes.byteLength / 1024).toFixed(1);
|
|
335
|
+
process.stdout.write(
|
|
336
|
+
`Converted ${inputs.length} phonemes (${skipped} skipped) \u2192 ${join(outDir, koeName)} [${kb} KB]
|
|
337
|
+
`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
main().catch((err) => {
|
|
341
|
+
process.stderr.write(`${err}
|
|
342
|
+
`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(()=>{var m=new Int16Array(0),u=class extends AudioWorkletProcessor{manifest=null;phonemes=new Map;queue=[];current=null;next=null;constructor(e){super(e),this.port.onmessage=t=>this.onMessage(t.data)}onMessage(e){e.type==="init"&&e.manifest?this.manifest=e.manifest:e.type==="phoneme"&&e.name&&e.buffer?this.phonemes.set(e.name,new Int16Array(e.buffer)):e.type==="play"&&e.notes?this.queue.push(...e.notes):e.type==="stop"&&(this.queue=[],this.current=null,this.next=null)}dequeue(){let e=this.queue.shift();if(!e||!this.manifest)return null;if(e.phoneme==="")return{data:m,length:0,consonant:0,pre:0,overlap:0,readPos:0,stepRate:1,remaining:e.duration};let t=this.manifest.phonemes[e.phoneme],n=this.phonemes.get(e.phoneme);if(!t||!n)return null;let i=t.pitch||this.manifest.referencePitch;return{data:n,length:t.length,consonant:t.consonant,pre:t.pre,overlap:t.overlap,readPos:0,stepRate:e.pitch/i,remaining:e.duration}}readSample(e,t){let n=t|0,i=t-n;return((e[n]??0)+((e[n+1]??0)-(e[n]??0))*i)/32768}advance(e){if(!(e.length<=0)&&(e.readPos+=e.stepRate,e.readPos>=e.length)){let t=e.length-e.consonant;e.readPos=t>0?e.consonant+(e.readPos-e.consonant)%t:e.length-1}}process(e,t){let n=t[0]?.[0];if(!n)return!0;for(let i=0;i<n.length;i++){if(!this.current||this.current.remaining<=0){let s=this.next!==null;if(this.current=this.next??this.dequeue(),this.next=null,!this.current){n[i]=0;continue}s||(this.current.readPos=this.current.pre)}let r=this.current;!this.next&&this.queue.length>0&&(this.next=this.dequeue());let a=this.readSample(r.data,r.readPos);if(!this.next&&this.queue.length===0&&r.remaining<=480&&r.length>0&&(a*=r.remaining/480),this.next){let s=this.next,o=Math.max(s.pre/s.stepRate,480);if(r.remaining<=o){let h=Math.min(Math.max(s.overlap/s.stepRate,480),o),l=o-r.remaining,c=Math.min(1,l/h),p=this.readSample(s.data,s.readPos);a=a*(1-c)+p*c,this.advance(s)}}n[i]=a,this.advance(r),r.remaining--}return!0}};registerProcessor("koe-processor",u);})();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var WorldlineModule=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["f"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("worldline.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr];case"i8":return HEAP8[ptr];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}var noExitRuntime=true;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr]=value;break;case"i8":HEAP8[ptr]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var ___assert_fail=(condition,filename,line,func)=>abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"]);class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;abort()};var __abort_js=()=>abort("");var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i<args.length;i++){var converter=toC[argTypes[i]];if(converter){if(stack===0)stack=stackSave();cArgs[i]=converter(args[i])}else{cArgs[i]=args[i]}}}var ret=func(...cArgs);function onDone(ret){if(stack!==0)stackRestore(stack);return convertReturnValue(ret)}ret=onDone(ret);return ret};var cwrap=(ident,returnType,argTypes,opts)=>{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["ccall"]=ccall;Module["cwrap"]=cwrap;Module["setValue"]=setValue;Module["getValue"]=getValue;var _F0,_WorldSynthesis,_Resample,_PhraseSynthNew,_PhraseSynthDelete,_PhraseSynthAddRequest,_PhraseSynthSetCurves,_PhraseSynthSynth,_free,_malloc,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_F0=Module["_F0"]=wasmExports["g"];_WorldSynthesis=Module["_WorldSynthesis"]=wasmExports["h"];_Resample=Module["_Resample"]=wasmExports["i"];_PhraseSynthNew=Module["_PhraseSynthNew"]=wasmExports["j"];_PhraseSynthDelete=Module["_PhraseSynthDelete"]=wasmExports["k"];_PhraseSynthAddRequest=Module["_PhraseSynthAddRequest"]=wasmExports["l"];_PhraseSynthSetCurves=Module["_PhraseSynthSetCurves"]=wasmExports["m"];_PhraseSynthSynth=Module["_PhraseSynthSynth"]=wasmExports["n"];_free=Module["_free"]=wasmExports["o"];_malloc=Module["_malloc"]=wasmExports["p"];__emscripten_stack_restore=wasmExports["q"];__emscripten_stack_alloc=wasmExports["r"];_emscripten_stack_get_current=wasmExports["s"];memory=wasmMemory=wasmExports["e"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:___assert_fail,b:___cxa_throw,d:__abort_js,c:_emscripten_resize_heap};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})}
|
|
2
|
+
;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=WorldlineModule;module.exports.default=WorldlineModule}else if(typeof define==="function"&&define["amd"])define([],()=>WorldlineModule);
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "onjmin",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"name": "@onjmin/koe",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "UTAU concatenative synthesis engine optimized for browser / mobile via WebAssembly + AudioWorklet",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"koe-convert": "./dist/koe-convert.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsx src/dev-server.ts",
|
|
26
|
+
"watch": "tsup --watch",
|
|
27
|
+
"convert": "tsx src/converter/cli.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"lint": "biome check src",
|
|
30
|
+
"clean": "rimraf dist"
|
|
31
|
+
},
|
|
32
|
+
"keywords": ["utau", "singing-synthesis", "audioworklet", "wasm"],
|
|
33
|
+
"packageManager": "pnpm@10.17.1",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@biomejs/biome": "2.5.0",
|
|
36
|
+
"@hono/node-server": "2.0.5",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"hono": "4.12.25",
|
|
39
|
+
"rimraf": "6.1.3",
|
|
40
|
+
"tsup": "8.5.1",
|
|
41
|
+
"tsx": "4.22.4",
|
|
42
|
+
"typescript": "6.0.3"
|
|
43
|
+
},
|
|
44
|
+
"volta": {
|
|
45
|
+
"node": "24.16.0",
|
|
46
|
+
"pnpm": "11.7.0"
|
|
47
|
+
}
|
|
48
|
+
}
|