@onjmin/koe 1.0.6 → 1.0.8
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 +122 -5
- package/dist/index.d.ts +364 -1
- package/dist/index.js +1903 -4
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +0 -1
- package/dist/koe-oto.js +1399 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
ブラウザ上でUTAU音源をラグ無しで再生するnpmモジュール。
|
|
4
4
|
|
|
5
5
|
UTAU の oto.ini で定義された音源を `.koe` アーカイブに変換し、WebAssembly + AudioWorklet でリアルタイム再生・高品質再合成を行う。
|
|
6
|
+
oto.ini が無い収録済み wav フォルダからは、原音設定そのものを自動生成できる。
|
|
6
7
|
|
|
7
8
|
- [DEMO](https://onjmin.github.io/koe/demo) koeフォーマット作成もこちらで
|
|
8
9
|
- [npm](https://www.npmjs.com/package/@onjmin/koe)
|
|
@@ -42,7 +43,114 @@ UTAU音源 (wav + oto.ini + frq)
|
|
|
42
43
|
|
|
43
44
|
## 使い方
|
|
44
45
|
|
|
45
|
-
### 1.
|
|
46
|
+
### 1. 原音設定の自動生成 (wav → oto.ini)
|
|
47
|
+
|
|
48
|
+
収録済みの wav フォルダから oto.ini を生成する。CLI と [DEMO ページ](https://onjmin.github.io/koe/demo) の両方から使える。
|
|
49
|
+
|
|
50
|
+
#### GUI から
|
|
51
|
+
|
|
52
|
+
DEMO ページの「原音設定 — oto.ini を作る」カードで、wav フォルダ(または zip)を選んでボタンを押すだけ。wav が入っているフォルダごとに oto.ini ができ、そのまま保存(複数フォルダなら zip)できる。「変換へ」を押すと、作った oto.ini をそのまま使って `.koe` を生成し、その場で歌わせて確認できる。
|
|
53
|
+
|
|
54
|
+
エイリアス接尾辞と、`- か` / `* あ` エイリアスを作るかどうかはカード内で切り替えられる。処理はすべてブラウザ内で完結し、wav はどこにも送信されない。
|
|
55
|
+
|
|
56
|
+
#### CLI から
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx koe-oto <音源フォルダ>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
フォルダを渡すだけで、その下の「wav が置かれている全フォルダ」それぞれに oto.ini が書き出される。
|
|
63
|
+
|
|
64
|
+
| オプション | 説明 |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `-n`, `--dry-run` | 書き込まず、生成結果の件数だけ表示する |
|
|
67
|
+
| `-f`, `--force` | 既存の oto.ini を上書きする (`oto.ini.bak` を残す) |
|
|
68
|
+
| `--suffix <s>` | 全エイリアスの末尾に `<s>` を付ける (既定: フォルダ名が音階名なら `_G4` 等) |
|
|
69
|
+
| `-q`, `--quiet` | 集計行だけ出力する |
|
|
70
|
+
|
|
71
|
+
既存の oto.ini があるフォルダは既定でスキップする。上書きしたい場合のみ `--force` を付ける。
|
|
72
|
+
|
|
73
|
+
#### 収録方式の判定
|
|
74
|
+
|
|
75
|
+
ファイル名がそのまま音素の書き起こしになっているので、方式はファイル名から決まる。
|
|
76
|
+
|
|
77
|
+
| ファイル名 | 判定 | 生成されるエイリアス |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `か.wav` / `_きゃ.wav` | 単独音 | `か`, `- か` |
|
|
80
|
+
| `_あ.wav` | 単独音 (母音) | `あ`, `- あ`, `* あ` |
|
|
81
|
+
| `_ああいあうえあ.wav` | 連続音 | `- あ`, `a あ`, `a い`, `i あ`, … |
|
|
82
|
+
| `_ああR.wav` | 連続音 + 語尾 | `- あ`, `a あ`, `a R` |
|
|
83
|
+
| `_あb.wav` / `_ううわ↑.wav` | テイク違い | `あb`, `- あb` / `- う↑`, `u う↑` … |
|
|
84
|
+
| `_xか.wav` | 語頭記号付き | `xか`, `- xか` |
|
|
85
|
+
| `カラオケ.wav` | 仮名として読めない → スキップ | — |
|
|
86
|
+
|
|
87
|
+
カタカナ・拗音・外来音 (`ヴぁ`, `てぃ`, `つぉ` 等) も解決する。仮名の前後に付いた `x` / `b` / `2` / `↑↓` のようなテイク記号はエイリアスに引き継ぐ。仮名として読めないファイルは音声ではないものとして飛ばすので、伴奏やサンプル曲が混ざったフォルダでもそのまま渡せる。
|
|
88
|
+
|
|
89
|
+
フォルダ名が音階そのもの (`G4`) か、音階タグを含む (`多音階03:_G4(連続音)`) 場合は、多音階音源としてエイリアス末尾に `_G4` を付ける。音源全体で oto.ini のエイリアスは1つの名前空間に混ざるため、これが無いと音階ごとの `- あ` が互いを上書きしてしまう。
|
|
90
|
+
|
|
91
|
+
#### 推定のしかた
|
|
92
|
+
|
|
93
|
+
[UTAU音源制作wiki の原音設定記事](https://w.atwiki.jp/vbmaker/pages/17.html) のセオリーをそのまま実装している。
|
|
94
|
+
|
|
95
|
+
- **オフセット** — 子音の立ち上がりの少し手前。さ行・は行のような摩擦音は 4kHz 以上の帯域で先に立ち上がるので、その帯域を見て検出する。
|
|
96
|
+
- **先行発声** — 母音の開始点。無声子音 (か・さ・た・は・ぱ行) は声帯が鳴り出した瞬間、な・ま・ら行は鼻音/はじき音が開放された瞬間、や・わ行は渡りの中間。
|
|
97
|
+
- **子音部 (固定範囲)** — 母音に入ってスペクトルが落ち着くまで。伸縮されるのが定常部の母音だけになる。
|
|
98
|
+
- **オーバーラップ** — 先行発声に対する比で決める。共鳴音 (な・ま・ら・や・わ行) はおよそ 0.6 倍、摩擦音・破擦音は 0.3 倍、下限 12ms・上限 40ms。か・た・ぱ行は破裂前の無音を再現するため負値 (−10ms)。母音単体は 20ms 固定。
|
|
99
|
+
- **右ブランク** — 減衰が始まる手前。負値 (オフセットからの相対値) で書き出す。
|
|
100
|
+
|
|
101
|
+
連続音はガイドBGMに合わせて収録されるため、モーラが等間隔に並ぶ。オンセット検出の自己相関からテンポを求め、グリッドを当ててから各モーラを最寄りのオンセットに吸着させる。テンポが求まれば先行発声 = 間隔の 1/2、オーバーラップ = その 1/3、固定範囲 = その 1.5 倍、右ブランク = ノートの 2/3 間隔先、という既存音源が共通して使っているテンプレートを当てる。
|
|
102
|
+
|
|
103
|
+
#### 精度
|
|
104
|
+
|
|
105
|
+
手作業の音源との一致度 (絶対時刻でのずれ)。`重音テト単独音` は人力精度が高い音源として比較対象にした。
|
|
106
|
+
|
|
107
|
+
| 音源 | 方式 | 先行発声 ≤20ms | ≤40ms | 中央絶対誤差 |
|
|
108
|
+
| --- | --- | --- | --- | --- |
|
|
109
|
+
| 重音テト単独音 | 単独音 | 72% | 85% | 11ms |
|
|
110
|
+
| 欲音ルコ♀ A3 | 連続音 | 53% | 81% | 18ms |
|
|
111
|
+
| つくよみちゃん _G4 | 連続音 | 42% | 71% | 25ms |
|
|
112
|
+
| 束音ロゼ G4 | 連続音 | 43% | 61% | 28ms |
|
|
113
|
+
| 欲音ルコ♂ | 連続音 | 30% | 55% | 37ms |
|
|
114
|
+
|
|
115
|
+
テトでは他のパラメータも、オフセット ≤20ms 89% / 中央絶対誤差 8ms、オーバーラップ ≤20ms 79% / 10.6ms、右ブランク ≤20ms 85% / 8ms。一番緩いのは子音部 (固定範囲) で中央絶対誤差 34ms だが、これは伸縮の開始位置を決めるだけなのでリズムには効かない。
|
|
116
|
+
|
|
117
|
+
連続音は音源ごとのテンプレート方針の差がそのまま誤差に出るため、単独音より一致率が落ちる (欲音ルコ♂ は人力でモーラごとに詰めてある音源)。
|
|
118
|
+
|
|
119
|
+
setParam の自動推定と同程度で、そのまま歌わせられる水準ではあるが、商用配布するなら人の手で詰める前提の出力。
|
|
120
|
+
|
|
121
|
+
なお `息.wav` `咳払い.wav` のような非言語音は、歌詞が仮名で書かれていない以上どんなエイリアスを振るべきか決めようがないのでスキップする (スキップしたファイルは `GenerateResult.skipped` と CLI の集計に出る)。必要ならその数行だけ手で足すことになる。
|
|
122
|
+
|
|
123
|
+
#### コードから使う
|
|
124
|
+
|
|
125
|
+
ブラウザでもそのまま動く (`fs` に依存しない)。
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { generateOto, formatOto, encodeOto } from "@onjmin/koe";
|
|
129
|
+
|
|
130
|
+
const files = [{ name: "_あ.wav", data: arrayBuffer }, /* ... */];
|
|
131
|
+
const { entries, skipped, style } = generateOto(files, { suffix: "_G4" });
|
|
132
|
+
|
|
133
|
+
console.log(style); // "solo" | "sequence" | "mixed"
|
|
134
|
+
console.log(formatOto(entries)); // oto.ini のテキスト
|
|
135
|
+
const bytes = encodeOto(entries); // Shift-JIS の Uint8Array
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`generateOto` は同期処理なので、フォルダが大きいとブラウザのUIが固まる。進捗を出したい場合は1ファイルずつ回す:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { generateOtoForFile, summarise } from "@onjmin/koe";
|
|
142
|
+
|
|
143
|
+
for (const file of files) {
|
|
144
|
+
const { entries, skipped, style } = generateOtoForFile(file, { suffix: "_G4" });
|
|
145
|
+
// …集計して、ここでイベントループに制御を返す
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`generateOto` が返す `entries` は `parseOto` と同じ `OtoEntry[]` なので、そのまま `pack()` に渡して `.koe` 化できる。
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### 2. 音源の変換 (oto.ini → .koe)
|
|
46
154
|
|
|
47
155
|
CLIコマンド `koe-convert` で UTAU 音源を `.koe` アーカイブに変換する。
|
|
48
156
|
|
|
@@ -90,7 +198,7 @@ await fs.writeFile("voice.koe", buf);
|
|
|
90
198
|
|
|
91
199
|
---
|
|
92
200
|
|
|
93
|
-
###
|
|
201
|
+
### 3. KoeEngine — リアルタイム再生 (ブラウザ)
|
|
94
202
|
|
|
95
203
|
AudioWorklet を使った連接合成エンジン。`koe-worklet.js` を同じオリジンから配信する必要がある。
|
|
96
204
|
|
|
@@ -132,7 +240,7 @@ interface NoteEvent {
|
|
|
132
240
|
|
|
133
241
|
---
|
|
134
242
|
|
|
135
|
-
###
|
|
243
|
+
### 4. VoiceBank — 音素 PCM の直接取得
|
|
136
244
|
|
|
137
245
|
AudioContext 不要。WORLD ボコーダや独自の合成処理に PCM を渡したい場合に使う。
|
|
138
246
|
|
|
@@ -161,7 +269,7 @@ bank.has("a"); // boolean
|
|
|
161
269
|
|
|
162
270
|
---
|
|
163
271
|
|
|
164
|
-
###
|
|
272
|
+
### 5. Worldline — 高品質ノート合成 (WORLD ボコーダ)
|
|
165
273
|
|
|
166
274
|
OpenUtau の worldline WASM で F0 分析・再合成を行う。`worldline.js` と `worldline.wasm` を配信する必要がある。
|
|
167
275
|
|
|
@@ -201,7 +309,7 @@ if (audio) {
|
|
|
201
309
|
|
|
202
310
|
---
|
|
203
311
|
|
|
204
|
-
###
|
|
312
|
+
### 6. .koe アーカイブ形式
|
|
205
313
|
|
|
206
314
|
```
|
|
207
315
|
[4B] magic 'KOE\0' (big-endian)
|
|
@@ -233,6 +341,14 @@ const pcmOffset = pcmBase(jsonLength); // PCM データの開始バイト位置
|
|
|
233
341
|
| `KoeEngine` | AudioWorklet ベースの連接合成エンジン |
|
|
234
342
|
| `VoiceBank` | .koe から音素 PCM をオンデマンド取得 |
|
|
235
343
|
| `Worldline` | WORLD ボコーダによる高品質ノート合成 |
|
|
344
|
+
| `generateOto` | wav 群 → oto.ini エントリを推定 (原音設定) |
|
|
345
|
+
| `generateOtoForFile` | wav 1本ぶんの推定 (進捗表示したいとき用) |
|
|
346
|
+
| `formatOto` / `encodeOto` | エントリ → oto.ini テキスト / Shift-JIS バイト列 |
|
|
347
|
+
| `analyze` / `analyzeWav` | WAV → フレーム特徴量 (RMS・有声度・スペクトル) |
|
|
348
|
+
| `estimateSolo` / `estimateSequence` | 単独音 / 連続音 1ファイル分のパラメータ推定 |
|
|
349
|
+
| `detectGrid` | 連続音のモーラ位置とテンポを検出 |
|
|
350
|
+
| `splitKana` | 仮名文字列 → モーラ (子音・母音・調音種別) |
|
|
351
|
+
| `transcribe` | wav ファイル名 → モーラ列 |
|
|
236
352
|
| `parseOto` | oto.ini テキストをパース |
|
|
237
353
|
| `parseWav` | WAV バイナリをパース |
|
|
238
354
|
| `toMono` | ステレオ → モノラル変換 |
|
|
@@ -242,6 +358,7 @@ const pcmOffset = pcmBase(jsonLength); // PCM データの開始バイト位置
|
|
|
242
358
|
| `trimToOto` | WAV を oto リージョンにトリミング |
|
|
243
359
|
| `packKoe` | manifest + PCM → .koe Blob |
|
|
244
360
|
| `parseKoeHeader` | .koe ヘッダ解析 |
|
|
361
|
+
| `unzipToFileMap` / `zipFiles` | zip の展開 / 作成 |
|
|
245
362
|
| `pcmBase` | JSON長 → PCM 開始バイト位置 |
|
|
246
363
|
| `detectF0` | PCM からピッチ自動検出 |
|
|
247
364
|
| `noteNameToHz` | 音名 → Hz 変換 (例: `"A4"` → `440`) |
|
package/dist/index.d.ts
CHANGED
|
@@ -217,6 +217,14 @@ interface ZipFile {
|
|
|
217
217
|
* original bytes and re-decode with the right encoding per entry.
|
|
218
218
|
*/
|
|
219
219
|
declare function unzipToFileMap(data: ArrayBuffer): Promise<Record<string, ZipFile>>;
|
|
220
|
+
/**
|
|
221
|
+
* Pack a path → bytes map into a zip Blob.
|
|
222
|
+
*
|
|
223
|
+
* Entry names are written as UTF-8 with the language flag set, which is what
|
|
224
|
+
* every current unzip tool reads — including Windows Explorer, so a bank whose
|
|
225
|
+
* folders are named in Japanese comes back out intact.
|
|
226
|
+
*/
|
|
227
|
+
declare function zipFiles(files: Record<string, Uint8Array>): Blob;
|
|
220
228
|
|
|
221
229
|
/**
|
|
222
230
|
* Read-only access to a .koe voice bank: its manifest plus per-phoneme PCM,
|
|
@@ -393,6 +401,25 @@ interface WorldlineLoadOptions {
|
|
|
393
401
|
* function you give it once per frame.
|
|
394
402
|
*/
|
|
395
403
|
type CurveInput = number | ((tMs: number, totalMs: number) => number);
|
|
404
|
+
interface PhraseUnit {
|
|
405
|
+
pcm: Float64Array;
|
|
406
|
+
posMs: number;
|
|
407
|
+
skipMs: number;
|
|
408
|
+
lengthMs: number;
|
|
409
|
+
fadeInMs: number;
|
|
410
|
+
fadeOutMs: number;
|
|
411
|
+
consonantMs: number;
|
|
412
|
+
cutMs?: number;
|
|
413
|
+
}
|
|
414
|
+
interface RenderPhraseParams {
|
|
415
|
+
units: PhraseUnit[];
|
|
416
|
+
pitch: CurveInput;
|
|
417
|
+
gender?: CurveInput;
|
|
418
|
+
tension?: CurveInput;
|
|
419
|
+
breathiness?: CurveInput;
|
|
420
|
+
voicing?: CurveInput;
|
|
421
|
+
tempo?: number;
|
|
422
|
+
}
|
|
396
423
|
interface RenderNoteParams {
|
|
397
424
|
/**
|
|
398
425
|
* Source phoneme PCM normalised to [-1, 1] (e.g. from
|
|
@@ -485,6 +512,7 @@ declare class Worldline {
|
|
|
485
512
|
* @returns Float32 PCM, or null when `pcm` is shorter than
|
|
486
513
|
* {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
|
|
487
514
|
*/
|
|
515
|
+
renderPhrase(params: RenderPhraseParams): Float32Array | null;
|
|
488
516
|
renderNote(params: RenderNoteParams): Float32Array | null;
|
|
489
517
|
}
|
|
490
518
|
|
|
@@ -503,4 +531,339 @@ declare function parseKoeHeader(headerBytes: ArrayBuffer): {
|
|
|
503
531
|
/** Byte offset where PCM data begins, given the JSON length. */
|
|
504
532
|
declare const pcmBase: (jsonLength: number) => number;
|
|
505
533
|
|
|
506
|
-
|
|
534
|
+
/**
|
|
535
|
+
* Frame-level acoustic features used to locate mora boundaries.
|
|
536
|
+
*
|
|
537
|
+
* Everything downstream reasons in *frames* at a fixed 2 ms hop, so a frame
|
|
538
|
+
* index doubles as a millisecond timestamp once multiplied by {@link HOP_MS}.
|
|
539
|
+
* The analysis runs at 16 kHz regardless of the source rate: that is well past
|
|
540
|
+
* the 4–8 kHz band where fricative noise lives, and keeps a 512-point FFT to
|
|
541
|
+
* 32 ms — short enough to see a plosive burst, long enough to resolve F1.
|
|
542
|
+
*/
|
|
543
|
+
|
|
544
|
+
interface Frames {
|
|
545
|
+
/** Number of frames. */
|
|
546
|
+
n: number;
|
|
547
|
+
/** Source duration in milliseconds. */
|
|
548
|
+
durationMs: number;
|
|
549
|
+
/** Short-time level in dBFS, −120 for digital silence. */
|
|
550
|
+
rmsDb: Float32Array;
|
|
551
|
+
/** {@link rmsDb} smoothed over ~30 ms, for threshold crossings. */
|
|
552
|
+
smoothDb: Float32Array;
|
|
553
|
+
/** Normalised autocorrelation peak, 0–1. Above ~0.5 reads as voiced. */
|
|
554
|
+
voiced: Float32Array;
|
|
555
|
+
/** Share of spectral energy above 4 kHz — high for /s/, /sh/, /ch/. */
|
|
556
|
+
highRatio: Float32Array;
|
|
557
|
+
/** Level of the >4 kHz band alone, in dB. Frication shows here first. */
|
|
558
|
+
highDb: Float32Array;
|
|
559
|
+
/** Noise floor of {@link highDb}, in dB. */
|
|
560
|
+
highFloorDb: number;
|
|
561
|
+
/** Positive spectral flux, normalised so its own median is 1. */
|
|
562
|
+
flux: Float32Array;
|
|
563
|
+
/** Median f0 of the voiced portion, in Hz (0 when nothing is voiced). */
|
|
564
|
+
f0: number;
|
|
565
|
+
/** Noise floor in dBFS, estimated from the quietest tenth of the file. */
|
|
566
|
+
floorDb: number;
|
|
567
|
+
/**
|
|
568
|
+
* Level of the quietest 50 ms in the file — a true noise floor, unlike
|
|
569
|
+
* {@link floorDb}, which a short recording's own voice can drag upwards.
|
|
570
|
+
* Used to trace an attack back past the point where it is merely audible.
|
|
571
|
+
*/
|
|
572
|
+
quietDb: number;
|
|
573
|
+
/** Loudest smoothed frame, in dBFS. */
|
|
574
|
+
peakDb: number;
|
|
575
|
+
}
|
|
576
|
+
/** Extract every feature the mora estimator needs from one mono signal. */
|
|
577
|
+
declare function analyze(wav: WavData): Frames;
|
|
578
|
+
/** Convenience wrapper: decode a WAV buffer and analyse it. */
|
|
579
|
+
declare function analyzeWav(buf: ArrayBuffer): Frames;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Kana → phoneme tables for oto.ini generation.
|
|
583
|
+
*
|
|
584
|
+
* A UTAU recording's filename *is* its phonetic transcript: `か.wav` holds one
|
|
585
|
+
* mora, `_ああいあうえあ.wav` holds seven. Everything the estimator does — where
|
|
586
|
+
* to look for the vowel, how wide to make the crossfade, whether the overlap
|
|
587
|
+
* goes negative — follows from which consonant a mora starts with, so the kana
|
|
588
|
+
* has to be resolved into (consonant, vowel) before any audio is touched.
|
|
589
|
+
*/
|
|
590
|
+
/**
|
|
591
|
+
* Articulation class of a mora's initial consonant. The estimator branches on
|
|
592
|
+
* this to pick where the preutterance lands and what overlap the mora gets;
|
|
593
|
+
* see `ARTICULATION` in `estimate.ts`.
|
|
594
|
+
*/
|
|
595
|
+
type ConsonantClass =
|
|
596
|
+
/** あ/い/う/え/お — no consonant at all. */
|
|
597
|
+
"vowel"
|
|
598
|
+
/** ん — a syllabic nasal that is its own nucleus. */
|
|
599
|
+
| "nasalN"
|
|
600
|
+
/** な/ま行 — voiced throughout, vowel starts at the nasal release. */
|
|
601
|
+
| "nasal"
|
|
602
|
+
/** ら行 — a flap: brief closure, then the vowel. */
|
|
603
|
+
| "liquid"
|
|
604
|
+
/** や/わ行 and vowel glides (いぇ, うぉ) — barely a consonant at all. */
|
|
605
|
+
| "semivowel"
|
|
606
|
+
/** さ/は行 — voiceless noise, so voicing onset *is* the vowel onset. */
|
|
607
|
+
| "fricativeVoiceless"
|
|
608
|
+
/** ざ行, ヴ — voiced noise. */
|
|
609
|
+
| "fricativeVoiced"
|
|
610
|
+
/** つ/ち — a stop released into friction; behaves like a plosive. */
|
|
611
|
+
| "affricate"
|
|
612
|
+
/** か/た/ぱ行 — a silent closure precedes the burst. */
|
|
613
|
+
| "plosiveVoiceless"
|
|
614
|
+
/** が/だ/ば行 — may prevoice through the closure. */
|
|
615
|
+
| "plosiveVoiced";
|
|
616
|
+
interface Syllable {
|
|
617
|
+
/** The kana as written, e.g. "きゃ". Used verbatim in the alias. */
|
|
618
|
+
kana: string;
|
|
619
|
+
/** Romanised onset, e.g. "ky". Empty for bare vowels and ん. */
|
|
620
|
+
consonant: string;
|
|
621
|
+
/** Romanised nucleus: a/i/u/e/o, or "n" for ん. */
|
|
622
|
+
vowel: string;
|
|
623
|
+
cls: ConsonantClass;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Katakana → hiragana, so ヴァ and ゔぁ resolve identically. Only the kana
|
|
627
|
+
* block is folded; ー and everything else is left alone for the caller to
|
|
628
|
+
* reject.
|
|
629
|
+
*/
|
|
630
|
+
declare function toHiragana(s: string): string;
|
|
631
|
+
/**
|
|
632
|
+
* Split a kana string into moras.
|
|
633
|
+
*
|
|
634
|
+
* Returns null if any character is not kana we can resolve — that is the
|
|
635
|
+
* signal to skip the file entirely, which is what keeps a bank's karaoke
|
|
636
|
+
* tracks and readme audio out of the generated oto.ini.
|
|
637
|
+
*/
|
|
638
|
+
declare function splitKana(text: string): Syllable[] | null;
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Parameter estimation for oto.ini entries.
|
|
642
|
+
*
|
|
643
|
+
* The rules encoded here follow the UTAU音源制作wiki's 原音設定 articles
|
|
644
|
+
* (https://w.atwiki.jp/vbmaker/pages/17.html and its 単独音 / 連続音 sequels):
|
|
645
|
+
*
|
|
646
|
+
* - オフセット sits just before the consonant, keeping a little room so the
|
|
647
|
+
* attack is never clipped.
|
|
648
|
+
* - 先行発声 marks where the *vowel* begins — the voicing onset for a voiceless
|
|
649
|
+
* consonant, the release for a nasal or a flap, the midpoint of the glide for
|
|
650
|
+
* や/わ行.
|
|
651
|
+
* - 子音部 runs from the offset through the vowel's onset until the spectrum
|
|
652
|
+
* settles, so a long note stretches only steady-state vowel.
|
|
653
|
+
* - オーバーラップ is ~20 ms for さ/な/ま/ら行, ~30 ms for や/わ行, and goes
|
|
654
|
+
* *negative* for 破裂音 to reproduce the silent closure of か/た/ぱ行.
|
|
655
|
+
* - 右ブランク lands just before the note starts to decay.
|
|
656
|
+
*/
|
|
657
|
+
|
|
658
|
+
/** Frame positions the oto parameters are built from. */
|
|
659
|
+
interface MoraPosition {
|
|
660
|
+
/** Frame where the consonant (or vowel, if there is none) begins. */
|
|
661
|
+
consStart: number;
|
|
662
|
+
/** Frame where the vowel begins — the 先行発声 anchor. */
|
|
663
|
+
vowelOnset: number;
|
|
664
|
+
/** Frame past which the vowel is steady. */
|
|
665
|
+
stable: number;
|
|
666
|
+
/** True when a voiced stop prevoiced into its burst. */
|
|
667
|
+
prevoiced: boolean;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Locate one mora inside `[from, to)`, given what consonant it starts with.
|
|
671
|
+
*/
|
|
672
|
+
declare function locateMora(f: Frames, cls: ConsonantClass, from: number, to: number): MoraPosition;
|
|
673
|
+
/**
|
|
674
|
+
* 単独音: one mora per file, one entry per alias.
|
|
675
|
+
*
|
|
676
|
+
* `aliases` lets a caller emit the usual family for a file — the bare kana plus
|
|
677
|
+
* a `- か` head variant — all sharing the same measurements.
|
|
678
|
+
*/
|
|
679
|
+
declare function estimateSolo(wav: string, f: Frames, syl: Syllable, aliases: string[]): OtoEntry[];
|
|
680
|
+
/**
|
|
681
|
+
* A 母音結合 entry (`* あ`): a mid-phrase vowel taken from the steady part of
|
|
682
|
+
* the note, with the long symmetric crossfade those aliases are used with.
|
|
683
|
+
*/
|
|
684
|
+
declare function estimateVowelJoin(wav: string, f: Frames, syl: Syllable, alias: string): OtoEntry | null;
|
|
685
|
+
/** A 連続音 recording's rhythmic grid. */
|
|
686
|
+
interface Grid {
|
|
687
|
+
/** Frame of the first mora's onset. */
|
|
688
|
+
start: number;
|
|
689
|
+
/** Frames between successive moras. */
|
|
690
|
+
interval: number;
|
|
691
|
+
/** Per-mora onset frames, snapped to the strongest nearby transition. */
|
|
692
|
+
onsets: number[];
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Fit `count` evenly spaced moras to the recording.
|
|
696
|
+
*
|
|
697
|
+
* 連続音 lists are sung to a guide BGM, so the moras land on a metronomic grid —
|
|
698
|
+
* which is exactly why the wiki treats 連続音 oto as something you *generate*
|
|
699
|
+
* and then touch up. Fitting a global tempo first, and only then snapping each
|
|
700
|
+
* mora to the nearest real transition, keeps one mis-detected onset from
|
|
701
|
+
* dragging the rest of the file out of alignment.
|
|
702
|
+
*/
|
|
703
|
+
declare function detectGrid(f: Frames, count: number): Grid | null;
|
|
704
|
+
/**
|
|
705
|
+
* 連続音: every mora in the file gets an entry, aliased against the vowel it
|
|
706
|
+
* follows (`a か`), with the first written as a phrase head (`- あ`).
|
|
707
|
+
*
|
|
708
|
+
* The template — 先行発声 at half the mora interval, オーバーラップ at a third of
|
|
709
|
+
* that, 固定範囲 half again as long, 右ブランク two thirds of an interval past the
|
|
710
|
+
* note — is the one the established 連続音 banks ship, and it survives a mora
|
|
711
|
+
* whose consonant is longer than average because half an interval is far more
|
|
712
|
+
* room than any Japanese onset needs.
|
|
713
|
+
*/
|
|
714
|
+
declare function estimateSequence(wav: string, f: Frames, syllables: Syllable[], opts?: {
|
|
715
|
+
suffix?: string;
|
|
716
|
+
prefix?: string;
|
|
717
|
+
trailingRest?: boolean;
|
|
718
|
+
}): OtoEntry[];
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Folder → oto.ini, with no manual step in between.
|
|
722
|
+
*
|
|
723
|
+
* The filename carries the transcript, so the recording style falls out of it:
|
|
724
|
+
* one mora per file is 単独音, several is 連続音. Everything else — which
|
|
725
|
+
* aliases to emit, where the phrase head goes, whether a trailing R belongs on
|
|
726
|
+
* the end — follows from the same parse.
|
|
727
|
+
*/
|
|
728
|
+
|
|
729
|
+
interface GenerateOptions {
|
|
730
|
+
/**
|
|
731
|
+
* Appended to every alias — the usual home for a multi-pitch or expression
|
|
732
|
+
* marker (`_G4`, `強`). Defaults to the folder's own note name when it has
|
|
733
|
+
* one; pass `""` to suppress that.
|
|
734
|
+
*/
|
|
735
|
+
suffix?: string;
|
|
736
|
+
/** Emit `- か` phrase-head aliases alongside the bare kana. Default true. */
|
|
737
|
+
headAliases?: boolean;
|
|
738
|
+
/** Emit `* あ` 母音結合 aliases for vowel-only files. Default true. */
|
|
739
|
+
vowelJoinAliases?: boolean;
|
|
740
|
+
}
|
|
741
|
+
/** One file that could not be transcribed, and why. */
|
|
742
|
+
interface SkippedFile {
|
|
743
|
+
wav: string;
|
|
744
|
+
reason: string;
|
|
745
|
+
}
|
|
746
|
+
interface GenerateResult {
|
|
747
|
+
entries: OtoEntry[];
|
|
748
|
+
skipped: SkippedFile[];
|
|
749
|
+
/** Recording style inferred from the filenames. */
|
|
750
|
+
style: "solo" | "sequence" | "mixed" | "empty";
|
|
751
|
+
}
|
|
752
|
+
/** What one recording produced. */
|
|
753
|
+
interface FileResult {
|
|
754
|
+
entries: OtoEntry[];
|
|
755
|
+
/** Set instead of entries when the file could not be set up. */
|
|
756
|
+
skipped: SkippedFile | null;
|
|
757
|
+
/** Style this one file was read as, or null if it was skipped. */
|
|
758
|
+
style: "solo" | "sequence" | null;
|
|
759
|
+
}
|
|
760
|
+
interface WavInput {
|
|
761
|
+
/** Filename as it appears in oto.ini, e.g. `_ああいあう.wav`. */
|
|
762
|
+
name: string;
|
|
763
|
+
data: ArrayBuffer;
|
|
764
|
+
}
|
|
765
|
+
interface Transcript {
|
|
766
|
+
syllables: Syllable[];
|
|
767
|
+
trailingRest: boolean;
|
|
768
|
+
/** Non-kana marker before the kana, e.g. the `x` of `_xか.wav`. */
|
|
769
|
+
prefix: string;
|
|
770
|
+
/** Non-kana marker after the kana, e.g. the `b` of `_あb.wav`. */
|
|
771
|
+
mark: string;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Read the transcript out of a filename.
|
|
775
|
+
*
|
|
776
|
+
* The leading `_` that marks a recording-list file, an extension, and a
|
|
777
|
+
* trailing pitch tag are all noise; what is left has to be kana end to end, or
|
|
778
|
+
* the file is not a voice sample we can set up (a karaoke track, a sample song,
|
|
779
|
+
* a readme recording).
|
|
780
|
+
*/
|
|
781
|
+
declare function transcribe(filename: string): Transcript | null;
|
|
782
|
+
/**
|
|
783
|
+
* Alias suffix implied by a folder's name.
|
|
784
|
+
*
|
|
785
|
+
* A multi-pitch bank keeps one folder per pitch and merges every oto.ini into
|
|
786
|
+
* one alias namespace, so without the pitch tag each folder's `- あ` would
|
|
787
|
+
* overwrite the last. The tag is taken either from a folder named for nothing
|
|
788
|
+
* but the pitch (`G4`) or from an explicit `_G4` token inside a longer name
|
|
789
|
+
* (`多音階03:_G4(連続音)`). A bare `G4` buried in a name is left alone — in
|
|
790
|
+
* `表情音01:強(G4歌連続音)` it describes the take, and the suffix the bank
|
|
791
|
+
* actually uses there is `強`, which no filename carries.
|
|
792
|
+
*/
|
|
793
|
+
declare function suffixFromFolderName(folder: string): string;
|
|
794
|
+
/**
|
|
795
|
+
* Estimate oto.ini entries for every WAV in one folder.
|
|
796
|
+
*
|
|
797
|
+
* Decoding and analysis are per-file and independent, so a bad WAV is reported
|
|
798
|
+
* and skipped rather than failing the folder.
|
|
799
|
+
*/
|
|
800
|
+
declare function generateOtoForFile(file: WavInput, options?: GenerateOptions): FileResult;
|
|
801
|
+
/**
|
|
802
|
+
* Estimate oto.ini entries for every WAV in one folder.
|
|
803
|
+
*
|
|
804
|
+
* Decoding and analysis are per-file and independent, so a bad WAV is reported
|
|
805
|
+
* and skipped rather than failing the folder. A caller that needs to stay
|
|
806
|
+
* responsive — a browser UI, say — should drive {@link generateOtoForFile}
|
|
807
|
+
* itself and yield between files.
|
|
808
|
+
*/
|
|
809
|
+
declare function generateOto(files: readonly WavInput[], options?: GenerateOptions): GenerateResult;
|
|
810
|
+
/** Fold per-file styles into the one label that describes the folder. */
|
|
811
|
+
declare function summarise(solo: number, sequence: number): GenerateResult["style"];
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* oto.ini serialisation.
|
|
815
|
+
*
|
|
816
|
+
* UTAU reads oto.ini as Shift-JIS, and OpenUtau follows a bank's declared
|
|
817
|
+
* encoding, so writing UTF-8 would leave every kana alias mojibake in the
|
|
818
|
+
* original editor. There is no Shift-JIS *encoder* in the platform — only a
|
|
819
|
+
* decoder — so the table is built by decoding every legal byte pair once and
|
|
820
|
+
* inverting the result.
|
|
821
|
+
*/
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Encode text as Shift-JIS. Characters with no Shift-JIS form become `?`,
|
|
825
|
+
* matching what UTAU's own tools do rather than corrupting the line.
|
|
826
|
+
*/
|
|
827
|
+
declare function encodeShiftJis(text: string): Uint8Array;
|
|
828
|
+
/** Render entries as oto.ini text (CRLF, as UTAU writes it). */
|
|
829
|
+
declare function formatOto(entries: readonly OtoEntry[]): string;
|
|
830
|
+
/** Render entries as Shift-JIS oto.ini bytes, ready to write to disk. */
|
|
831
|
+
declare function encodeOto(entries: readonly OtoEntry[]): Uint8Array;
|
|
832
|
+
|
|
833
|
+
interface UtauTTSUnit {
|
|
834
|
+
position: number;
|
|
835
|
+
mora: string;
|
|
836
|
+
alias: string;
|
|
837
|
+
oto_path: string;
|
|
838
|
+
note_start_ms: number;
|
|
839
|
+
duration_ms: number;
|
|
840
|
+
offset_ms: number;
|
|
841
|
+
consonant_ms: number;
|
|
842
|
+
cutoff_ms: number;
|
|
843
|
+
preutterance_ms: number;
|
|
844
|
+
overlap_ms: number;
|
|
845
|
+
}
|
|
846
|
+
interface UtauTTSPlan {
|
|
847
|
+
duration_ms: number;
|
|
848
|
+
units: UtauTTSUnit[];
|
|
849
|
+
pitch_cents?: number[];
|
|
850
|
+
pitch_frame_ms?: number;
|
|
851
|
+
}
|
|
852
|
+
declare class UtauTTSAdapter {
|
|
853
|
+
private worldline;
|
|
854
|
+
constructor(worldline: Worldline);
|
|
855
|
+
/**
|
|
856
|
+
* Wasmモジュールを初期化します。
|
|
857
|
+
*/
|
|
858
|
+
static initializeWasm(wasmUrl?: string): Promise<void>;
|
|
859
|
+
/**
|
|
860
|
+
* koe の VoiceBank から、UtauTTS Wasm に渡すための仮想 oto.ini 辞書を構築します。
|
|
861
|
+
*/
|
|
862
|
+
private buildOtoEntriesFromKoe;
|
|
863
|
+
/**
|
|
864
|
+
* 指定したテキストを合成するための Plan (発話計画) と音声を生成します。
|
|
865
|
+
*/
|
|
866
|
+
synthesizeText(bank: VoiceBank, text: string, tone?: string): Promise<Float32Array | null>;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export { type ConsonantClass, type FileResult, type Frames, type FrqData, type GenerateOptions, type GenerateResult, type Grid, KoeEngine, type KoeEngineOptions, MIN_WORLDLINE_SAMPLES, type Manifest, type MoraPosition, type NoteEvent, type OtoEntry, type PackInput, type PackOutput, type PhonemeEntry, type PlayOptions, type RenderNoteParams, type SkippedFile, type Syllable, type TrimmedPhoneme, UtauTTSAdapter, type UtauTTSPlan, type UtauTTSUnit, VoiceBank, WORLDLINE_SAMPLE_RATE, type WavData, type WavInput, Worldline, type WorldlineLoadOptions, type ZipFile, analyze, analyzeWav, detectF0, detectGrid, encodeOto, encodeShiftJis, estimateSequence, estimateSolo, estimateVowelJoin, formatOto, frqAverageF0InRange, frqFileName, generateOto, generateOtoForFile, leadInFromEntry, locateMora, normalizePcm, noteNameToHz, otoRegion, pack, packKoe, parseFrq, parseFrqAverageF0, parseKoeHeader, parseOto, parseWav, pcmBase, pitchFromAliasSuffix, readWavPcm48k, resample, samplesToMs, splitKana, suffixFromFolderName, summarise, toHiragana, toInt16, toMono, transcribe, trimToOto, unzipToFileMap, zipFiles };
|