@onjmin/koe 1.0.5 → 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 +122 -5
- package/dist/index.d.ts +421 -11
- package/dist/index.js +1885 -14
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +71 -14
- package/dist/koe-oto.js +1399 -0
- package/dist/koe-worklet.js +1 -1
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UTAU `.frq` frequency-analysis files (FREQ0003). The same format OpenUtau
|
|
3
|
-
* reads: an 8-byte header, hop size,
|
|
4
|
-
*
|
|
3
|
+
* reads: an 8-byte header, hop size, the average fundamental frequency of the
|
|
4
|
+
* whole recording, then a per-frame f0 / amplitude curve.
|
|
5
5
|
*
|
|
6
6
|
* Layout:
|
|
7
7
|
* char[8] "FREQ0003"
|
|
8
|
-
* int32 hopSize
|
|
9
|
-
* float64 averageF0
|
|
8
|
+
* int32 hopSize ← in ORIGINAL WAV samples
|
|
9
|
+
* float64 averageF0 ← whole-file average in Hz
|
|
10
10
|
* byte[16] (blank)
|
|
11
11
|
* int32 length
|
|
12
12
|
* { float64 f0, float64 amp } × length
|
|
13
13
|
*/
|
|
14
|
+
interface FrqData {
|
|
15
|
+
/** Analysis hop in samples of the ORIGINAL wav (not the 48 kHz conversion) */
|
|
16
|
+
hopSize: number;
|
|
17
|
+
/** Whole-file average f0 in Hz, as stored in the header */
|
|
18
|
+
averageF0: number;
|
|
19
|
+
/** Per-frame f0 in Hz — 0 where the analyser found no clear pitch */
|
|
20
|
+
f0: Float64Array;
|
|
21
|
+
/** Per-frame amplitude, parallel to {@link f0} */
|
|
22
|
+
amp: Float64Array;
|
|
23
|
+
}
|
|
24
|
+
/** Parse a `.frq` file, including the per-frame curve. Null if not FREQ0003. */
|
|
25
|
+
declare function parseFrq(buffer: ArrayBuffer): FrqData | null;
|
|
26
|
+
/**
|
|
27
|
+
* Average f0 over the voiced frames covering a time span of the recording.
|
|
28
|
+
*
|
|
29
|
+
* The header's whole-file average includes leading silence and unvoiced
|
|
30
|
+
* consonants, so it can sit well away from the pitch actually sounding in the
|
|
31
|
+
* region a note is built from. Playback resamples the whole phoneme by one
|
|
32
|
+
* scalar ratio, so an inaccurate value both detunes the note and makes two
|
|
33
|
+
* crossfading notes drift apart in phase across the overlap — a 1% error at
|
|
34
|
+
* 233 Hz drifts ~25° over a 30 ms overlap and ~84° over 100 ms.
|
|
35
|
+
*
|
|
36
|
+
* @param startMs / endMs span within the ORIGINAL recording, in milliseconds
|
|
37
|
+
* @param sourceRate sample rate of the original WAV the frq describes
|
|
38
|
+
* @returns Hz, or 0 when the span holds no voiced frames
|
|
39
|
+
*/
|
|
40
|
+
declare function frqAverageF0InRange(frq: FrqData, startMs: number, endMs: number, sourceRate: number): number;
|
|
41
|
+
/** Whole-file average f0 in Hz from a `.frq` file, or null. */
|
|
14
42
|
declare function parseFrqAverageF0(buffer: ArrayBuffer): number | null;
|
|
15
43
|
/** Map a WAV filename to its sibling frq filename: "あ.wav" → "あ_wav.frq". */
|
|
16
44
|
declare function frqFileName(wavName: string): string;
|
|
@@ -77,7 +105,7 @@ declare function parseOto(content: string): OtoEntry[];
|
|
|
77
105
|
|
|
78
106
|
interface PackInput {
|
|
79
107
|
oto: OtoEntry;
|
|
80
|
-
/** Full
|
|
108
|
+
/** Full PCM of the source WAV (48kHz / 16bit / mono) */
|
|
81
109
|
pcm: Int16Array;
|
|
82
110
|
/** Known recorded pitch in Hz (e.g. from the .frq file). 0/undefined → auto-detect. */
|
|
83
111
|
recordedPitch?: number;
|
|
@@ -93,6 +121,16 @@ interface TrimmedPhoneme {
|
|
|
93
121
|
/** Manifest params relative to the trimmed start (sample 0 = oto offset) */
|
|
94
122
|
entry: Omit<PhonemeEntry, "offset">;
|
|
95
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolve an oto entry's usable region within the full 48 kHz PCM.
|
|
126
|
+
*
|
|
127
|
+
* Shared with the converter CLI, which needs the same bounds to look up the
|
|
128
|
+
* region's local f0 in a `.frq` curve.
|
|
129
|
+
*/
|
|
130
|
+
declare function otoRegion(pcmLength: number, oto: OtoEntry): {
|
|
131
|
+
start: number;
|
|
132
|
+
end: number;
|
|
133
|
+
};
|
|
96
134
|
/**
|
|
97
135
|
* Cut the full WAV PCM down to its usable oto region and recompute parameters
|
|
98
136
|
* relative to the trimmed start.
|
|
@@ -107,8 +145,8 @@ interface TrimmedPhoneme {
|
|
|
107
145
|
*/
|
|
108
146
|
declare function trimToOto(pcm: Int16Array, oto: OtoEntry, recordedPitch?: number): TrimmedPhoneme;
|
|
109
147
|
/**
|
|
110
|
-
* Pack
|
|
111
|
-
* Each phoneme is trimmed to its oto region first.
|
|
148
|
+
* Pack phonemes into voice.bin + manifest.json.
|
|
149
|
+
* Each phoneme is trimmed to its oto region and DC-centred first.
|
|
112
150
|
* Duplicate aliases are silently overwritten by the later entry.
|
|
113
151
|
*/
|
|
114
152
|
declare function pack(inputs: PackInput[], referencePitch?: number): PackOutput;
|
|
@@ -142,7 +180,27 @@ declare function toMono(wav: WavData): WavData;
|
|
|
142
180
|
declare function resample(wav: WavData, targetRate: number): WavData;
|
|
143
181
|
/** Convert Float32 [-1,1] samples to Int16 PCM. */
|
|
144
182
|
declare function toInt16(samples: Float32Array): Int16Array;
|
|
145
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Decode a WAV to 48kHz/16bit/mono Int16 PCM, reporting the source sample rate.
|
|
185
|
+
*
|
|
186
|
+
* The rate is needed to index a sibling `.frq` file, whose analysis hop is
|
|
187
|
+
* counted in ORIGINAL samples — see {@link frqAverageF0InRange}.
|
|
188
|
+
*
|
|
189
|
+
* Note this does not touch amplitude: peak levels are carried through
|
|
190
|
+
* unchanged, so a bank's own relative loudness between phonemes is preserved
|
|
191
|
+
* (UTAU's engine instead normalises each region to −6 dBFS, which is why its
|
|
192
|
+
* イ/エ段 and 語尾 samples come out louder than recorded).
|
|
193
|
+
*/
|
|
194
|
+
declare function readWavPcm48k(buf: ArrayBuffer): {
|
|
195
|
+
pcm: Int16Array;
|
|
196
|
+
sourceRate: number;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Convert a WAV to 48kHz/16bit/mono Int16 PCM.
|
|
200
|
+
*
|
|
201
|
+
* @deprecated Misnomer — this never normalised amplitude. Use
|
|
202
|
+
* {@link readWavPcm48k}, which also reports the source sample rate.
|
|
203
|
+
*/
|
|
146
204
|
declare function normalizePcm(buf: ArrayBuffer): Int16Array;
|
|
147
205
|
|
|
148
206
|
/** Minimal file-like handle so zip entries can stand in for `File` objects. */
|
|
@@ -159,6 +217,14 @@ interface ZipFile {
|
|
|
159
217
|
* original bytes and re-decode with the right encoding per entry.
|
|
160
218
|
*/
|
|
161
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;
|
|
162
228
|
|
|
163
229
|
/**
|
|
164
230
|
* Read-only access to a .koe voice bank: its manifest plus per-phoneme PCM,
|
|
@@ -203,6 +269,21 @@ interface KoeEngineOptions {
|
|
|
203
269
|
/** URL to koe-worklet.js. Defaults to './koe-worklet.js'. */
|
|
204
270
|
workletUrl?: string;
|
|
205
271
|
}
|
|
272
|
+
interface PlayOptions {
|
|
273
|
+
/**
|
|
274
|
+
* Play the first note's lead-in (its consonant / preutterance region) instead
|
|
275
|
+
* of skipping straight to the vowel.
|
|
276
|
+
*
|
|
277
|
+
* Every note but the first gets its lead-in from the crossfade with the note
|
|
278
|
+
* before it. The first note has no predecessor, so the lead-in has to come
|
|
279
|
+
* from somewhere: with `leadIn` the phrase starts one preutterance EARLIER
|
|
280
|
+
* relative to its beats, and {@link KoeEngine.play} returns that offset in
|
|
281
|
+
* samples so a sequencer can schedule around it. Left off (the default), the
|
|
282
|
+
* first note keeps its beat exactly but opens on its vowel, dropping the
|
|
283
|
+
* consonant.
|
|
284
|
+
*/
|
|
285
|
+
leadIn?: boolean;
|
|
286
|
+
}
|
|
206
287
|
/**
|
|
207
288
|
* Main-thread API for the koe concatenative synthesis engine.
|
|
208
289
|
*
|
|
@@ -237,8 +318,18 @@ declare class KoeEngine {
|
|
|
237
318
|
load(koe: Blob | string): Promise<void>;
|
|
238
319
|
/** Fetch one phoneme's PCM and deliver it to the worklet (deduped, cached). */
|
|
239
320
|
private ensurePhoneme;
|
|
240
|
-
/**
|
|
241
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Stop current playback, preload the phonemes for `notes`, then queue them.
|
|
323
|
+
*
|
|
324
|
+
* @returns the lead-in offset in samples — how far the first note's audio
|
|
325
|
+
* starts ahead of its beat. 0 unless {@link PlayOptions.leadIn}.
|
|
326
|
+
*/
|
|
327
|
+
play(notes: NoteEvent[], options?: PlayOptions): Promise<number>;
|
|
328
|
+
/**
|
|
329
|
+
* Output samples the first note's lead-in occupies ahead of its beat —
|
|
330
|
+
* mirrors what the worklet does with `leadIn`, so callers can compensate.
|
|
331
|
+
*/
|
|
332
|
+
private leadInSamples;
|
|
242
333
|
/** Stop playback and clear the queue. */
|
|
243
334
|
stop(): void;
|
|
244
335
|
/** Resume the AudioContext if suspended (e.g. after autoplay block). */
|
|
@@ -310,6 +401,25 @@ interface WorldlineLoadOptions {
|
|
|
310
401
|
* function you give it once per frame.
|
|
311
402
|
*/
|
|
312
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
|
+
}
|
|
313
423
|
interface RenderNoteParams {
|
|
314
424
|
/**
|
|
315
425
|
* Source phoneme PCM normalised to [-1, 1] (e.g. from
|
|
@@ -402,6 +512,7 @@ declare class Worldline {
|
|
|
402
512
|
* @returns Float32 PCM, or null when `pcm` is shorter than
|
|
403
513
|
* {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
|
|
404
514
|
*/
|
|
515
|
+
renderPhrase(params: RenderPhraseParams): Float32Array | null;
|
|
405
516
|
renderNote(params: RenderNoteParams): Float32Array | null;
|
|
406
517
|
}
|
|
407
518
|
|
|
@@ -420,4 +531,303 @@ declare function parseKoeHeader(headerBytes: ArrayBuffer): {
|
|
|
420
531
|
/** Byte offset where PCM data begins, given the JSON length. */
|
|
421
532
|
declare const pcmBase: (jsonLength: number) => number;
|
|
422
533
|
|
|
423
|
-
|
|
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 };
|