@onjmin/koe 1.0.6 → 1.0.7

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
@@ -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. 音源の変換 (oto.ini → .koe)
46
+ ### 1. 原音設定の自動生成 (wavoto.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
- ### 2. KoeEngine — リアルタイム再生 (ブラウザ)
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
- ### 3. VoiceBank — 音素 PCM の直接取得
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
- ### 4. Worldline — 高品質ノート合成 (WORLD ボコーダ)
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
- ### 5. .koe アーカイブ形式
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,303 @@ 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
- export { type FrqData, KoeEngine, type KoeEngineOptions, MIN_WORLDLINE_SAMPLES, type Manifest, type NoteEvent, type OtoEntry, type PackInput, type PackOutput, type PhonemeEntry, type PlayOptions, type RenderNoteParams, type TrimmedPhoneme, VoiceBank, WORLDLINE_SAMPLE_RATE, type WavData, Worldline, type WorldlineLoadOptions, type ZipFile, detectF0, frqAverageF0InRange, frqFileName, leadInFromEntry, normalizePcm, noteNameToHz, otoRegion, pack, packKoe, parseFrq, parseFrqAverageF0, parseKoeHeader, parseOto, parseWav, pcmBase, pitchFromAliasSuffix, readWavPcm48k, resample, samplesToMs, toInt16, toMono, trimToOto, unzipToFileMap };
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
+ 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, 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 };