@onjmin/koe 1.0.3 → 1.0.5
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/dist/index.d.ts +166 -112
- package/dist/index.js +1240 -606
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +182 -154
- package/dist/koe-worklet.js +1 -1
- package/package.json +4 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UTAU `.frq` frequency-analysis files (FREQ0003). The same format OpenUtau
|
|
3
|
+
* reads: an 8-byte header, hop size, then the average fundamental frequency of
|
|
4
|
+
* the recording — exactly the reference pitch we need for correct resampling.
|
|
5
|
+
*
|
|
6
|
+
* Layout:
|
|
7
|
+
* char[8] "FREQ0003"
|
|
8
|
+
* int32 hopSize
|
|
9
|
+
* float64 averageF0 ← the recorded pitch in Hz
|
|
10
|
+
* byte[16] (blank)
|
|
11
|
+
* int32 length
|
|
12
|
+
* { float64 f0, float64 amp } × length
|
|
13
|
+
*/
|
|
14
|
+
declare function parseFrqAverageF0(buffer: ArrayBuffer): number | null;
|
|
15
|
+
/** Map a WAV filename to its sibling frq filename: "あ.wav" → "あ_wav.frq". */
|
|
16
|
+
declare function frqFileName(wavName: string): string;
|
|
17
|
+
|
|
1
18
|
/**
|
|
2
19
|
* One phoneme, already trimmed to its usable oto region.
|
|
3
20
|
* Sample 0 corresponds to the oto `offset` (left blank); everything before it
|
|
@@ -36,6 +53,113 @@ interface NoteEvent {
|
|
|
36
53
|
duration: number;
|
|
37
54
|
}
|
|
38
55
|
|
|
56
|
+
interface OtoEntry {
|
|
57
|
+
/** Source WAV filename */
|
|
58
|
+
wav: string;
|
|
59
|
+
/** Phoneme alias */
|
|
60
|
+
alias: string;
|
|
61
|
+
/** Left blank — offset from WAV start (ms) */
|
|
62
|
+
offset: number;
|
|
63
|
+
/** Consonant portion end from offset (ms) */
|
|
64
|
+
consonant: number;
|
|
65
|
+
/** Right blank — negative = from WAV end, positive = from offset (ms) */
|
|
66
|
+
cutoff: number;
|
|
67
|
+
/** Preutterance from offset (ms) */
|
|
68
|
+
pre: number;
|
|
69
|
+
/** Overlap / crossfade region (ms) */
|
|
70
|
+
overlap: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse oto.ini content (already decoded to UTF-8 string).
|
|
74
|
+
* Silently skips malformed lines.
|
|
75
|
+
*/
|
|
76
|
+
declare function parseOto(content: string): OtoEntry[];
|
|
77
|
+
|
|
78
|
+
interface PackInput {
|
|
79
|
+
oto: OtoEntry;
|
|
80
|
+
/** Full normalized PCM of the source WAV (48kHz / 16bit / mono) */
|
|
81
|
+
pcm: Int16Array;
|
|
82
|
+
/** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */
|
|
83
|
+
recordedPitch?: number;
|
|
84
|
+
}
|
|
85
|
+
interface PackOutput {
|
|
86
|
+
manifest: Manifest;
|
|
87
|
+
/** Raw PCM blob — Int16 / 48kHz / mono */
|
|
88
|
+
bin: ArrayBuffer;
|
|
89
|
+
}
|
|
90
|
+
interface TrimmedPhoneme {
|
|
91
|
+
/** PCM trimmed to the oto region [offset, cutoff] */
|
|
92
|
+
pcm: Int16Array;
|
|
93
|
+
/** Manifest params relative to the trimmed start (sample 0 = oto offset) */
|
|
94
|
+
entry: Omit<PhonemeEntry, "offset">;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Cut the full WAV PCM down to its usable oto region and recompute parameters
|
|
98
|
+
* relative to the trimmed start.
|
|
99
|
+
*
|
|
100
|
+
* UTAU oto.ini values are all in ms and measured from `offset` (the left blank),
|
|
101
|
+
* except `cutoff` (right blank):
|
|
102
|
+
* - cutoff >= 0 : measured from the END of the file
|
|
103
|
+
* - cutoff < 0 : region length from offset = |cutoff|
|
|
104
|
+
*
|
|
105
|
+
* After trimming, sample 0 == oto offset, so pre/overlap/consonant carry over
|
|
106
|
+
* unchanged (just converted to samples), and the slice length is the region end.
|
|
107
|
+
*/
|
|
108
|
+
declare function trimToOto(pcm: Int16Array, oto: OtoEntry, recordedPitch?: number): TrimmedPhoneme;
|
|
109
|
+
/**
|
|
110
|
+
* Pack normalized PCM phonemes into voice.bin + manifest.json.
|
|
111
|
+
* Each phoneme is trimmed to its oto region first.
|
|
112
|
+
* Duplicate aliases are silently overwritten by the later entry.
|
|
113
|
+
*/
|
|
114
|
+
declare function pack(inputs: PackInput[], referencePitch?: number): PackOutput;
|
|
115
|
+
|
|
116
|
+
/** Parse a note name like "E4", "G#4", "Db5" → frequency in Hz (null if invalid). */
|
|
117
|
+
declare function noteNameToHz(name: string): number | null;
|
|
118
|
+
/** Recorded pitch encoded in a multi-pitch alias suffix: "a い_E4" → 329.63 Hz. */
|
|
119
|
+
declare function pitchFromAliasSuffix(alias: string): number | null;
|
|
120
|
+
/**
|
|
121
|
+
* Estimate the fundamental frequency (Hz) of a voiced region by normalized
|
|
122
|
+
* autocorrelation. Returns 0 when no clear pitch is found (unvoiced consonant,
|
|
123
|
+
* silence, or a region too short to analyse).
|
|
124
|
+
*
|
|
125
|
+
* The signal is decimated to a lower analysis rate for speed; f0 below ~700 Hz
|
|
126
|
+
* is well within the resulting Nyquist limit. A parabolic interpolation around
|
|
127
|
+
* the best lag gives sub-sample (sub-semitone) accuracy.
|
|
128
|
+
*/
|
|
129
|
+
declare function detectF0(pcm: Int16Array, start: number, end: number): number;
|
|
130
|
+
|
|
131
|
+
interface WavData {
|
|
132
|
+
sampleRate: number;
|
|
133
|
+
channels: number;
|
|
134
|
+
/** Normalized samples in [-1, 1], interleaved if multi-channel */
|
|
135
|
+
samples: Float32Array;
|
|
136
|
+
}
|
|
137
|
+
/** Parse a WAV file from an ArrayBuffer. Supports PCM 8/16/24-bit and IEEE float 32-bit. */
|
|
138
|
+
declare function parseWav(buf: ArrayBuffer): WavData;
|
|
139
|
+
/** Mix down to mono by averaging all channels. */
|
|
140
|
+
declare function toMono(wav: WavData): WavData;
|
|
141
|
+
/** Linear interpolation resample to targetRate. Expects mono input. */
|
|
142
|
+
declare function resample(wav: WavData, targetRate: number): WavData;
|
|
143
|
+
/** Convert Float32 [-1,1] samples to Int16 PCM. */
|
|
144
|
+
declare function toInt16(samples: Float32Array): Int16Array;
|
|
145
|
+
/** Normalize then convert a WAV to 48kHz/16bit/mono Int16 PCM. */
|
|
146
|
+
declare function normalizePcm(buf: ArrayBuffer): Int16Array;
|
|
147
|
+
|
|
148
|
+
/** Minimal file-like handle so zip entries can stand in for `File` objects. */
|
|
149
|
+
interface ZipFile {
|
|
150
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Extracts a zip archive into a flat path → file map, mirroring the shape of
|
|
154
|
+
* `webkitdirectory` file lists so the converter can treat both sources the same.
|
|
155
|
+
*
|
|
156
|
+
* Many UTAU voicebank zips are packed on Windows without the UTF-8 flag, so
|
|
157
|
+
* entry names are Shift_JIS bytes. fflate decodes those as latin1 (1 byte =
|
|
158
|
+
* 1 code point) rather than mangling them, so we can losslessly recover the
|
|
159
|
+
* original bytes and re-decode with the right encoding per entry.
|
|
160
|
+
*/
|
|
161
|
+
declare function unzipToFileMap(data: ArrayBuffer): Promise<Record<string, ZipFile>>;
|
|
162
|
+
|
|
39
163
|
/**
|
|
40
164
|
* Read-only access to a .koe voice bank: its manifest plus per-phoneme PCM,
|
|
41
165
|
* fetched on demand (Blob slice or HTTP Range). The full bank is never held in
|
|
@@ -59,6 +183,7 @@ declare class VoiceBank {
|
|
|
59
183
|
* @param koe a Blob/File of the .koe archive, or a URL (served with Range support)
|
|
60
184
|
*/
|
|
61
185
|
static load(koe: Blob | string): Promise<VoiceBank>;
|
|
186
|
+
private static fromBlob;
|
|
62
187
|
/** True if the bank contains a phoneme under this alias. */
|
|
63
188
|
has(phoneme: string): boolean;
|
|
64
189
|
/**
|
|
@@ -118,6 +243,11 @@ declare class KoeEngine {
|
|
|
118
243
|
stop(): void;
|
|
119
244
|
/** Resume the AudioContext if suspended (e.g. after autoplay block). */
|
|
120
245
|
resume(): Promise<void>;
|
|
246
|
+
/**
|
|
247
|
+
* Tear down the worklet node and close the AudioContext, releasing the audio
|
|
248
|
+
* hardware. The engine cannot be reused afterwards — create a new one.
|
|
249
|
+
*/
|
|
250
|
+
dispose(): Promise<void>;
|
|
121
251
|
/**
|
|
122
252
|
* Read a phoneme's raw PCM and return it as a Float64Array normalised to
|
|
123
253
|
* [-1, 1]. Convenience that forwards to the underlying {@link VoiceBank}.
|
|
@@ -168,14 +298,29 @@ interface WorldlineLoadOptions {
|
|
|
168
298
|
*/
|
|
169
299
|
scriptUrl: string;
|
|
170
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* A per-frame expression value: either a flat constant for the whole note, or
|
|
303
|
+
* a function evaluated once per 10ms WORLD frame for a custom curve (vibrato,
|
|
304
|
+
* portamento, scoop-in, humanize jitter, hand-drawn automation, …).
|
|
305
|
+
*
|
|
306
|
+
* `tMs` is elapsed time from the start of the rendered buffer (0 = includes
|
|
307
|
+
* the `preMs` lead-in), `totalMs` is the full rendered length (`preMs +
|
|
308
|
+
* durationMs`). Callers own all curve shaping — worldline itself has no
|
|
309
|
+
* opinion on what "vibrato" or "scoop" means, it just samples whatever
|
|
310
|
+
* function you give it once per frame.
|
|
311
|
+
*/
|
|
312
|
+
type CurveInput = number | ((tMs: number, totalMs: number) => number);
|
|
171
313
|
interface RenderNoteParams {
|
|
172
314
|
/**
|
|
173
315
|
* Source phoneme PCM normalised to [-1, 1] (e.g. from
|
|
174
316
|
* `VoiceBank.getPcm()` / `KoeEngine.getPcm()`).
|
|
175
317
|
*/
|
|
176
318
|
pcm: Float64Array;
|
|
177
|
-
/**
|
|
178
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Target output pitch in Hz. Pass a function for vibrato, portamento,
|
|
321
|
+
* scoop-in, pitch-drift humanize, etc. — it is sampled once per 10ms frame.
|
|
322
|
+
*/
|
|
323
|
+
pitch: CurveInput;
|
|
179
324
|
/** Sustain / vowel duration in ms (the lead-in below is rendered on top). */
|
|
180
325
|
durationMs: number;
|
|
181
326
|
/** Preutterance / lead-in in ms — convert from {@link PhonemeEntry.pre}. */
|
|
@@ -184,6 +329,24 @@ interface RenderNoteParams {
|
|
|
184
329
|
consonantMs: number;
|
|
185
330
|
/** Reference tempo in BPM for worldline's internal timing. Default 120. */
|
|
186
331
|
tempo?: number;
|
|
332
|
+
/**
|
|
333
|
+
* Formant/gender shift, 0-1. 0.5 (default) = unmodified. Below 0.5 skews
|
|
334
|
+
* toward a lower/thicker formant (older, huskier); above 0.5 toward a
|
|
335
|
+
* higher/thinner one (younger, brighter) — pitch itself is unaffected.
|
|
336
|
+
*/
|
|
337
|
+
gender?: CurveInput;
|
|
338
|
+
/**
|
|
339
|
+
* Tension, 0-1. 0.5 (default) = neutral. Higher = tighter/more strained
|
|
340
|
+
* ("こぶし"-style push); lower = more relaxed/breathy-adjacent.
|
|
341
|
+
*/
|
|
342
|
+
tension?: CurveInput;
|
|
343
|
+
/** Breathiness, 0-1. 0.5 (default) = neutral. Higher = airier/whispered. */
|
|
344
|
+
breathiness?: CurveInput;
|
|
345
|
+
/**
|
|
346
|
+
* Voicing ratio, 0-1. 1.0 (default) = fully voiced. Lower blends toward an
|
|
347
|
+
* unvoiced/falsetto-adjacent texture.
|
|
348
|
+
*/
|
|
349
|
+
voicing?: CurveInput;
|
|
187
350
|
}
|
|
188
351
|
/** Convert a sample count at 48 kHz to milliseconds. */
|
|
189
352
|
declare const samplesToMs: (samples: number) => number;
|
|
@@ -242,115 +405,6 @@ declare class Worldline {
|
|
|
242
405
|
renderNote(params: RenderNoteParams): Float32Array | null;
|
|
243
406
|
}
|
|
244
407
|
|
|
245
|
-
interface OtoEntry {
|
|
246
|
-
/** Source WAV filename */
|
|
247
|
-
wav: string;
|
|
248
|
-
/** Phoneme alias */
|
|
249
|
-
alias: string;
|
|
250
|
-
/** Left blank — offset from WAV start (ms) */
|
|
251
|
-
offset: number;
|
|
252
|
-
/** Consonant portion end from offset (ms) */
|
|
253
|
-
consonant: number;
|
|
254
|
-
/** Right blank — negative = from WAV end, positive = from offset (ms) */
|
|
255
|
-
cutoff: number;
|
|
256
|
-
/** Preutterance from offset (ms) */
|
|
257
|
-
pre: number;
|
|
258
|
-
/** Overlap / crossfade region (ms) */
|
|
259
|
-
overlap: number;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Parse oto.ini content (already decoded to UTF-8 string).
|
|
263
|
-
* Silently skips malformed lines.
|
|
264
|
-
*/
|
|
265
|
-
declare function parseOto(content: string): OtoEntry[];
|
|
266
|
-
|
|
267
|
-
interface WavData {
|
|
268
|
-
sampleRate: number;
|
|
269
|
-
channels: number;
|
|
270
|
-
/** Normalized samples in [-1, 1], interleaved if multi-channel */
|
|
271
|
-
samples: Float32Array;
|
|
272
|
-
}
|
|
273
|
-
/** Parse a WAV file from an ArrayBuffer. Supports PCM 8/16/24-bit and IEEE float 32-bit. */
|
|
274
|
-
declare function parseWav(buf: ArrayBuffer): WavData;
|
|
275
|
-
/** Mix down to mono by averaging all channels. */
|
|
276
|
-
declare function toMono(wav: WavData): WavData;
|
|
277
|
-
/** Linear interpolation resample to targetRate. Expects mono input. */
|
|
278
|
-
declare function resample(wav: WavData, targetRate: number): WavData;
|
|
279
|
-
/** Convert Float32 [-1,1] samples to Int16 PCM. */
|
|
280
|
-
declare function toInt16(samples: Float32Array): Int16Array;
|
|
281
|
-
/** Normalize then convert a WAV to 48kHz/16bit/mono Int16 PCM. */
|
|
282
|
-
declare function normalizePcm(buf: ArrayBuffer): Int16Array;
|
|
283
|
-
|
|
284
|
-
interface PackInput {
|
|
285
|
-
oto: OtoEntry;
|
|
286
|
-
/** Full normalized PCM of the source WAV (48kHz / 16bit / mono) */
|
|
287
|
-
pcm: Int16Array;
|
|
288
|
-
/** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */
|
|
289
|
-
recordedPitch?: number;
|
|
290
|
-
}
|
|
291
|
-
interface PackOutput {
|
|
292
|
-
manifest: Manifest;
|
|
293
|
-
/** Raw PCM blob — Int16 / 48kHz / mono */
|
|
294
|
-
bin: ArrayBuffer;
|
|
295
|
-
}
|
|
296
|
-
interface TrimmedPhoneme {
|
|
297
|
-
/** PCM trimmed to the oto region [offset, cutoff] */
|
|
298
|
-
pcm: Int16Array;
|
|
299
|
-
/** Manifest params relative to the trimmed start (sample 0 = oto offset) */
|
|
300
|
-
entry: Omit<PhonemeEntry, "offset">;
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Cut the full WAV PCM down to its usable oto region and recompute parameters
|
|
304
|
-
* relative to the trimmed start.
|
|
305
|
-
*
|
|
306
|
-
* UTAU oto.ini values are all in ms and measured from `offset` (the left blank),
|
|
307
|
-
* except `cutoff` (right blank):
|
|
308
|
-
* - cutoff >= 0 : measured from the END of the file
|
|
309
|
-
* - cutoff < 0 : region length from offset = |cutoff|
|
|
310
|
-
*
|
|
311
|
-
* After trimming, sample 0 == oto offset, so pre/overlap/consonant carry over
|
|
312
|
-
* unchanged (just converted to samples), and the slice length is the region end.
|
|
313
|
-
*/
|
|
314
|
-
declare function trimToOto(pcm: Int16Array, oto: OtoEntry, recordedPitch?: number): TrimmedPhoneme;
|
|
315
|
-
/**
|
|
316
|
-
* Pack normalized PCM phonemes into voice.bin + manifest.json.
|
|
317
|
-
* Each phoneme is trimmed to its oto region first.
|
|
318
|
-
* Duplicate aliases are silently overwritten by the later entry.
|
|
319
|
-
*/
|
|
320
|
-
declare function pack(inputs: PackInput[], referencePitch?: number): PackOutput;
|
|
321
|
-
|
|
322
|
-
/** Parse a note name like "E4", "G#4", "Db5" → frequency in Hz (null if invalid). */
|
|
323
|
-
declare function noteNameToHz(name: string): number | null;
|
|
324
|
-
/** Recorded pitch encoded in a multi-pitch alias suffix: "a い_E4" → 329.63 Hz. */
|
|
325
|
-
declare function pitchFromAliasSuffix(alias: string): number | null;
|
|
326
|
-
/**
|
|
327
|
-
* Estimate the fundamental frequency (Hz) of a voiced region by normalized
|
|
328
|
-
* autocorrelation. Returns 0 when no clear pitch is found (unvoiced consonant,
|
|
329
|
-
* silence, or a region too short to analyse).
|
|
330
|
-
*
|
|
331
|
-
* The signal is decimated to a lower analysis rate for speed; f0 below ~700 Hz
|
|
332
|
-
* is well within the resulting Nyquist limit. A parabolic interpolation around
|
|
333
|
-
* the best lag gives sub-sample (sub-semitone) accuracy.
|
|
334
|
-
*/
|
|
335
|
-
declare function detectF0(pcm: Int16Array, start: number, end: number): number;
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* UTAU `.frq` frequency-analysis files (FREQ0003). The same format OpenUtau
|
|
339
|
-
* reads: an 8-byte header, hop size, then the average fundamental frequency of
|
|
340
|
-
* the recording — exactly the reference pitch we need for correct resampling.
|
|
341
|
-
*
|
|
342
|
-
* Layout:
|
|
343
|
-
* char[8] "FREQ0003"
|
|
344
|
-
* int32 hopSize
|
|
345
|
-
* float64 averageF0 ← the recorded pitch in Hz
|
|
346
|
-
* byte[16] (blank)
|
|
347
|
-
* int32 length
|
|
348
|
-
* { float64 f0, float64 amp } × length
|
|
349
|
-
*/
|
|
350
|
-
declare function parseFrqAverageF0(buffer: ArrayBuffer): number | null;
|
|
351
|
-
/** Map a WAV filename to its sibling frq filename: "あ.wav" → "あ_wav.frq". */
|
|
352
|
-
declare function frqFileName(wavName: string): string;
|
|
353
|
-
|
|
354
408
|
/**
|
|
355
409
|
* Koe Archive Format (.koe)
|
|
356
410
|
* [4B] magic 'KOE\0' (big-endian)
|
|
@@ -366,4 +420,4 @@ declare function parseKoeHeader(headerBytes: ArrayBuffer): {
|
|
|
366
420
|
/** Byte offset where PCM data begins, given the JSON length. */
|
|
367
421
|
declare const pcmBase: (jsonLength: number) => number;
|
|
368
422
|
|
|
369
|
-
export { KoeEngine, type KoeEngineOptions, MIN_WORLDLINE_SAMPLES, type Manifest, type NoteEvent, type OtoEntry, type PackInput, type PackOutput, type PhonemeEntry, type RenderNoteParams, type TrimmedPhoneme, VoiceBank, WORLDLINE_SAMPLE_RATE, type WavData, Worldline, type WorldlineLoadOptions, detectF0, frqFileName, leadInFromEntry, normalizePcm, noteNameToHz, pack, packKoe, parseFrqAverageF0, parseKoeHeader, parseOto, parseWav, pcmBase, pitchFromAliasSuffix, resample, samplesToMs, toInt16, toMono, trimToOto };
|
|
423
|
+
export { KoeEngine, type KoeEngineOptions, MIN_WORLDLINE_SAMPLES, type Manifest, type NoteEvent, type OtoEntry, type PackInput, type PackOutput, type PhonemeEntry, type RenderNoteParams, type TrimmedPhoneme, VoiceBank, WORLDLINE_SAMPLE_RATE, type WavData, Worldline, type WorldlineLoadOptions, type ZipFile, detectF0, frqFileName, leadInFromEntry, normalizePcm, noteNameToHz, pack, packKoe, parseFrqAverageF0, parseKoeHeader, parseOto, parseWav, pcmBase, pitchFromAliasSuffix, resample, samplesToMs, toInt16, toMono, trimToOto, unzipToFileMap };
|