@onjmin/koe 1.0.2 → 1.0.4

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
@@ -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,98 @@ 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
+
39
148
  /**
40
149
  * Read-only access to a .koe voice bank: its manifest plus per-phoneme PCM,
41
150
  * fetched on demand (Blob slice or HTTP Range). The full bank is never held in
@@ -59,6 +168,7 @@ declare class VoiceBank {
59
168
  * @param koe a Blob/File of the .koe archive, or a URL (served with Range support)
60
169
  */
61
170
  static load(koe: Blob | string): Promise<VoiceBank>;
171
+ private static fromBlob;
62
172
  /** True if the bank contains a phoneme under this alias. */
63
173
  has(phoneme: string): boolean;
64
174
  /**
@@ -118,6 +228,11 @@ declare class KoeEngine {
118
228
  stop(): void;
119
229
  /** Resume the AudioContext if suspended (e.g. after autoplay block). */
120
230
  resume(): Promise<void>;
231
+ /**
232
+ * Tear down the worklet node and close the AudioContext, releasing the audio
233
+ * hardware. The engine cannot be reused afterwards — create a new one.
234
+ */
235
+ dispose(): Promise<void>;
121
236
  /**
122
237
  * Read a phoneme's raw PCM and return it as a Float64Array normalised to
123
238
  * [-1, 1]. Convenience that forwards to the underlying {@link VoiceBank}.
@@ -155,6 +270,7 @@ type WorldlineFactory = (opts?: {
155
270
  }) => Promise<WorldlineWasm>;
156
271
  declare global {
157
272
  var WorldlineModule: WorldlineFactory | undefined;
273
+ var importScripts: ((...urls: string[]) => void) | undefined;
158
274
  }
159
275
  interface WorldlineLoadOptions {
160
276
  /**
@@ -217,7 +333,13 @@ declare class Worldline {
217
333
  private wasm;
218
334
  readonly sampleRate = 48000;
219
335
  private constructor();
220
- /** Load + instantiate the worldline WASM module (deduped per scriptUrl). */
336
+ /**
337
+ * Load + instantiate the worldline WASM module (deduped per scriptUrl).
338
+ *
339
+ * Works on the main thread (loads via `<script>`) and inside a classic Web
340
+ * Worker (loads via `importScripts`), so the heavy synthesis can run
341
+ * off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.
342
+ */
221
343
  static load(options: WorldlineLoadOptions): Promise<Worldline>;
222
344
  /**
223
345
  * Render one note to Float32 PCM at 48 kHz.
@@ -235,115 +357,6 @@ declare class Worldline {
235
357
  renderNote(params: RenderNoteParams): Float32Array | null;
236
358
  }
237
359
 
238
- interface OtoEntry {
239
- /** Source WAV filename */
240
- wav: string;
241
- /** Phoneme alias */
242
- alias: string;
243
- /** Left blank — offset from WAV start (ms) */
244
- offset: number;
245
- /** Consonant portion end from offset (ms) */
246
- consonant: number;
247
- /** Right blank — negative = from WAV end, positive = from offset (ms) */
248
- cutoff: number;
249
- /** Preutterance from offset (ms) */
250
- pre: number;
251
- /** Overlap / crossfade region (ms) */
252
- overlap: number;
253
- }
254
- /**
255
- * Parse oto.ini content (already decoded to UTF-8 string).
256
- * Silently skips malformed lines.
257
- */
258
- declare function parseOto(content: string): OtoEntry[];
259
-
260
- interface WavData {
261
- sampleRate: number;
262
- channels: number;
263
- /** Normalized samples in [-1, 1], interleaved if multi-channel */
264
- samples: Float32Array;
265
- }
266
- /** Parse a WAV file from an ArrayBuffer. Supports PCM 8/16/24-bit and IEEE float 32-bit. */
267
- declare function parseWav(buf: ArrayBuffer): WavData;
268
- /** Mix down to mono by averaging all channels. */
269
- declare function toMono(wav: WavData): WavData;
270
- /** Linear interpolation resample to targetRate. Expects mono input. */
271
- declare function resample(wav: WavData, targetRate: number): WavData;
272
- /** Convert Float32 [-1,1] samples to Int16 PCM. */
273
- declare function toInt16(samples: Float32Array): Int16Array;
274
- /** Normalize then convert a WAV to 48kHz/16bit/mono Int16 PCM. */
275
- declare function normalizePcm(buf: ArrayBuffer): Int16Array;
276
-
277
- interface PackInput {
278
- oto: OtoEntry;
279
- /** Full normalized PCM of the source WAV (48kHz / 16bit / mono) */
280
- pcm: Int16Array;
281
- /** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */
282
- recordedPitch?: number;
283
- }
284
- interface PackOutput {
285
- manifest: Manifest;
286
- /** Raw PCM blob — Int16 / 48kHz / mono */
287
- bin: ArrayBuffer;
288
- }
289
- interface TrimmedPhoneme {
290
- /** PCM trimmed to the oto region [offset, cutoff] */
291
- pcm: Int16Array;
292
- /** Manifest params relative to the trimmed start (sample 0 = oto offset) */
293
- entry: Omit<PhonemeEntry, "offset">;
294
- }
295
- /**
296
- * Cut the full WAV PCM down to its usable oto region and recompute parameters
297
- * relative to the trimmed start.
298
- *
299
- * UTAU oto.ini values are all in ms and measured from `offset` (the left blank),
300
- * except `cutoff` (right blank):
301
- * - cutoff >= 0 : measured from the END of the file
302
- * - cutoff < 0 : region length from offset = |cutoff|
303
- *
304
- * After trimming, sample 0 == oto offset, so pre/overlap/consonant carry over
305
- * unchanged (just converted to samples), and the slice length is the region end.
306
- */
307
- declare function trimToOto(pcm: Int16Array, oto: OtoEntry, recordedPitch?: number): TrimmedPhoneme;
308
- /**
309
- * Pack normalized PCM phonemes into voice.bin + manifest.json.
310
- * Each phoneme is trimmed to its oto region first.
311
- * Duplicate aliases are silently overwritten by the later entry.
312
- */
313
- declare function pack(inputs: PackInput[], referencePitch?: number): PackOutput;
314
-
315
- /** Parse a note name like "E4", "G#4", "Db5" → frequency in Hz (null if invalid). */
316
- declare function noteNameToHz(name: string): number | null;
317
- /** Recorded pitch encoded in a multi-pitch alias suffix: "a い_E4" → 329.63 Hz. */
318
- declare function pitchFromAliasSuffix(alias: string): number | null;
319
- /**
320
- * Estimate the fundamental frequency (Hz) of a voiced region by normalized
321
- * autocorrelation. Returns 0 when no clear pitch is found (unvoiced consonant,
322
- * silence, or a region too short to analyse).
323
- *
324
- * The signal is decimated to a lower analysis rate for speed; f0 below ~700 Hz
325
- * is well within the resulting Nyquist limit. A parabolic interpolation around
326
- * the best lag gives sub-sample (sub-semitone) accuracy.
327
- */
328
- declare function detectF0(pcm: Int16Array, start: number, end: number): number;
329
-
330
- /**
331
- * UTAU `.frq` frequency-analysis files (FREQ0003). The same format OpenUtau
332
- * reads: an 8-byte header, hop size, then the average fundamental frequency of
333
- * the recording — exactly the reference pitch we need for correct resampling.
334
- *
335
- * Layout:
336
- * char[8] "FREQ0003"
337
- * int32 hopSize
338
- * float64 averageF0 ← the recorded pitch in Hz
339
- * byte[16] (blank)
340
- * int32 length
341
- * { float64 f0, float64 amp } × length
342
- */
343
- declare function parseFrqAverageF0(buffer: ArrayBuffer): number | null;
344
- /** Map a WAV filename to its sibling frq filename: "あ.wav" → "あ_wav.frq". */
345
- declare function frqFileName(wavName: string): string;
346
-
347
360
  /**
348
361
  * Koe Archive Format (.koe)
349
362
  * [4B] magic 'KOE\0' (big-endian)