@kongyo2/kongyoroid 1.0.0 → 2.0.0
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 +233 -0
- package/dist/cache.d.ts +32 -7
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +138 -9
- package/dist/cache.js.map +1 -1
- package/dist/cli/flags.d.ts +270 -0
- package/dist/cli/flags.d.ts.map +1 -0
- package/dist/cli/flags.js +240 -0
- package/dist/cli/flags.js.map +1 -0
- package/dist/cli/help.d.ts +11 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +336 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli.js +598 -232
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +43 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +56 -3
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +59 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -14
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +11 -0
- package/dist/io.d.ts.map +1 -1
- package/dist/io.js +149 -5
- package/dist/io.js.map +1 -1
- package/dist/kongyoroid.d.ts +237 -10
- package/dist/kongyoroid.d.ts.map +1 -1
- package/dist/kongyoroid.js +777 -86
- package/dist/kongyoroid.js.map +1 -1
- package/dist/limits.d.ts +5 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +6 -1
- package/dist/limits.js.map +1 -1
- package/dist/request.d.ts +1 -0
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +102 -15
- package/dist/request.js.map +1 -1
- package/dist/schema.d.ts +3 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +271 -68
- package/dist/schema.js.map +1 -1
- package/dist/song/mml.d.ts +15 -0
- package/dist/song/mml.d.ts.map +1 -0
- package/dist/song/mml.js +184 -0
- package/dist/song/mml.js.map +1 -0
- package/dist/song/pitch.d.ts.map +1 -0
- package/dist/{pitch.js → song/pitch.js} +1 -1
- package/dist/song/pitch.js.map +1 -0
- package/dist/song/score.d.ts +92 -0
- package/dist/song/score.d.ts.map +1 -0
- package/dist/song/score.js +454 -0
- package/dist/song/score.js.map +1 -0
- package/dist/synth/acoustics.d.ts +27 -0
- package/dist/synth/acoustics.d.ts.map +1 -0
- package/dist/synth/acoustics.js +608 -0
- package/dist/synth/acoustics.js.map +1 -0
- package/dist/synth/analysis.d.ts +22 -0
- package/dist/synth/analysis.d.ts.map +1 -0
- package/dist/synth/analysis.js +170 -0
- package/dist/synth/analysis.js.map +1 -0
- package/dist/synth/engine.d.ts +115 -0
- package/dist/synth/engine.d.ts.map +1 -0
- package/dist/synth/engine.js +223 -0
- package/dist/synth/engine.js.map +1 -0
- package/dist/synth/filters.d.ts +60 -0
- package/dist/synth/filters.d.ts.map +1 -0
- package/dist/synth/filters.js +166 -0
- package/dist/synth/filters.js.map +1 -0
- package/dist/synth/glottal.d.ts +20 -0
- package/dist/synth/glottal.d.ts.map +1 -0
- package/dist/synth/glottal.js +76 -0
- package/dist/synth/glottal.js.map +1 -0
- package/dist/synth/phonemes.d.ts +42 -0
- package/dist/synth/phonemes.d.ts.map +1 -0
- package/dist/synth/phonemes.js +311 -0
- package/dist/synth/phonemes.js.map +1 -0
- package/dist/synth/plan.d.ts +127 -0
- package/dist/synth/plan.d.ts.map +1 -0
- package/dist/synth/plan.js +81 -0
- package/dist/synth/plan.js.map +1 -0
- package/dist/synth/prosody.d.ts +30 -0
- package/dist/synth/prosody.d.ts.map +1 -0
- package/dist/synth/prosody.js +46 -0
- package/dist/synth/prosody.js.map +1 -0
- package/dist/synth/random.d.ts +15 -0
- package/dist/synth/random.d.ts.map +1 -0
- package/dist/synth/random.js +59 -0
- package/dist/synth/random.js.map +1 -0
- package/dist/synth/renderer.d.ts +77 -0
- package/dist/synth/renderer.d.ts.map +1 -0
- package/dist/synth/renderer.js +343 -0
- package/dist/synth/renderer.js.map +1 -0
- package/dist/synth/song.d.ts +30 -0
- package/dist/synth/song.d.ts.map +1 -0
- package/dist/synth/song.js +453 -0
- package/dist/synth/song.js.map +1 -0
- package/dist/synth/speech.d.ts +49 -0
- package/dist/synth/speech.d.ts.map +1 -0
- package/dist/synth/speech.js +354 -0
- package/dist/synth/speech.js.map +1 -0
- package/dist/synth/stream.d.ts +26 -0
- package/dist/synth/stream.d.ts.map +1 -0
- package/dist/synth/stream.js +67 -0
- package/dist/synth/stream.js.map +1 -0
- package/dist/synth/textstream.d.ts +35 -0
- package/dist/synth/textstream.d.ts.map +1 -0
- package/dist/synth/textstream.js +127 -0
- package/dist/synth/textstream.js.map +1 -0
- package/dist/synth/voice.d.ts +64 -0
- package/dist/synth/voice.d.ts.map +1 -0
- package/dist/synth/voice.js +208 -0
- package/dist/synth/voice.js.map +1 -0
- package/dist/text/dictionary.d.ts +50 -0
- package/dist/text/dictionary.d.ts.map +1 -0
- package/dist/text/dictionary.js +0 -0
- package/dist/text/dictionary.js.map +1 -0
- package/dist/text/frontend.d.ts +34 -0
- package/dist/text/frontend.d.ts.map +1 -0
- package/dist/text/frontend.js +550 -0
- package/dist/text/frontend.js.map +1 -0
- package/dist/text/lexicon.d.ts +10 -0
- package/dist/text/lexicon.d.ts.map +1 -0
- package/dist/text/lexicon.js +763 -0
- package/dist/text/lexicon.js.map +1 -0
- package/dist/{mora.d.ts → text/mora.d.ts} +15 -3
- package/dist/text/mora.d.ts.map +1 -0
- package/dist/{mora.js → text/mora.js} +87 -9
- package/dist/text/mora.js.map +1 -0
- package/dist/text/normalize.d.ts +34 -0
- package/dist/text/normalize.d.ts.map +1 -0
- package/dist/text/normalize.js +496 -0
- package/dist/text/normalize.js.map +1 -0
- package/dist/text/notation.d.ts +32 -0
- package/dist/text/notation.d.ts.map +1 -0
- package/dist/text/notation.js +182 -0
- package/dist/text/notation.js.map +1 -0
- package/dist/text/reading.d.ts +52 -0
- package/dist/text/reading.d.ts.map +1 -0
- package/dist/text/reading.js +32 -0
- package/dist/text/reading.js.map +1 -0
- package/dist/text/sentences.d.ts +10 -0
- package/dist/text/sentences.d.ts.map +1 -0
- package/dist/text/sentences.js +84 -0
- package/dist/text/sentences.js.map +1 -0
- package/dist/{text.d.ts → text/split.d.ts} +2 -2
- package/dist/text/split.d.ts.map +1 -0
- package/dist/{text.js → text/split.js} +2 -2
- package/dist/text/split.js.map +1 -0
- package/dist/types.d.ts +58 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +5 -1
- package/dist/version.js.map +1 -1
- package/dist/voicevox/client.d.ts +1 -1
- package/dist/voicevox/client.d.ts.map +1 -1
- package/dist/voicevox/dictionary.js +1 -1
- package/dist/voicevox/dictionary.js.map +1 -1
- package/dist/voicevox/song.js +1 -1
- package/dist/voicevox/song.js.map +1 -1
- package/dist/voicevox/speech.d.ts +1 -0
- package/dist/voicevox/speech.d.ts.map +1 -1
- package/dist/voicevox/speech.js +8 -2
- package/dist/voicevox/speech.js.map +1 -1
- package/docs/cli.md +251 -0
- package/examples/batch.jsonl +4 -3
- package/examples/builtin/dictionary.json +7 -0
- package/examples/builtin/song-compact.json +13 -0
- package/examples/builtin/song-mml.json +9 -0
- package/examples/builtin/song-notes.json +25 -0
- package/examples/builtin/speech-dictionary.json +9 -0
- package/examples/builtin/speech-kana.json +7 -0
- package/examples/builtin/speech.json +7 -0
- package/package.json +11 -3
- package/dist/formant.d.ts +0 -34
- package/dist/formant.d.ts.map +0 -1
- package/dist/formant.js +0 -622
- package/dist/formant.js.map +0 -1
- package/dist/mora.d.ts.map +0 -1
- package/dist/mora.js.map +0 -1
- package/dist/notation.d.ts +0 -15
- package/dist/notation.d.ts.map +0 -1
- package/dist/notation.js +0 -114
- package/dist/notation.js.map +0 -1
- package/dist/pitch.d.ts.map +0 -1
- package/dist/pitch.js.map +0 -1
- package/dist/score.d.ts +0 -37
- package/dist/score.d.ts.map +0 -1
- package/dist/score.js +0 -166
- package/dist/score.js.map +0 -1
- package/dist/text.d.ts.map +0 -1
- package/dist/text.js.map +0 -1
- package/examples/song-notes.json +0 -24
- /package/dist/{pitch.d.ts → song/pitch.d.ts} +0 -0
- /package/examples/{song.json → voicevox/song.json} +0 -0
- /package/examples/{speech-kana.json → voicevox/speech-kana.json} +0 -0
- /package/examples/{speech.json → voicevox/speech.json} +0 -0
package/dist/formant.js
DELETED
|
@@ -1,622 +0,0 @@
|
|
|
1
|
-
import { setImmediate as yieldToEventLoop } from "node:timers/promises";
|
|
2
|
-
import { checkAbort, invalid } from "./errors.js";
|
|
3
|
-
import { LIMITS } from "./limits.js";
|
|
4
|
-
import { kanaToUnits } from "./mora.js";
|
|
5
|
-
import { parseKanaNotation } from "./notation.js";
|
|
6
|
-
import { midiToHz } from "./pitch.js";
|
|
7
|
-
import { encodePcm16, wavHeader } from "./wav.js";
|
|
8
|
-
function vowel(f, amp) {
|
|
9
|
-
return { f, bw: [80, 110, 170, 230], amp, voiced: 1, noise: 0.02, level: 1, kind: "vowel", duration: 0.1 };
|
|
10
|
-
}
|
|
11
|
-
function whisper(base) {
|
|
12
|
-
return { ...base, voiced: 0, noise: 1, level: 0.22, bw: [140, 180, 240, 300] };
|
|
13
|
-
}
|
|
14
|
-
function nasal(f, duration) {
|
|
15
|
-
return {
|
|
16
|
-
f,
|
|
17
|
-
bw: [90, 220, 280, 320],
|
|
18
|
-
amp: [1, 0.22, 0.09, 0.03],
|
|
19
|
-
voiced: 1,
|
|
20
|
-
noise: 0.01,
|
|
21
|
-
level: 0.72,
|
|
22
|
-
kind: "nasal",
|
|
23
|
-
duration,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function stop(f, amp, voiced, level, duration) {
|
|
27
|
-
return { f, bw: [220, 320, 420, 520], amp, voiced, noise: 1, level, kind: "stop", duration };
|
|
28
|
-
}
|
|
29
|
-
function fricative(f, bw, amp, voiced, level, duration, kind = "fricative") {
|
|
30
|
-
return { f, bw, amp, voiced, noise: 1, level, kind, duration };
|
|
31
|
-
}
|
|
32
|
-
function glide(f, amp) {
|
|
33
|
-
return { f, bw: [70, 120, 180, 240], amp, voiced: 1, noise: 0.02, level: 0.82, kind: "glide", duration: 0.045 };
|
|
34
|
-
}
|
|
35
|
-
function palatal(base) {
|
|
36
|
-
return { ...base, f: [base.f[0], Math.max(base.f[1], 2100), Math.max(base.f[2], 2900), base.f[3]] };
|
|
37
|
-
}
|
|
38
|
-
function labial(base) {
|
|
39
|
-
return { ...base, f: [base.f[0], Math.min(base.f[1], 900), base.f[2], base.f[3]] };
|
|
40
|
-
}
|
|
41
|
-
const A = vowel([750, 1200, 2600, 3500], [1, 0.5, 0.25, 0.1]);
|
|
42
|
-
const I = vowel([290, 2250, 3000, 3600], [1, 0.32, 0.2, 0.08]);
|
|
43
|
-
const U = vowel([330, 1300, 2350, 3400], [1, 0.28, 0.16, 0.06]);
|
|
44
|
-
const E = vowel([500, 1900, 2550, 3500], [1, 0.45, 0.25, 0.1]);
|
|
45
|
-
const O = vowel([520, 900, 2500, 3400], [1, 0.45, 0.18, 0.07]);
|
|
46
|
-
const SILENCE = {
|
|
47
|
-
f: [500, 1500, 2500, 3500],
|
|
48
|
-
bw: [200, 200, 200, 200],
|
|
49
|
-
amp: [0, 0, 0, 0],
|
|
50
|
-
voiced: 0,
|
|
51
|
-
noise: 0,
|
|
52
|
-
level: 0,
|
|
53
|
-
kind: "silence",
|
|
54
|
-
duration: 0.08,
|
|
55
|
-
};
|
|
56
|
-
const K = stop([1500, 2500, 3500, 4500], [0.4, 1, 0.6, 0.3], 0, 0.7, 0.065);
|
|
57
|
-
const G = stop([400, 1800, 2600, 3400], [0.7, 1, 0.5, 0.25], 0.45, 0.65, 0.05);
|
|
58
|
-
const T = stop([1000, 3500, 4500, 5500], [0.3, 1, 0.7, 0.4], 0, 0.7, 0.06);
|
|
59
|
-
const D = stop([350, 2000, 3000, 4000], [0.8, 1, 0.5, 0.25], 0.5, 0.65, 0.045);
|
|
60
|
-
const P = stop([600, 1300, 2500, 3500], [1, 0.6, 0.3, 0.15], 0, 0.55, 0.065);
|
|
61
|
-
const B = stop([300, 1000, 2300, 3300], [1, 0.6, 0.3, 0.15], 0.55, 0.6, 0.045);
|
|
62
|
-
const S = fricative([4500, 6000, 7500, 9000], [800, 900, 1000, 1200], [0.6, 1, 0.8, 0.5], 0, 0.45, 0.085);
|
|
63
|
-
const SH = fricative([2600, 3800, 5200, 7000], [500, 600, 800, 1000], [0.8, 1, 0.7, 0.4], 0, 0.5, 0.085);
|
|
64
|
-
const H = fricative([900, 1900, 2800, 3600], [300, 400, 500, 600], [0.7, 0.8, 0.5, 0.3], 0, 0.32, 0.07);
|
|
65
|
-
const F = fricative([1100, 2100, 3200, 4500], [600, 700, 800, 900], [0.7, 0.8, 0.6, 0.4], 0, 0.3, 0.075);
|
|
66
|
-
const NASAL_N = nasal([280, 1300, 2300, 3300], 0.055);
|
|
67
|
-
const M = nasal([250, 950, 2200, 3200], 0.06);
|
|
68
|
-
const R = {
|
|
69
|
-
f: [380, 1400, 2300, 3300],
|
|
70
|
-
bw: [100, 150, 200, 250],
|
|
71
|
-
amp: [1, 0.45, 0.2, 0.08],
|
|
72
|
-
voiced: 1,
|
|
73
|
-
noise: 0.02,
|
|
74
|
-
level: 0.6,
|
|
75
|
-
kind: "flap",
|
|
76
|
-
duration: 0.026,
|
|
77
|
-
};
|
|
78
|
-
const Y = glide([280, 2100, 2900, 3500], [1, 0.35, 0.2, 0.08]);
|
|
79
|
-
const W = glide([300, 700, 2300, 3300], [1, 0.4, 0.15, 0.06]);
|
|
80
|
-
const TIMBRES = {
|
|
81
|
-
a: A,
|
|
82
|
-
i: I,
|
|
83
|
-
u: U,
|
|
84
|
-
e: E,
|
|
85
|
-
o: O,
|
|
86
|
-
A: whisper(A),
|
|
87
|
-
I: whisper(I),
|
|
88
|
-
U: whisper(U),
|
|
89
|
-
E: whisper(E),
|
|
90
|
-
O: whisper(O),
|
|
91
|
-
N: nasal([280, 1100, 2300, 3300], 0.085),
|
|
92
|
-
cl: SILENCE,
|
|
93
|
-
pau: SILENCE,
|
|
94
|
-
k: K,
|
|
95
|
-
ky: palatal(K),
|
|
96
|
-
kw: labial(K),
|
|
97
|
-
g: G,
|
|
98
|
-
gy: palatal(G),
|
|
99
|
-
gw: labial(G),
|
|
100
|
-
t: T,
|
|
101
|
-
ty: palatal(T),
|
|
102
|
-
d: D,
|
|
103
|
-
dy: palatal(D),
|
|
104
|
-
p: P,
|
|
105
|
-
py: palatal(P),
|
|
106
|
-
b: B,
|
|
107
|
-
by: palatal(B),
|
|
108
|
-
s: S,
|
|
109
|
-
sh: SH,
|
|
110
|
-
z: fricative(S.f, S.bw, S.amp, 0.6, 0.5, 0.07),
|
|
111
|
-
j: fricative(SH.f, SH.bw, SH.amp, 0.6, 0.5, 0.075, "affricate"),
|
|
112
|
-
ch: fricative(SH.f, SH.bw, SH.amp, 0, 0.55, 0.085, "affricate"),
|
|
113
|
-
ts: fricative(S.f, S.bw, S.amp, 0, 0.5, 0.085, "affricate"),
|
|
114
|
-
h: H,
|
|
115
|
-
hy: palatal(H),
|
|
116
|
-
f: F,
|
|
117
|
-
v: fricative(F.f, F.bw, F.amp, 0.7, 0.45, 0.06),
|
|
118
|
-
n: NASAL_N,
|
|
119
|
-
ny: palatal(NASAL_N),
|
|
120
|
-
m: M,
|
|
121
|
-
my: palatal(M),
|
|
122
|
-
r: R,
|
|
123
|
-
ry: palatal(R),
|
|
124
|
-
y: Y,
|
|
125
|
-
w: W,
|
|
126
|
-
};
|
|
127
|
-
const PALATALIZED = new Set(["ky", "gy", "ty", "dy", "py", "by", "hy", "ny", "my", "ry"]);
|
|
128
|
-
const LABIALIZED = new Set(["kw", "gw"]);
|
|
129
|
-
const BASE_F0 = 150;
|
|
130
|
-
function isVoicedSustain(phoneme) {
|
|
131
|
-
const kind = TIMBRES[phoneme].kind;
|
|
132
|
-
return (kind === "vowel" || kind === "nasal" || kind === "glide" || kind === "flap") && TIMBRES[phoneme].voiced > 0;
|
|
133
|
-
}
|
|
134
|
-
function finalize(drafts, sampleRate, plan) {
|
|
135
|
-
const segments = [];
|
|
136
|
-
let cursor = 0;
|
|
137
|
-
let seconds = 0;
|
|
138
|
-
for (const [index, draft] of drafts.entries()) {
|
|
139
|
-
seconds += draft.seconds;
|
|
140
|
-
const end = Math.round(seconds * sampleRate);
|
|
141
|
-
const frames = end - cursor;
|
|
142
|
-
if (frames <= 0)
|
|
143
|
-
continue;
|
|
144
|
-
cursor = end;
|
|
145
|
-
const previous = drafts[index - 1];
|
|
146
|
-
const next = drafts[index + 1];
|
|
147
|
-
segments.push({
|
|
148
|
-
phoneme: draft.phoneme,
|
|
149
|
-
frames,
|
|
150
|
-
f0Start: draft.f0Start,
|
|
151
|
-
f0End: draft.f0End,
|
|
152
|
-
amplitude: draft.amplitude,
|
|
153
|
-
vibrato: draft.vibrato,
|
|
154
|
-
rampIn: previous === undefined || !isVoicedSustain(previous.phoneme),
|
|
155
|
-
rampOut: next === undefined || !isVoicedSustain(next.phoneme),
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
if (cursor > LIMITS.formantSeconds * sampleRate) {
|
|
159
|
-
invalid("$", `Audio would last ${(cursor / sampleRate).toFixed(1)} s; the formant engine renders at most ${LIMITS.formantSeconds} s per request.`, "Split the input into shorter requests, or use the voicevox engine.");
|
|
160
|
-
}
|
|
161
|
-
return { ...plan, segments, frames: cursor };
|
|
162
|
-
}
|
|
163
|
-
function consonantDrafts(consonant, seconds, f0Start, f0End) {
|
|
164
|
-
if (consonant === null)
|
|
165
|
-
return [];
|
|
166
|
-
const out = [{ phoneme: consonant, seconds, f0Start, f0End, amplitude: 1, vibrato: false }];
|
|
167
|
-
if (PALATALIZED.has(consonant))
|
|
168
|
-
out.push({ phoneme: "y", seconds: 0.03, f0Start: f0End, f0End, amplitude: 0.9, vibrato: false });
|
|
169
|
-
if (LABIALIZED.has(consonant))
|
|
170
|
-
out.push({ phoneme: "w", seconds: 0.03, f0Start: f0End, f0End, amplitude: 0.9, vibrato: false });
|
|
171
|
-
return out;
|
|
172
|
-
}
|
|
173
|
-
function shapePitch(hz, pitch, intonation) {
|
|
174
|
-
const mean = Math.log(BASE_F0 * 1.08);
|
|
175
|
-
const scaled = mean + (Math.log(hz) - mean) * intonation;
|
|
176
|
-
return Math.exp(scaled * 2 ** pitch);
|
|
177
|
-
}
|
|
178
|
-
function heuristicPhrases(units) {
|
|
179
|
-
const phrases = [];
|
|
180
|
-
let moras = [];
|
|
181
|
-
const flush = (pauseWeight, interrogative) => {
|
|
182
|
-
if (moras.length === 0) {
|
|
183
|
-
const last = phrases.at(-1);
|
|
184
|
-
if (last !== undefined && pauseWeight > 0) {
|
|
185
|
-
phrases[phrases.length - 1] = {
|
|
186
|
-
phrase: { ...last.phrase, pause: true, interrogative: last.phrase.interrogative || interrogative },
|
|
187
|
-
pauseWeight: last.pauseWeight + pauseWeight,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
phrases.push({ phrase: { moras, accent: moras.length, pause: pauseWeight > 0, interrogative }, pauseWeight });
|
|
193
|
-
moras = [];
|
|
194
|
-
};
|
|
195
|
-
for (const unit of units) {
|
|
196
|
-
if (unit.kind === "pause") {
|
|
197
|
-
flush(unit.weight, unit.text === "?");
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
moras.push({ text: unit.text, consonant: unit.consonant, vowel: unit.vowel });
|
|
201
|
-
if (moras.length >= 7)
|
|
202
|
-
flush(0, false);
|
|
203
|
-
}
|
|
204
|
-
flush(0, false);
|
|
205
|
-
return phrases;
|
|
206
|
-
}
|
|
207
|
-
export function planSpeech(request, sampleRate) {
|
|
208
|
-
const phrases = request.kana === undefined
|
|
209
|
-
? heuristicPhrases(kanaToUnits(request.text, "$.text"))
|
|
210
|
-
: parseKanaNotation(request.kana, "$.kana").map((phrase) => ({ phrase, pauseWeight: 1 }));
|
|
211
|
-
if (phrases.length === 0)
|
|
212
|
-
invalid("$.text", "Text contains no readable kana.");
|
|
213
|
-
const speed = request.speed;
|
|
214
|
-
const drafts = [];
|
|
215
|
-
const rest = (seconds) => {
|
|
216
|
-
drafts.push({ phoneme: "pau", seconds, f0Start: 0, f0End: 0, amplitude: 0, vibrato: false });
|
|
217
|
-
};
|
|
218
|
-
rest(request.prePause);
|
|
219
|
-
let previousHz = shapePitch(BASE_F0, request.pitch, request.intonation);
|
|
220
|
-
for (const [phraseIndex, { phrase, pauseWeight }] of phrases.entries()) {
|
|
221
|
-
const declination = Math.max(0.85, 1 - 0.04 * phraseIndex);
|
|
222
|
-
const count = phrase.moras.length;
|
|
223
|
-
for (const [moraIndex, mora] of phrase.moras.entries()) {
|
|
224
|
-
const position = moraIndex + 1;
|
|
225
|
-
const high = phrase.accent === 1 ? position === 1 : position >= 2 && position <= phrase.accent;
|
|
226
|
-
const slope = high ? 1.22 - (0.1 * moraIndex) / Math.max(1, count - 1) : 0.97;
|
|
227
|
-
const targetHz = shapePitch(BASE_F0 * slope * declination, request.pitch, request.intonation);
|
|
228
|
-
const last = moraIndex === count - 1;
|
|
229
|
-
const endHz = request.upspeak && phrase.interrogative && last ? targetHz * 1.35 : targetHz * 0.985;
|
|
230
|
-
const timbre = TIMBRES[mora.vowel];
|
|
231
|
-
const consonant = mora.consonant;
|
|
232
|
-
const consonantSeconds = consonant === null ? 0 : TIMBRES[consonant].duration / speed;
|
|
233
|
-
drafts.push(...consonantDrafts(consonant, consonantSeconds, previousHz, targetHz));
|
|
234
|
-
const vowelSeconds = (mora.vowel === "cl"
|
|
235
|
-
? 0.08
|
|
236
|
-
: mora.vowel === "N"
|
|
237
|
-
? 0.085
|
|
238
|
-
: timbre.kind === "vowel" && timbre.voiced === 0
|
|
239
|
-
? 0.07
|
|
240
|
-
: consonant === null
|
|
241
|
-
? 0.12
|
|
242
|
-
: 0.095) / speed;
|
|
243
|
-
drafts.push({
|
|
244
|
-
phoneme: mora.vowel,
|
|
245
|
-
seconds: vowelSeconds,
|
|
246
|
-
f0Start: targetHz,
|
|
247
|
-
f0End: endHz,
|
|
248
|
-
amplitude: 1,
|
|
249
|
-
vibrato: false,
|
|
250
|
-
});
|
|
251
|
-
previousHz = endHz;
|
|
252
|
-
}
|
|
253
|
-
if (phrase.pause) {
|
|
254
|
-
const base = request.pauseLength ?? 0.18 * pauseWeight;
|
|
255
|
-
rest((base * request.pauseScale) / speed);
|
|
256
|
-
}
|
|
257
|
-
else if (phraseIndex < phrases.length - 1) {
|
|
258
|
-
rest(0.02 / speed);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
rest(request.postPause);
|
|
262
|
-
return finalize(drafts, sampleRate, {
|
|
263
|
-
sampleRate,
|
|
264
|
-
seed: request.seed,
|
|
265
|
-
volume: request.volume,
|
|
266
|
-
vibratoDepth: 0,
|
|
267
|
-
vibratoRate: 0,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
export function planSong(request, sampleRate) {
|
|
271
|
-
const secondsPerBeat = 60 / request.tempo;
|
|
272
|
-
const notes = request.notes;
|
|
273
|
-
const drafts = [];
|
|
274
|
-
const rest = (seconds) => {
|
|
275
|
-
drafts.push({ phoneme: "pau", seconds, f0Start: 0, f0End: 0, amplitude: 0, vibrato: false });
|
|
276
|
-
};
|
|
277
|
-
const leadIn = notes[0]?.key === null ? 0 : request.leadIn;
|
|
278
|
-
const durations = notes.map((note) => note.beats * secondsPerBeat);
|
|
279
|
-
const consonantOf = (index) => {
|
|
280
|
-
const note = notes[index];
|
|
281
|
-
if (note === undefined || note.key === null)
|
|
282
|
-
return { consonant: null, seconds: 0, fromOwn: false };
|
|
283
|
-
const units = kanaToUnits(note.lyric, `$.notes[${index}].lyric`);
|
|
284
|
-
const unit = units[0];
|
|
285
|
-
if (unit === undefined || unit.kind !== "mora" || unit.consonant === null) {
|
|
286
|
-
return { consonant: null, seconds: 0, fromOwn: false };
|
|
287
|
-
}
|
|
288
|
-
const nominal = TIMBRES[unit.consonant].duration + (PALATALIZED.has(unit.consonant) || LABIALIZED.has(unit.consonant) ? 0.03 : 0);
|
|
289
|
-
const fromOwn = index === 0 && leadIn <= 0;
|
|
290
|
-
const available = index === 0 ? (fromOwn ? (durations[0] ?? 0) * 0.4 : leadIn) : (durations[index - 1] ?? 0) * 0.4;
|
|
291
|
-
return { consonant: unit.consonant, seconds: Math.min(nominal, available), fromOwn };
|
|
292
|
-
};
|
|
293
|
-
let previousHz = 0;
|
|
294
|
-
const first = consonantOf(0);
|
|
295
|
-
if (leadIn > 0)
|
|
296
|
-
rest(leadIn - first.seconds);
|
|
297
|
-
for (const [index, note] of notes.entries()) {
|
|
298
|
-
const duration = durations[index] ?? 0;
|
|
299
|
-
const own = consonantOf(index);
|
|
300
|
-
const following = consonantOf(index + 1);
|
|
301
|
-
if (note.key === null) {
|
|
302
|
-
rest(Math.max(0, duration - following.seconds));
|
|
303
|
-
previousHz = 0;
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
const hz = midiToHz(note.key);
|
|
307
|
-
const vowelUnit = kanaToUnits(note.lyric, `$.notes[${index}].lyric`)[0];
|
|
308
|
-
const vowelPhoneme = vowelUnit !== undefined && vowelUnit.kind === "mora" ? vowelUnit.vowel : "a";
|
|
309
|
-
if (own.consonant !== null && own.seconds > 0) {
|
|
310
|
-
const glideSeconds = PALATALIZED.has(own.consonant) || LABIALIZED.has(own.consonant) ? Math.min(0.03, own.seconds * 0.4) : 0;
|
|
311
|
-
const parts = consonantDrafts(own.consonant, own.seconds - glideSeconds, previousHz || hz, hz);
|
|
312
|
-
for (const part of parts)
|
|
313
|
-
if (part.phoneme === "y" || part.phoneme === "w")
|
|
314
|
-
part.seconds = glideSeconds;
|
|
315
|
-
drafts.push(...parts);
|
|
316
|
-
}
|
|
317
|
-
const vowelSeconds = Math.max(0, duration - following.seconds - (own.fromOwn ? own.seconds : 0));
|
|
318
|
-
const previous = drafts.at(-1);
|
|
319
|
-
if (previous !== undefined &&
|
|
320
|
-
previous.phoneme === vowelPhoneme &&
|
|
321
|
-
previous.vibrato &&
|
|
322
|
-
previous.f0End === hz &&
|
|
323
|
-
own.consonant === null) {
|
|
324
|
-
previous.seconds += vowelSeconds;
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
drafts.push({
|
|
328
|
-
phoneme: vowelPhoneme,
|
|
329
|
-
seconds: vowelSeconds,
|
|
330
|
-
f0Start: previousHz === 0 ? hz : previousHz,
|
|
331
|
-
f0End: hz,
|
|
332
|
-
amplitude: 1,
|
|
333
|
-
vibrato: true,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
previousHz = hz;
|
|
337
|
-
}
|
|
338
|
-
if (notes.at(-1)?.key !== null)
|
|
339
|
-
rest(request.leadOut);
|
|
340
|
-
return finalize(drafts, sampleRate, {
|
|
341
|
-
sampleRate,
|
|
342
|
-
seed: request.seed,
|
|
343
|
-
volume: request.volume,
|
|
344
|
-
vibratoDepth: request.vibratoDepth ?? 25,
|
|
345
|
-
vibratoRate: request.vibratoRate,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
class Resonator {
|
|
349
|
-
a1 = 0;
|
|
350
|
-
a2 = 0;
|
|
351
|
-
b = 0;
|
|
352
|
-
y1 = 0;
|
|
353
|
-
y2 = 0;
|
|
354
|
-
x1 = 0;
|
|
355
|
-
x2 = 0;
|
|
356
|
-
tune(frequency, width, rate) {
|
|
357
|
-
const radius = Math.exp((-Math.PI * width) / rate);
|
|
358
|
-
this.a1 = 2 * radius * Math.cos((2 * Math.PI * Math.min(rate * 0.45, frequency)) / rate);
|
|
359
|
-
this.a2 = radius * radius;
|
|
360
|
-
this.b = (1 - radius) * 0.5;
|
|
361
|
-
}
|
|
362
|
-
step(x) {
|
|
363
|
-
const y = this.b * (x - this.x2) + this.a1 * this.y1 - this.a2 * this.y2;
|
|
364
|
-
this.x2 = this.x1;
|
|
365
|
-
this.x1 = x;
|
|
366
|
-
this.y2 = this.y1;
|
|
367
|
-
this.y1 = y;
|
|
368
|
-
return y;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
function polyBlep(t, step) {
|
|
372
|
-
if (t < step) {
|
|
373
|
-
const x = t / step;
|
|
374
|
-
return x + x - x * x - 1;
|
|
375
|
-
}
|
|
376
|
-
if (t > 1 - step) {
|
|
377
|
-
const x = (t - 1) / step;
|
|
378
|
-
return x * x + x + x + 1;
|
|
379
|
-
}
|
|
380
|
-
return 0;
|
|
381
|
-
}
|
|
382
|
-
const CONTROL = 32;
|
|
383
|
-
class FormantRenderer {
|
|
384
|
-
plan;
|
|
385
|
-
filters = [new Resonator(), new Resonator(), new Resonator(), new Resonator()];
|
|
386
|
-
frequencies = new Float64Array([500, 1500, 2500, 3500]);
|
|
387
|
-
widths = new Float64Array([100, 150, 200, 250]);
|
|
388
|
-
gains = new Float64Array(4);
|
|
389
|
-
segmentIndex = 0;
|
|
390
|
-
segmentStart = 0;
|
|
391
|
-
cursor = 0;
|
|
392
|
-
phase = 0;
|
|
393
|
-
random;
|
|
394
|
-
source = 0;
|
|
395
|
-
dcIn = 0;
|
|
396
|
-
dcOut = 0;
|
|
397
|
-
pitchStep = 0;
|
|
398
|
-
voiced = 0;
|
|
399
|
-
noise = 0;
|
|
400
|
-
level = 0;
|
|
401
|
-
envelope = 0;
|
|
402
|
-
lastHz = BASE_F0;
|
|
403
|
-
constructor(plan) {
|
|
404
|
-
this.plan = plan;
|
|
405
|
-
this.random = plan.seed >>> 0 || 1;
|
|
406
|
-
}
|
|
407
|
-
nextRandom() {
|
|
408
|
-
let r = this.random;
|
|
409
|
-
r ^= r << 13;
|
|
410
|
-
r ^= r >>> 17;
|
|
411
|
-
r ^= r << 5;
|
|
412
|
-
this.random = r >>> 0;
|
|
413
|
-
return this.random / 2_147_483_648 - 1;
|
|
414
|
-
}
|
|
415
|
-
control(segment, local, rate) {
|
|
416
|
-
const timbre = TIMBRES[segment.phoneme];
|
|
417
|
-
const progress = local / Math.max(1, segment.frames);
|
|
418
|
-
const seconds = local / rate;
|
|
419
|
-
const tau = timbre.kind === "vowel" || timbre.kind === "nasal" || timbre.kind === "glide" ? 0.014 : 0.006;
|
|
420
|
-
const mix = 1 - Math.exp(-CONTROL / (rate * tau));
|
|
421
|
-
for (let band = 0; band < 4; band++) {
|
|
422
|
-
const f = this.frequencies[band] ?? 500;
|
|
423
|
-
const w = this.widths[band] ?? 100;
|
|
424
|
-
const targetF = timbre.f[band] ?? f;
|
|
425
|
-
const targetW = timbre.bw[band] ?? w;
|
|
426
|
-
const nextF = f + (targetF - f) * mix;
|
|
427
|
-
const nextW = w + (targetW - w) * mix;
|
|
428
|
-
this.frequencies[band] = nextF;
|
|
429
|
-
this.widths[band] = nextW;
|
|
430
|
-
this.gains[band] = timbre.amp[band] ?? 0;
|
|
431
|
-
this.filters[band]?.tune(nextF, nextW, rate);
|
|
432
|
-
}
|
|
433
|
-
let hz = segment.f0Start > 0 && segment.f0End > 0
|
|
434
|
-
? segment.f0Start + (segment.f0End - segment.f0Start) * Math.min(1, progress * 3)
|
|
435
|
-
: this.lastHz;
|
|
436
|
-
if (segment.f0End > 0)
|
|
437
|
-
this.lastHz = segment.f0End;
|
|
438
|
-
if (segment.vibrato && this.plan.vibratoDepth > 0) {
|
|
439
|
-
const fade = Math.min(1, Math.max(0, (seconds - 0.12) / 0.18));
|
|
440
|
-
const cents = this.plan.vibratoDepth * fade * Math.sin(2 * Math.PI * this.plan.vibratoRate * (this.cursor / rate));
|
|
441
|
-
hz *= 2 ** (cents / 1200);
|
|
442
|
-
}
|
|
443
|
-
hz *= 1 + 0.004 * this.nextRandom();
|
|
444
|
-
this.pitchStep = hz / rate;
|
|
445
|
-
let voiced = timbre.voiced;
|
|
446
|
-
let noise = timbre.noise;
|
|
447
|
-
let level = timbre.level;
|
|
448
|
-
switch (timbre.kind) {
|
|
449
|
-
case "stop": {
|
|
450
|
-
const closure = 0.6;
|
|
451
|
-
if (progress < closure) {
|
|
452
|
-
noise = 0;
|
|
453
|
-
voiced = timbre.voiced > 0 ? 0.12 : 0;
|
|
454
|
-
level = timbre.voiced > 0 ? 0.3 : 0;
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
const burst = (progress - closure) / (1 - closure);
|
|
458
|
-
noise = Math.exp(-burst * 5);
|
|
459
|
-
voiced = timbre.voiced * Math.min(1, burst * 3);
|
|
460
|
-
}
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
case "affricate":
|
|
464
|
-
if (progress < 0.3) {
|
|
465
|
-
noise = 0;
|
|
466
|
-
voiced = timbre.voiced * 0.2;
|
|
467
|
-
level *= 0.3;
|
|
468
|
-
}
|
|
469
|
-
break;
|
|
470
|
-
case "flap":
|
|
471
|
-
level *= 0.5 + 0.5 * Math.abs(Math.cos(Math.PI * progress));
|
|
472
|
-
break;
|
|
473
|
-
default:
|
|
474
|
-
break;
|
|
475
|
-
}
|
|
476
|
-
this.voiced = voiced;
|
|
477
|
-
this.noise = noise;
|
|
478
|
-
this.level = level * segment.amplitude;
|
|
479
|
-
}
|
|
480
|
-
fill(buffer) {
|
|
481
|
-
const plan = this.plan;
|
|
482
|
-
const rate = plan.sampleRate;
|
|
483
|
-
const attack = Math.max(1, Math.round(rate * 0.008));
|
|
484
|
-
const release = Math.max(1, Math.round(rate * 0.014));
|
|
485
|
-
for (let i = 0; i < buffer.length; i++, this.cursor++) {
|
|
486
|
-
let segment = plan.segments[this.segmentIndex];
|
|
487
|
-
while (segment !== undefined && this.cursor >= this.segmentStart + segment.frames) {
|
|
488
|
-
this.segmentStart += segment.frames;
|
|
489
|
-
this.segmentIndex += 1;
|
|
490
|
-
segment = plan.segments[this.segmentIndex];
|
|
491
|
-
}
|
|
492
|
-
if (segment === undefined) {
|
|
493
|
-
buffer[i] = 0;
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
const local = this.cursor - this.segmentStart;
|
|
497
|
-
if (local % CONTROL === 0 || local === 0)
|
|
498
|
-
this.control(segment, local, rate);
|
|
499
|
-
const saw = 2 * this.phase - 1 - polyBlep(this.phase, this.pitchStep);
|
|
500
|
-
this.phase += this.pitchStep;
|
|
501
|
-
this.phase -= Math.floor(this.phase);
|
|
502
|
-
this.source += 0.3 * (saw - this.source);
|
|
503
|
-
const excitation = this.source * this.voiced + this.nextRandom() * this.noise * 0.6;
|
|
504
|
-
let filtered = 0;
|
|
505
|
-
for (let band = 0; band < 4; band++) {
|
|
506
|
-
filtered += (this.gains[band] ?? 0) * (this.filters[band]?.step(excitation) ?? 0);
|
|
507
|
-
}
|
|
508
|
-
const dc = filtered - this.dcIn + 0.995 * this.dcOut;
|
|
509
|
-
this.dcIn = filtered;
|
|
510
|
-
this.dcOut = dc;
|
|
511
|
-
const target = (segment.rampIn && local < attack ? local / attack : 1) *
|
|
512
|
-
(segment.rampOut && segment.frames - local < release ? (segment.frames - local) / release : 1);
|
|
513
|
-
this.envelope += 0.2 * (target - this.envelope);
|
|
514
|
-
buffer[i] = Math.tanh(dc * 7) * this.level * this.envelope;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const LEVEL_TARGET = { vowel: 0.3, nasal: 0.22, glide: 0.26 };
|
|
519
|
-
function levelGain(segment, block, sung) {
|
|
520
|
-
const kind = TIMBRES[segment.phoneme].kind;
|
|
521
|
-
if ((kind !== "vowel" && kind !== "nasal" && kind !== "glide") || TIMBRES[segment.phoneme].voiced === 0)
|
|
522
|
-
return 1;
|
|
523
|
-
let sum = 0;
|
|
524
|
-
for (let i = 0; i < block.length; i++)
|
|
525
|
-
sum += (block[i] ?? 0) ** 2;
|
|
526
|
-
const rms = Math.sqrt(sum / Math.max(1, block.length));
|
|
527
|
-
if (rms < 1e-4)
|
|
528
|
-
return 1;
|
|
529
|
-
const ratio = (LEVEL_TARGET[kind] * segment.amplitude) / rms;
|
|
530
|
-
return Math.min(2, Math.max(0.5, ratio ** (sung ? 0.85 : 0.5)));
|
|
531
|
-
}
|
|
532
|
-
function applyLevel(block, gain, previousGain, ramp, volume) {
|
|
533
|
-
for (let i = 0; i < block.length; i++) {
|
|
534
|
-
const blend = i < ramp ? i / ramp : 1;
|
|
535
|
-
const g = previousGain + (gain - previousGain) * blend;
|
|
536
|
-
const value = (block[i] ?? 0) * g * volume;
|
|
537
|
-
block[i] = volume > 1 ? Math.tanh(value) : value;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
export function* renderChunks(plan, options = {}) {
|
|
541
|
-
const renderer = new FormantRenderer(plan);
|
|
542
|
-
const ramp = Math.max(1, Math.round(plan.sampleRate * 0.006));
|
|
543
|
-
const sung = plan.vibratoDepth > 0;
|
|
544
|
-
let previousGain = 1;
|
|
545
|
-
for (const segment of plan.segments) {
|
|
546
|
-
checkAbort(options.signal);
|
|
547
|
-
const block = new Float32Array(segment.frames);
|
|
548
|
-
renderer.fill(block);
|
|
549
|
-
const gain = levelGain(segment, block, sung);
|
|
550
|
-
applyLevel(block, gain, previousGain, ramp, plan.volume);
|
|
551
|
-
previousGain = gain;
|
|
552
|
-
yield block;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
export async function renderInto(plan, sink, options = {}) {
|
|
556
|
-
const renderer = new FormantRenderer(plan);
|
|
557
|
-
const ramp = Math.max(1, Math.round(plan.sampleRate * 0.006));
|
|
558
|
-
const sung = plan.vibratoDepth > 0;
|
|
559
|
-
const step = Math.max(1024, Math.round(plan.sampleRate / 4));
|
|
560
|
-
let previousGain = 1;
|
|
561
|
-
const fillFrom = async (block, offset) => {
|
|
562
|
-
if (offset >= block.length)
|
|
563
|
-
return;
|
|
564
|
-
renderer.fill(block.subarray(offset, Math.min(block.length, offset + step)));
|
|
565
|
-
await yieldToEventLoop();
|
|
566
|
-
checkAbort(options.signal);
|
|
567
|
-
return fillFrom(block, offset + step);
|
|
568
|
-
};
|
|
569
|
-
const next = async (index) => {
|
|
570
|
-
const segment = plan.segments[index];
|
|
571
|
-
if (segment === undefined)
|
|
572
|
-
return;
|
|
573
|
-
checkAbort(options.signal);
|
|
574
|
-
const block = new Float32Array(segment.frames);
|
|
575
|
-
await fillFrom(block, 0);
|
|
576
|
-
const gain = levelGain(segment, block, sung);
|
|
577
|
-
applyLevel(block, gain, previousGain, ramp, plan.volume);
|
|
578
|
-
previousGain = gain;
|
|
579
|
-
sink(block);
|
|
580
|
-
return next(index + 1);
|
|
581
|
-
};
|
|
582
|
-
await next(0);
|
|
583
|
-
}
|
|
584
|
-
export function renderPlan(plan, options = {}) {
|
|
585
|
-
const pcm = new Float32Array(plan.frames);
|
|
586
|
-
let offset = 0;
|
|
587
|
-
for (const chunk of renderChunks(plan, options)) {
|
|
588
|
-
pcm.set(chunk, offset);
|
|
589
|
-
offset += chunk.length;
|
|
590
|
-
}
|
|
591
|
-
return pcm;
|
|
592
|
-
}
|
|
593
|
-
export function encodePlanSync(plan, options = {}) {
|
|
594
|
-
const out = new Uint8Array(44 + plan.frames * 2);
|
|
595
|
-
out.set(wavHeader(plan.frames, plan.sampleRate));
|
|
596
|
-
let offset = 44;
|
|
597
|
-
for (const chunk of renderChunks(plan, options)) {
|
|
598
|
-
encodePcm16(chunk, out, offset);
|
|
599
|
-
offset += chunk.length * 2;
|
|
600
|
-
}
|
|
601
|
-
return out;
|
|
602
|
-
}
|
|
603
|
-
export async function encodePlan(plan, options = {}) {
|
|
604
|
-
const out = new Uint8Array(44 + plan.frames * 2);
|
|
605
|
-
out.set(wavHeader(plan.frames, plan.sampleRate));
|
|
606
|
-
let offset = 44;
|
|
607
|
-
await renderInto(plan, (chunk) => {
|
|
608
|
-
encodePcm16(chunk, out, offset);
|
|
609
|
-
offset += chunk.length * 2;
|
|
610
|
-
}, options);
|
|
611
|
-
return out;
|
|
612
|
-
}
|
|
613
|
-
export function planRequest(request, sampleRate) {
|
|
614
|
-
return request.kind === "speech" ? planSpeech(request, sampleRate) : planSong(request, sampleRate);
|
|
615
|
-
}
|
|
616
|
-
export function renderFormantSync(request, sampleRate, options = {}) {
|
|
617
|
-
return encodePlanSync(planRequest(request, sampleRate), options);
|
|
618
|
-
}
|
|
619
|
-
export async function renderFormant(request, sampleRate, options = {}) {
|
|
620
|
-
return encodePlan(planRequest(request, sampleRate), options);
|
|
621
|
-
}
|
|
622
|
-
//# sourceMappingURL=formant.js.map
|